同じ著者の『SQL 第2版 ゼロからはじめるデータベース操作』を読んだのでさらにSQLを理解するために読みました。
対象
この書籍の対象は半年から一年ほどSQLを書いている中級者とのことです。またSQLとはそもそもなんなのかを知りたい人にとっても有益です。
第1部
魔法のSQL
case
式
case
は結局ラベルの読み替えです。他のコードと組み合わせることで絶大な効果を発揮します。
- 単純
case
式
```sql:単純case
式
case sex
when '1' then '男'
when '2' then '女'
else 'その他' end
- 検索`case`式
- 基本的にはこちらを使う
```sql:検索`case`式
case when sex = '1' then '男'
when sex = '2' then '女'
else 'その他' end
-
case式は見つかった時点で打ち切られる
- 以降は無視される
-
注意点
- 戻り値のデータ型をそろえる
- 最終的には一つの値に定まるから
-
end
の書き忘れ -
else
は必ず書く- 書かないと
else null
となって結果が変わる- エラーにならないので厄介
- 書かないと
- 戻り値のデータ型をそろえる
-
県ごとの数値を地方ごとに変換して集計する
select case pref_name
when '徳島' then '四国'
when '香川' then '四国'
when '愛媛' then '四国'
when '高知' then '四国'
when '福岡' then '九州'
when '佐賀' then '九州'
when '長崎' then '九州'
else 'その他' end as district,
sum(population)
from PopTbl
group by district;
-
where
で出し分けるものをselect
を使って一発で表示する-
1
の男性と2
の女性人口を集計したい - レコード集約のための
sum
-
```sql:where
で出し分けるものを`select`を使って一発で表示する
select pref_name
sum(case when sex = '1' then population else 0 end) as cnt_m,
sum(case when sex = '2' then population else 0 end) as cnt_f
from PopTbl2
group by pref_name;
- 条件分岐させた`update`
```sql:条件分岐させた`update`
update Personnel
set salary = case when salary >=300000
then salary * 0.9
when salary > 250000 and salary < 280000
then salary * 1.2
else salary end;
- テーブル同士のマッチング
-
where
を使わずselect
で絞り込む
-
select CM.course_name,
case when exists (select course_id from OpenCourses OC
where month = 201806
and OC.course_id = CM.course_id)
then 'o' else 'x' end as '6月',
case when exists (select course_id from OpenCourses OC
where month = 201807
and OC.course_id = CM.course_id)
then 'o' else 'x' end as '7月',
case when exists (select course_id from OpenCourses OC
where month = 201808
and OC.course_id = CM.course_id)
then 'o' else 'x' end as '8月'
from CourseMaster CM;
-
case
の中で集約関数を使う-
having
を使わずselect
で分岐させる
-
```sql:case
の中で集約関数を使う
select std_id,
case when count(*) = 1
then max(club_id)
else max(case when main_club_flg = 'Y'
then club_id
else null end) end as main_club
from StudentClub
group by std_id;
## 必ずわかるウィンドウ関数
- ウィンドウとは
1. `partition by`によるレコード集合のカット
2. `order by`による順序づけ
3. フレーム句によるカレントレコードを中心としたサブセットの定義
## 自己結合の使い方
- 自己結合は非等値結合と組み合わせて使う
- 同じ値段だけど、商品名が違うレコードを検索するSQL
```sql:同じ値段だけど、商品名が違うレコードを検索するSQL
select distinct P1.name, P1.price
from Products P1 inner join Products P2
on P1.price = P2.price
and P1.name <> P2.name
order by P1.price;
3値論理とNULL
SQLはNULL
があるため3値論理となる。この項は理解度が低かったので、また読み直す必要がある。
-
NULL
とは- サングラスをかけた人の目の色
- 冷蔵庫の目の色
-
NUll
が入っていた場合の困ったことの事例-
not in
サブクエリでNULL
が入ると結果は常に空となる
-
-
NULL
は値ではない - 値でないから述語もうまく適用できない
- 無理やり適用すると
unknown
となる -
unknown
が入るとSQLが直感に反する動作となる - 段階的なステップに分けてSQLの動作を追うことが大切
最善の対策を挙げるなら、テーブルにNOTNULL制約を付けて極力NULLを排除すること
EXISTS
述語の使い方
述語とは戻り値が真偽値となる関数のこと。EXISTS
はド・モルガンの法則とともに効果的に使える。
ド・モルガンの法則
∀xPx=¬∃x¬Px
(すべてのxが条件Pを満たす=条件Pを満たさないxが存在しない)
∃xPx=¬∀x¬Px
(条件Pを満たすxが存在する=すべてのxが条件Pを満たさないわけではない)
- テーブルに存在しないデータを探す
考え方としては、全員が皆勤したと仮定した場合の集合を作り、そこから現実に出席した人々を引き算すればよいわけです。
select distinct M1.meeting, M2.person
from Meetings M1 cross join Meetings M2
where not exists
(select *
from Meetings M3
where M1.meeting = M3.meeting
and M2.person = M3.person);
- 「すべての行について~」という全称量化の表現を、「~でない行が1つも存在しない」という二重否定文へ変換する
select distinct student_id
form TestScores TS1
where not exists
(select *
from TestScores TS2
where TS2.student_id = TS1.student_id
and TS2.score < 50);
-
exists
だけが行の集合を引数にとる - その点で
exists
は高階関数の一種とみなせる -
SQL
には全称量化子に相当する演算子がないので、not exists
で代用する。
SQLの述語のほとんどが3値論理に従う中で、EXISTSだけは2値論理的に振る舞うという奇妙な仕様になっている
そのためunknown
もfalse
となる。よって条件をつくるときはNULL
を考慮しないといけません。
HAVING
句の力
SQL
は集合指向で考えられた言語です。
最初に学んだ言語のスキーマ(概念の枠組み)が心理的モデルとして固定され、それを通して世界を見るようになるため、異なるスキーマを持つ言語の理解が妨げられる
HAVING句は集合の性質を調べる道具となります。また現在の標準SQLではHAVING句を単独でも使えます。
HAVING句は集合の性質を調べる道具として使えます。特に集約関数やCASE式と組み合わせたときの記述力は強力無比です。
HAVING句は円(ベン図)を書くとわかりやすいです。
HAVING句で集合を切り分けて問題を解く際には、紙の上に円を描いてみるのが非常に効果的です
考え方は、何を持って集合と見なすかに着目します。
WHERE句が集合の要素の性質を調べる道具であるのに対し、HAVING句は集合自身の性質を調べる道具である。
where
は要素の性質を、having
は集合自身の性質を調べます。
・実体1つにつき1行が対応している➡要素なのでWHERE句を使う。
・実体1つにつき複数行が対応している➡集合なのでHAVING句を使う。
ループや代入を使わないでgroup by
やall
で最頻値を求める
- 最頻値を求めるALL述語の利用
select income, count(*) as count
from Grauates
group by income
having count(*) >= all (select count(*)
from Graduates
group by income);
NULLを含まないものを選択する
count
の二つの種類
COUNT(*)がNULLを数えるのに対し、COUNT(列名)は他の集約関数と同様、NULLを除外して集計するという結果の違いです。
=
であればNULL
は含まれていない。
select dpt
from Students
group by dpt
having count(*) = count(sbmt_date);
case
を使うと?
select dpt
from Students
group by dpt
having count(*) = sum(case when sbmt_date is not null
then 1 else 0 end);
特性関数を使いこなして、どんな複雑な条件でも記述する
- クラスの75%以上の生徒が80点以上のクラスを選択せよ。
select class
from TestResults
group by class
having count(*) * 0.75
<= sum(case when score >= 80
then 1
else 0 end);
- 50点以上を取った生徒のうち、男子の数が女子の数より多いクラスを選択せよ。
select class
from TestResults
group by class
having sum(case when score >= 50
and sex = '男'
then 1
else 0 end)
> sum(case when score >= 50
and sex = '女'
then 1
else 0 end);
- 女子の平均点が、男子の平均点より高いクラスを選択せよ。
- 女子だけのクラスの場合を考慮する
-
null
になると選択対象外になることを使う
-
- 女子だけのクラスの場合を考慮する
select class
from TestResults
group by class
having avg(case when sex = '男'
then score
else null end)
< avg(case when sex = '女'
then score
else null end);
SQL
は集団の特徴傾向を把握するのに向いています。
今回の例題でも、私たちが考えたのは、あくまでクラスが集団として持つ特徴や傾向性であって、誰が何点を取ったかという生徒の個人情報は詮索しません。構成員の匿名性を保ったまま集団の傾向を把握するこの考え方は、そのまま統計学の方法論でもあります。BIとSQLの親和性が高いのもうなずける話です。
全称文を集合で表現する
状態が「待機」の行数と、集合全体の行数が一致するグループを選択する
-
count
を使う- 総数と数値に変換したステータスとを比較する
この2つだけが持っていて、他の集合が持っていない性質とは何でしょう。それは、状態が「待機」の行数と、集合全体の行数が一致する、という性質です。
select team_id
from Teams
group by team_id
having count(*) = sum(case when status = '待機'
then 1
else 0 end);
-
MIN
とMAX
を使う- 同じであればその1種類の数値のみ
select team_id,
case when max(status) = '待機'
and min(status) = '待機'
then '総員スタンバイ'
else '隊長! メンバーが足りません'
end as status
from Teams group by team_id;
一意集合と多重集合
- 資材のダブっている拠点を選択する
select center
from Materials
group by center
having count(material) <> count(distinct material);
select center, material
from Materials M1
where exists (select *
from Materials M2
where M1.center = M2.center
and M1.receive_date <> M2.receive_date
and M1.material = M2.material);
バスケット解析
--商品マスタと店舗在庫テーブルを結合した結果が3行になる店舗が選択される
select SI.shop
from ShopItems SI inner join Items I
on SI.item = I.item
group by SI.shop
having count(SI.item) = (select count(item)
from Items);
HAVING
句のまとめ
HAVING句はCASE式や自己結合などと組み合わせた時に真価を発揮します。
HAVING句もまた集合指向言語の強力な武器の1つです。そしてその真価は、CASE式や自己結合といった他の武器と組み合わせたときに発揮されるのです。
- 集合の性質を調べるための条件の使い方一覧
-- colの値が一意である
count (distinct col) = count(col)
-- colにNULLが存在しない
count(*) = count(col)
-- colは歯抜けのない連番(開始値は1)
count(*) = max(col)
-- colは歯抜けのない連番(開始値は任意の整数)
count(*) = max(col) - min(col) + 1
-- colが1つだけの値を持つか、またはNULLである
min(col) = max(col)
-- すべてのcol_xの符号が同じである
min(col) * max(col) > 0
-- 最大値の符号が正で最小値の符号が負
min(col) * max(col) < 0
-- colは少なくとも1つのゼロを含む
min(abs(col)) = 0
-- colの最大値と最小値が指定した定数から同じ幅の距離がある
min(col - 定数) = - max(col - 定数)
ウィンドウ関数で行間比較を行なう
相関サブクエリでやっていたものをウィンドウ関数で行ないます。相関サブクエリはネストが深く読みにくく、パフォーマンスも落ちるためです。
- 成長、後退、現状維持を一度に求める
- チャートランキングのようなもの
select year, current_sale as sale,
case when current_sale = pre_sale
then '→'
when current_sale > pre_sale
then '↑'
when current_sale < pre_sale
then '↓'
else '-' end as var
from (select year,
sale as current_sale,
sum(sale) over (order by year
range between 1 preceding
and 1 preceding) as pre_sale
from Sales) TMP
order by year;
sum
は見かけだけで実際集約はしません。
SUM(sale)という形でSUM関数を使っているようにも見えますが、実はこれは見かけだけのことで、本物の(というのも妙な表現ですが)集約関数のSUMのようにテーブルのレコード数を減らす(集約する)動作はしないのです
またウィンドウ関数は物理的なレコードの順序で集計範囲を設定できます。
ウィンドウ関数の場合、……フレーム句でRANGEをROWSに変えることで、物理的なレコードの順序で集計範囲を設定できる
-
ウィンドウ関数 vs 相関サブクエリ
- ウィンドウ関数ではサブクエリを単体で実行できる
- 理解しやすい
- テーブルへのスキャンも一回なのでパフォーマンスも良い
- ウィンドウ関数ではサブクエリを単体で実行できる
-
各商品分類について、平均単価より高い商品を選択する
select shohin_mei, shohin_bunrui, hanbai_tanka
from (select shohin_mei, shohin_bunrui, hanbai_tanka,
avg(hanbai_tanka)
over(partition by shohin_bunrui) as avg_tanka
from Shohin) TMP
where hanbai_tanka > avg_tanka;
- ウィンドウ関数は元のテーブルに列として結果を追加するだけ
- あとは比較すれば良い
このウィンドウ関数の結果のすばらしい(都合の良い)ところは、商品分類ごとの平均単価を計算していながらも、レコードを集約せずそのまま元のテーブルに列として結果を追加するだけ、という情報保全性が働くところです。
外部結合の使い方
本来SQL
は帳票作成のための言語ではないが実際にはよく利用されています。外部結合を使って表を成形する便利な方法もたくさんあります。
- 行→列に水平展開する
-
case
を使う
-
select name,
case when sum(case when course = 'SQL入門'
then 1 else null end) = 1
then 'o' else null end as 'SQL入門',
case when sum(case when course = 'UNIX基礎'
then 1 else null end) = 1
then 'o' else null end as 'UNIX基礎',
case when sum(case when course = 'Java中級'
then 1 else null end) = 1
then 'o' else null end as 'Java中級'
from Courses
group by name;
- 列→行に変換する
-
union all
を使う
-
select employee, child_1 as child from Personnel
union all
select employee, child_2 as child from Personnel
union all
select employee, child_3 as child from Personnel;
- 表側を入れ子にしたい
- 外部結合を一度にまとめる
表側を入れ子にするときは、その形のマスタをあらかじめ用意してやればよい
- 商品ごとに総計でいくつ売れたかを調べる帳票を出力する
-
1 : 多
と考えてgroup by
でまとめる
-
select I.item_no, sum(SH.quantity) as total_qty
from Items I left outer join SalesHistory SH
on I.item_no = SH.item_no
group by I.item_no;
結合は、一対一でなくとも一対多ならば行数は(不当には)増えない
SQLで集合演算
注意点
- 重複を認める時は
UNION ALL
を使う- ALLオプションを付けるとソートが行なわれないのでパフォーマンスが向上する
SQLの集合演算子にも、重複を認めるバージョンと認めないバージョンの2通りが用意されています。通常、UNIONやINTERSECTをそのまま使うと、結果から重複行を排除します。もし重複行を残したい場合は、ALLオプションを付けて、UNIONALLのように記述します。
- 演算の順番に優先順位がある
- UNIONとEXCEPTに対して、INTERSECTのほうが先に実行される
- 除算の標準的な定義がない
テーブル同士の比較
-
UNION
は冪等性をもつ- 繰り返し処理を実行しても、一度だけ実行した場合と結果が同じになる
- インストールとアンインストール
- 注意
-
UNION ALL
は変化する - 重複があれば
UNION
も変化する
-
- 主キーは大事
- 繰り返し処理を実行しても、一度だけ実行した場合と結果が同じになる
相等性をチェックするには?
- 「AとBは互いに等しい」
-
A union B = A intersect B
で交差を使う- AとBが違えば必ず
union
の方が多くなる-
(A union B) except (A union B)
が空集合かどうかを調べるとわかる- 列数や列名関係ない
-
null
を含むテーブルにも使える - 事前準備がいらない
-
- AとBが違えば必ず
-
select case when count(*) = 0
then '等しい'
else '異なる' end as result
from ((select * from tbl_A
union
select * from tbl_B)
except
(select * from tbl_A
intersect
select * from tbl_B)) TMP;
- 違うことがわかったらその行を表示する
(select * from tbl_A
except
select * from tbl_B)
union all
(select * from tbl_B
except
select * from tbl_A);
SQLで数列を扱う
もともとSQL
は順序の概念がなかったので手続き的に扱うことが難しかったのが、ウィンドウ関数
によってスムーズにできるようになってきました。
- 数を集合ととらえることで連番を作れる
- ビューとして保存しておくと後々便利
- 差集合として欠番を洗い出すこともできる
-
EXCEPT
やNOT IN
などを使う
-
- 差集合として欠番を洗い出すこともできる
- ビューとして保存しておくと後々便利
- 差集合の4通り
- 王道の
except
- わかりやすい
not in
-
not in
と考え方が同じ'not exists` - ひねくれ者の
外部結合
- 王道の
連番(シーケンス)を見つける
- 自己結合で始点と終点の組み合わせを作る
- S2.seat = S1.seat + (:head_cnt -1)
- 始点-終点間の全ての点が満たすべき条件を記述する
- すべての行の座席の状態が「空」であること
- 条件Pを満たさない行が存在しない
-
not exists
とS3.status <> '空'
-
- 条件Pを満たさない行が存在しない
- すべての行の座席の状態が「空」であること
select S1.seat as start_seat, '〜',
S2.seat as end_seat
from Seats S1, Seats S2
where S2.seat = S1.seat + (:head_cnt -1)
and not exists
(select *
from Seats S3
where S3.seat between S1.seat and S2.seat
and S3.status <> '空');
ウィンドウ関数ではどうなるか?
-
終点 - 始点 = 2
となることを使う
select seat, '〜', seat + (:head_cnt- 1)
from (select seat,
max(seat)
over(order by seat
rows between (:head_cnt - 1) following
and (head_cnt - 1) following) as end_seat
from Seats
where status = '空') TMP
where end_seat - seat = (:head_cnt - 1);
- 特定の取引日における株価が上昇しているか否か
select deal_date, price
case sign(price - max(price)
over(order by deal_date
rows between 1 preceding
and 1 preceding))
when 1 then 'up'
when 0 then 'stay'
when -1 then 'down' else null end as diff
from MyStock;
SQL
上達に欠かせないのは、「集合」と「述語」に親しむことです。
-
SQL
のデータの扱い方- 順序を無視した集合とみなす方法
- 順序を持った集合とみなす
- 基本的にはウィンドウ関数を使う
- 「すべての〜」を表現するには
not exists
を使う
SQL
を速くするぞ
SQL
の家庭の医学。ちょっと遅いなと感じたときの初期診断ツール。低速ストレージへのアクセスを減らします。ボトルネックを見つけ、そこを重点的に解消しましょう。
サブクエリを引数に取る場合、in
よりexists
を使う
-
not in
とnot exists
は大抵同じ結果を返す -
exists
の方が速い-
id
にインデックスがあればid
の参照だけで済む - 合致する行が1行でもあればそこで打ち切る
-
-
in
の方が可読性は高い- そんなに遅くなければそのままで良い
-
Oracle
はin
でもインデックススキャンする -
PosgreSQL
はin
の速度改善がされている
-
- そんなに遅くなければそのままで良い
select *
from Class_A
where id in (select id from Class_B);
select *
from Class_A A
where exists
(select *
from Class_B B
where A.id = B.id);
サブクエリを引数に取る場合、in
よりも結合を使う
select A.id, A.name
from Class_A A inner join Class_B B
on A.id = B.id;
ソートを回避する
ソートは裏で動いています。
- ソートが発生する代表的な演算
group by
order by
- 集約関数
sum
count
avg
max
min
distinct
- 集合演算子
union
intersect
except
- ウィンドウ関数
rank
row_number
- 等
集合演算子のALL
オプションをうまく使う
-
union
、intersect
、except
を使うと重複排除のためソートする- 重複が発生しないとわかっているなら
union all
を使う- 実装状況のばらつきに注意
- 重複が発生しないとわかっているなら
distinct
をexists
で代用する
- 売上のあった商品を探す
select distinct I.item_no
from Items I inner join SalesHistory SH
on I.item_no = SH.item_no;
select item_no
from Items I
where exists (select *
from SalesHistory SH
where I.item_no = SH.item_no);
min
・max
でインデックスを使う
select max(item)
from Items;
select max(item_no)
from Items;
where
句で書ける条件はhaving
句には書かない
-
group by
の集約は事前に行数を絞り込んだほうが負荷が軽くなる -
where
句でインデックスが利用できる
そのインデックス、本当に使われてますか?
- 索引列に加工を行なっている
- インデックスを利用するときは、列は裸
-- インデックスが使われない
select * from SomeTable where col_1 * 1.1 > 100;
-- 右側での式はOK
select * from SomeTable where col_1 > 100 / 1.1
- インデックス列に
null
が存在する- 原則的に
null
は列の正当な値ではない
- 原則的に
- 否定形を使っている
- インデックスが使用できない
<>
!=
not in
- インデックスが使用できない
-
or
を使っている - 複合索引の場合に、列の順番を間違えている
- 最初の列を先頭にする
- 順番も崩さない
- 後方一致、または中間一致の
like
述語を用いている-
like
を使うときは前方一致のみインデックスを使える
-
- 暗黙の型変換を行なっている
- オーバーヘッド発生させる
- インデックスも使用不可
-- x
select * from SomeTable where col_1 = 10;
-- o
select * from SomeTable where col_1 = '10';
-- o
select * from SomeTable where col_1 = cast(10, as char(2));
中間テーブルを減らせ
-
having
句を活用する -
in
で複数のキーを利用する場合は一箇所にまとめる- 型変換を気にしなくて良い
- インデックスを利用できる
select *
from Addresses1 A1
where id || state || city in (select id || state || city
from Addresses2 A2);
- 集約よりも結合を先に行なう
- ビューのご利用は計画的に
SQLプログラミング作法
- 名前に意味を持たせる
- なるべくコメントはあったほうがいい
- SQLは段階的なデバッグがほとんどできない
-
from
句から書く - 読みにくいコードは何物も、誰も解決してくれない
RDB近現代史
- 最初は階層型だった
- アポロ計画
- 1968年IBMの社内報
- E.F.コッド
- 「大容量データバンクのための関係モデル」
- E.F.コッド
- RDBの「テーブル」と「SQL」の発明
- 「ループをなくすのがRDBを考えた目的だった」
- 1981年チューリング賞でのコッド発言
- 破壊的イノベーション
- ローエンドのUNIXマシンでも動く
- 最初は実用に耐えないおもちゃだった
- だがユーザフレンドリ
- 最初は実用に耐えないおもちゃだった
- ローエンドのUNIXマシンでも動く
- 「ループをなくすのがRDBを考えた目的だった」
- RDBの限界
- 性能と信頼性のトレードオフ
- ボトルネックとなる
- データモデルの限界
- 「グラフ」と「非構造化データ」に弱い
- 木構造のグラフ
- ネットワーク構造の循環グラフや非循環グラフ
- 非構造化データの
JSON
- スキーマがない
- 「グラフ」と「非構造化データ」に弱い
- 性能と信頼性のトレードオフ
-
NoSQL
-
KVS
-
Redis
やmemcached
-
- ドキュメント指向型DB
MongoDB
- ACIDの犠牲がある
- 破壊的イノベーションではない
-
なぜ"関係"モデルという名前なの?
2つの集合の直積の部分集合を集合論では「二項関係」と呼びます。それをコッドはn項に拡張しました。これはcross join
として実装されています。
関係に始まり関係に終わる
SQL
はUNIX
の「なんでもファイル主義」のように「なんでも関係主義」です。SELECT
文とは実は、テーブル(関係)を引数にとってテーブル(関係)を返す関数なのです。閉包性を持っています。関係は「自由に四則演算が可能な集合」であり、集合論や群論などをそのまま援用できます。
アドレス、この巨大な怪物
データベースをアドレスから解放することが目標でした。データの管理方法を、位置から内容へ変えることです。これは人間が認識しやすい有意味な世界を作るということです。SQL
やRDB
はアドレスの呪縛からいかにして逃れるかという問題への一つの解答でした。『賢いデータ構造と間抜けなコードのほうが、その逆よりずっとまし』。
順序をめぐる冒険
順序を持つか持たないかに関して対立する保守派と革新派の話です。
group by
とpartition by
partition
は「類」です。SQL
やRDB
には集合論や群論などの成果が多く取り入れられています。
手続き型から宣言型・集合指向へ頭を切り替える7箇条
- IF文やCASE文は、CASE式で置き換える。SQLはむしろ関数型言語と考え方が近い
- ループはGROUPBY句とウィンドウ関数で置き換える
- テーブルの行に順序はない
- テーブルを集合と見なそう
- EXISTS述語と「量化」の概念を理解しよう
- 「全ての」を「〜ないものはない」に変換する
- 読みにくいがパフォーマンスが良い
- HAVING句の真価を学ぶ
- 四角を描くな、円を描け
神のいない論理
真と偽と「可能」の3値論理の歴史です。
SQLと再帰集合
数学の歴史をさらに紐解きます。
NULL
撲滅委員会
- コードの場合──未コード化用コードを割り振る
-
0 : 未知
、9 : 適用不能
など
-
- 名前の場合──「名無しの権兵衛」を割り振る
-
UNKNOWN
を割り振る
-
- 数値の場合──0で代替する
-
NULL
を0に変換する
-
- 日付の場合──最大値・最小値で代替する
- 「0001-01-01」や「9999-12-31」のように可能な最大値・最小値を使う
1. まずデフォルト値を入れられないか検討する。2.どうしようもない場合だけNULLを許可する。
2.どうしようもない場合だけNULLを許可する。
SQLに置ける存在の階層
興味深い内容でした。
読了後のまとめ
第1部はSQLを集合と捉えた踏み込んだ内容で、第2部はSQLの歴史や数学との繋がりが教養書のように興味深く理解できるように書かれていました。単純に実務で使うからというSQLの観点から、哲学や数学の歴史の一部として納得できるようになりました。表現も平易でわかりやすく、とても質の高い本だと思います。