Table に Record を Insert する / TypeScript エンジニアのための PostgresDB
Tweet対応する参考書籍の章
- 4-4 データの追加
SQL と TypeScript の対比
では今回学習する SQL をみてみましょう。
insert into table_name
で対象のテーブルに新規 item を追加する構文です。DB においては Item ではなく正確には Record と表現します。つまり Table に Record を追加する、という表現を使います。
-- テーブルを作成する
create table articles
(
id serial,
name text,
category text
);
-- テーブルに item = record を追加する
insert into articles(name, category)
VALUES ('name1', 'category1');
-- 結果を確認する
select *
from articles;
では TypeScript 的に見てみましょう。ようはテーブルを作った段階では空であった配列に、新規 item を追加するという操作です。この際、当然ですが追加する item は正しい型でないといけません。ですから例えば name が number
だったら型エラーですね。
type Article = {
id: number;
name: string;
category: string;
};
// テーブルを作る
type Articles = Article[];
// 最初はテーブルには何も入っていない
const articles: Articles = [];
// 新規アイテムを追加する
const newArticles: Articles = [
...articles,
{
id: 1,
name: "name1",
category: "category1",
},
];
JS エンジニアから見るとちょっとクセのある insert into 構文
実は以下三つの書き方はどれも構文的には正しいものです。ちょっと JS エンジニア的には癖があるように見えるので解説します。
insert into articles(name, category)
VALUES ('name1', 'category1');
insert into articles(category, name)
VALUES ('category2', 'name2');
insert into articles(name)
VALUES ('name3');
選択できるのは keyof Article のみ
まず最初に、name, category
と自由に引数の名前を指定しているようにみえますが、これは必ず type Article
に含まれているプロパティである必要があります。ですから title
などを使ったら絶対にいけません。
-- title はないため、エラーになる
insert into articles(title, category)
VALUES ('name1', 'category1');
つまり TS 的なイメージでいうと以下のような制限がかかっているということです。keyof Article
にないものは、指定してはダメだということです。
type Article = {
id: number;
name: string;
category: string;
};
const insertIntoArticles = (...x: (keyof Article)[]) => () => { //... }
順番も重要
さらに VALUES('value1', 'value2')
の部分も少し特殊です。Tuple のようになっていると考えるといいと思います。つまり (name, category)
と指定したら VALUES('value1': Article["name"], 'value2': Article["category"])
のように型制限がかかっています。引数的な (name, category)
の部分で「指定した順番」に、VALUES('value1', 'value2')
の部分が対応するのです。
type Article = {
id: number;
name: string;
category: string;
};
type Arguments = ["name", "category"];
const insertIntoArticle = (...x: Arguments) => (
...values: [Article["name"], Article["category"]]
) => {
//...
};
なので (category, name)
と順番を逆に指定したら VALUES('value1': Article["category"], 'value2': Article["title"])
のように、対応が反対になります。とにかく引数的な (name, category)
の部分で「指定した順番」が重要になるのです。
この事実を踏まえて冒頭の SQL をみてみるとよく理解できるはずです。とにかく冒頭の引数的なことろは keyof Article
からしか選択できません。そして配置した順番が、VALUES
で指定する順番を決定していますので、(name, category)
の場合と (category, name)
の場合で、name を入れる位置がひっくり返っているわけですね。
insert into articles(name, category)
VALUES ('name1', 'category1');
insert into articles(category, name)
VALUES ('category2', 'name2');
insert into articles(name)
VALUES ('name3');
指定しなかった部分はどうなっているのか?
-- 問題ない
insert into articles(name, category)
VALUES ('name1', 'category1');
-- エラー
insert into articles(name)
VALUES ('name3');
さて今まで id
は指定してきませんでしたが問題はありませんでした。同様に category
を指定しなくても name
を指定しなくても構文的には問題がありません。なぜでしょうか。
実はこれは default 値が勝手に入れられます。デフォルト値という考え方が PostgresDB にもあるという点が非常に重要です。
const insertIntoArticles = ({ id = ++index, name = null, category = null }: Article) => {
return {
id,
name,
category,
};
};
まず id
を指定しなかった場合は id
の型定義は serial
なので、デフォルト値として連番の整数が入ります。
それ以外の name
と caregory
はそれぞれ型定義は text
なので特殊なデフォルト値ではなく単純に null が使用されます。
defautl 値を指定したい場合には以下のように書きます。(これは現状覚える必要はないと思います。参考まで)
create table demo
(
id integer,
name text default 'default value'::text
);
ということで、指定したなかった部分は「デフォルト値」が使用されるという点を理解しましょう。
この勢いで PostgresDB をマスターだ!
初めての SQL 実行お疲れ様でした! 二個できたらこれを100日継続したら200個できるので、継続しさえすればマスターしたも同然です!
がんばっていきましょう!