対応する参考書籍の章

  • 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 だったら型エラーですね。

TS的に記述
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 などを使ったら絶対にいけません。

Shemaに含まれない名称はだめ
-- 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');

指定しなかった部分はどうなっているのか?

id,category等なくても問題がないのはなぜか
-- 問題ない
insert into articles(name, category)
VALUES ('name1', 'category1');

-- エラー
insert into articles(name)
VALUES ('name3');

さて今まで id は指定してきませんでしたが問題はありませんでした。同様に category を指定しなくても name を指定しなくても構文的には問題がありません。なぜでしょうか。

実はこれは default 値が勝手に入れられます。デフォルト値という考え方が PostgresDB にもあるという点が非常に重要です。

idはデフォルト値がserial
const insertIntoArticles = ({ id = ++index, name = null, category = null }: Article) => {
  return {
    id,
    name,
    category,
  };
};

まず id を指定しなかった場合は id の型定義は serial なので、デフォルト値として連番の整数が入ります。

それ以外の namecaregory はそれぞれ型定義は text なので特殊なデフォルト値ではなく単純に null が使用されます。

defautl 値を指定したい場合には以下のように書きます。(これは現状覚える必要はないと思います。参考まで)

default値を指定する
create table demo
(
    id     integer,
    name text default 'default value'::text
);

ということで、指定したなかった部分は「デフォルト値」が使用されるという点を理解しましょう。

この勢いで PostgresDB をマスターだ!

初めての SQL 実行お疲れ様でした! 二個できたらこれを100日継続したら200個できるので、継続しさえすればマスターしたも同然です!

がんばっていきましょう!