Copyright 2016-2018 G1Systems Inc.
SQLの書き方
初級者・中級者向け
1
Copyright 2016-2018 G1Systems Inc.
はじめに
SQLおじさんですw
SQLServerだけではなく、RDB全般について
専門的に仕事を請けています。
金融から、製造業、ネットゲームまで、オー
ルランドに対応しております。
その中で、今日はSQLの基礎についてお話し
ます。
2
Copyright 2016-2018 G1Systems Inc.
DBサーバの構造を知ろう!
3
Copyright 2016-2018 G1Systems Inc.
RDBMSの内部データ構造
RDBMSは、レコード単位でデータを保存せず、
ページ単位で、データを保存しています。
SQLServerは 8KByteです。
4
空き
ヘッダ情報
1レコード 2レコード
3レコード
4レコード
5レコード
6レコード
7レコード
データは1ページ単位で読んでから、
レコード、カラムに切り分ける。
ページサイズ 8KByte
Copyright 2016-2018 G1Systems Inc.
5
実データ
(生き物)
インデックス ア~ノ EEEEEEEEEEE
ハ~ン FFFFFFFFFFF
ア~ス DDDDDDDDDDD
セ~ノ CCCCCCCCCCC
ハ~ユ BBBBBBBBBBB
ヨ~ン AAAAAAAAAAA
アリ 99999999999
イヌ 88888888888
ウシ 77777777777
…… ……
セミ 66666666666
…… ……
はページ
ID 名前 分類 体長 特徴
…… ……
ID 名前 分類 体長 特徴
ID 名前 分類 体長 特徴
10001 アリ 昆虫 1~3mm
90012 ネコ 哺乳類 20~60cm
…… ……
SQLServer のプライマリキーは、基本クラスタードイン
デックスになりリーフが実データになります。
Copyright 2016-2018 G1Systems Inc.
(余談)インメモリデータベースについて
メモリが安くなっていますから、すべてメモリにキャッ
シュされているシステムも少なくありません。そのため、
全てメモリにキャッシュされているなら、インメモリデー
タベースにする必要がないと考える人も多いです。
しかし、それは間違いです。
インメモリデータベースは、HDDを意識する必要がない
ため、ページではなく、レコード単位でデータにアクセス
でき、インデックスの構造も違いますから、
無駄読み、無駄書きが少なくなるため高速になります。
6
Copyright 2016-2018 G1Systems Inc.
実行プランを見てみよう!
7
Copyright 2016-2018 G1Systems Inc.
サンプルSQLのテーブルについて
スクリプトを流すと、テーブルが2つとランダムなダミーデータが
作られます。
■ 売上
売上日にインデックスがある。
SYSDATEから1年前のデータが10万件。
SYSDATEの1年前~2年前のデータが100件。
■ 顧客
ID 1~1000 のデータが作られます。
1~100までが「削除フラグ = 1」になります。
8
Copyright 2016-2018 G1Systems Inc.
変数を使って実行プランを見るときの注意!
9
変数を使うと、実行プランは変数にデフォルト値を使って作られ
ます。(推定実行プランも)
変数によって最適な実行計画が欲しいときは、
OPTION(RECOMPILE)を指定し、
【実際の実行プランを含める】を ON にして実行してください。
Copyright 2016-2018 G1Systems Inc.
ヒットするデータが少ないと予想したとき
10
インデックスを抽出して
プライマリーキー(実データ)
をループしながら結果セットを
作っていきます。
Copyright 2016-2018 G1Systems Inc.
ヒットするデータが少ないと予想したとき
RowIDLists[] rowIDs New RowIDLists[] ; // ヒットしたもの
RetrunRows[] retRows New RetrunRows[] ; // 戻すレコード
rowIDs = IX_売上日.getRange(#日付F#, #日付T#); //日付範囲
for(RowID rowID:rowIDs){
// 計算処理
retRows.add(売上.getRow(rowID));
}
Return retRows;
11
Copyright 2016-2018 G1Systems Inc.
ヒットするデータが多いと予想したとき
12
実データを全部読み取って、要
らない行をフィルタします。
読み取った行数 100100行
実際の行数 100000行
Copyright 2016-2018 G1Systems Inc.
ヒットするデータが多いと予想したとき
RetrunRows[] retRows New RetrunRows[]; // 戻すレコード
for(Row row:売上){ // 「売上」テーブルを総スキャン
if(row.売上日 >= #日付F# && row.売上日 <= #日付T#){
// 計算処理
retRows.add(row);
}
}
Return retRows;
13
Copyright 2016-2018 G1Systems Inc.
SQLが実行されるまでRDBMSがすること
SQL文のシンタックス(文法)チェック
DBオブジェクト(テーブルなど)の存在チェック
DBオブジェクトに対する権限チェック
カラムなどの存在チェック
パース処理(文法上の整理)
テーブル・インデックスの統計情報から実行プラン作成
マシン語に翻訳(コンパイル)
SQLは、実行前に超重い処理をしています!
14
Copyright 2016-2018 G1Systems Inc.
SQLが書きにくい理由
手続き型と同じ方法で考えているから!
15
Copyright 2016-2018 G1Systems Inc.
言語水準は高い方から低い方へ考える
16
自然言語
基本設計
詳細設計
詳細設計
最後はマシン語になる!
Copyright 2016-2018 G1Systems Inc.
O/Rマッパーなどを使った開発のイメージ
17
材料を集め
組み立て
ディスプレイ
パーツを削りだし
(ここだけSQL)
どのように組み
合わせるか仕様
書が必要になる。
Copyright 2016-2018 G1Systems Inc.
SQLはプログラムをオプティマイザが書く!
18
自然言語
基本設計?
詳細設計
SQL
実行計画
SQLは言語水準が高く、
プログラムというより
は、詳細設計に近い!
Copyright 2016-2018 G1Systems Inc.
SQLで処理することのイメージは
19
材料を集め
組み合わし
削り出す
別言語で
ディスプレイ
Copyright 2016-2018 G1Systems Inc.
手続き型プログラムはHow、 SQLはWhat
通常のプログラム
(手続き型言語 ≒ オブジェクト指向言語)は、
「どう処理するか?(How)」を記述する。
SQLを書くときは、「何が欲しいか?(What)」を記述
するべきですが、 「どう処理するか?(How)」を先に
考えてしまう。
これは、「コーディングしてから設計を考える」ことにな
り、非常に書きにくい(考えにくい)
そのため、SQLを難しく感じてしまうのです。
20
Copyright 2016-2018 G1Systems Inc.
SQLの書き方
21
時間の制約があるので
FROM句とWHERE句について解説します。
Copyright 2016-2018 G1Systems Inc.
FROM句について
22
Copyright 2016-2018 G1Systems Inc.
FROM句はデータの基
どこから
(テーブル・ビュー・サブクエリなど)
データを持ってくるかを記述する。
複数のソースがあるときは、その結合方法も
記述する。
(JOINのこと)
23
Copyright 2016-2018 G1Systems Inc.
INNER JOIN のイメージ
24
Table_A a Table_B b
WHRER句の抽出条件
a.B_ID = b.ID
内側になるレコード
だけが対象となる。
Copyright 2016-2018 G1Systems Inc.
LEFT OUTER JOIN のイメージ
25
Table_A a Table_B b
WHRER句の抽出条件
a.B_ID = b.ID
左側すべてと、結合
条件に一致する右側
のレコードが対象と
なる。
Copyright 2016-2018 G1Systems Inc.
RIGHT OUTER JOIN のイメージ
26
Table_A a Table_B b
WHRER句の抽出条件
a.B_ID = b.ID
右側すべてと、結合
条件に一致する右側
のレコードが対象と
なる。
Copyright 2016-2018 G1Systems Inc.
FULL OUTER JOIN のイメージ
27
Table_A a Table_B b
WHRER句の抽出条件
a.B_ID = b.ID
両方のテーブルを対
象とし、結合条件で
結合する。
Copyright 2016-2018 G1Systems Inc.
CROSS JOIN のイメージ
28
Table_A a Table_B b
a.B_ID = b.ID
全ての組合せが
結果として返る。
Copyright 2016-2018 G1Systems Inc.
※ CROSS JOIN
左右のテーブルの全組み合わせになる。
SELECT *
FROM Table_A
CROSS JOIN Table_B;
または、
SELECT *
FROM Table_A, Table_B;
Table_Aに100件、Table_Bに100件あれば、10000件出
力される。
29
Copyright 2016-2018 G1Systems Inc.
CROSS JOIN はダミーを作るときなど
0~9まで入ったnumテーブルを用意して……
SELECT
(a.id * 100 + b.id * 10 + c.id) AS id
'ダミー文字列'
|| (a.id * 100 + b.id * 10 + c.id) AS col1
FROM
num a, num b, num c;
30
Copyright 2016-2018 G1Systems Inc.
テスト(パラメータテーブル)でも使える
xxx区分(A~D)、xxx種別(01~09)のすべての
組み合わせ(テストパターン)を作る。
-- CREATE TABLE wrk_テストパターン AS
SELECT
a.区分, a.区分名, b.種別, b.種別名
INTO wrk_テストパターン
FROM
xxx区分 a, xxx種別 b;
31
Copyright 2016-2018 G1Systems Inc.
INNER JOIN と OUTER JOIN
32
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
生徒マスタ 特待生マスタ
INNER JOIN と OUTER JOIN で、結果はどう変わる?
生徒マスタ a INNER JOIN 特待生マスタ b ON a.ID = b.生徒ID
生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID
生徒マスタ a RIGHT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID
1
削除フラグ
0
0
0
0
0
Copyright 2016-2018 G1Systems Inc.33
生徒マスタ 特待生マスタ
生徒マスタ a INNER JOIN 特待生マスタ b ON a.ID = b.生徒ID
INNER JOIN
INNER JOIN は、マッチした部分だけが残る。
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
0
0
0
0
Copyright 2016-2018 G1Systems Inc.34
生徒マスタ 特待生マスタ
生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID
LEFT OUTER JOIN
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
0
0
0
0
LEFT OUTER JOIN は、左側テーブルがすべて残る。
Copyright 2016-2018 G1Systems Inc.35
生徒マスタ 特待生マスタ
生徒マスタ a RIGHT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID
RIGHT OUTER JOIN
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
0
0
0
0
RIGHT OUTER JOIN は、右側テーブルがすべて残る。
Copyright 2016-2018 G1Systems Inc.36
例題
すべての生徒のリストに、特待生マスタの削除フラグが 0の
特待生ランクを出力してください。
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
生徒マスタ 特待生マスタ
1
削除フラグ
0
0
0
0
0
Copyright 2016-2018 G1Systems Inc.
解答
37
SELECT a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ここに結合条件を追加
これを出力
したくない
生徒マスタ 特待生マスタ
2 C
生徒ID ランク
3 A
10 B
33 C
40 B
48 C
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
0
0
0
0
Copyright 2016-2018 G1Systems Inc.
これはNG!
38
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
WHERE
b.削除フラグ = 0;
Copyright 2016-2018 G1Systems Inc.
なぜNGか?
39
WHERE 削除FLAG = 0 では、
NULL = 0 となる、 のレコードが出力できない。
1行だけになってしまう。
← NULL = 0 は False
← NULL = 0 は False
← NULL = 0 は False
生徒マスタ 特待生マスタ
2 C
生徒ID ランク
3 A
(4) NULL
(5) NULL
(6) NULL
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
NULL
NULL
NULL ← NULL = 0 は False
10 B
33 C
40 B
0
0
0
(1) NULL NULL
← 1= 0 は False
Copyright 2016-2018 G1Systems Inc.
サブクエリーは意味は正しいけれど……
40
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN
(SELECT * FROM 特待生マスタ
WHERE 削除フラグ = 0) b
ON a.ID = b.生徒ID;
Copyright 2016-2018 G1Systems Inc.
正しくはこうする!
41
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND 0 = b.削除フラグ;
Copyright 2016-2018 G1Systems Inc.
イメージにすると……
42
生徒マスタ a 特待生マスタ b
a.ID = b.生徒ID
AND b.削除フラグ = 0
a.ID = b.生徒ID
AND b.削除フラグ <> 0
a.ID <> b.生徒ID
AND b.削除フラグ = 0
a.ID <> b.生徒ID
AND b.削除フラグ <> 0
b.削除フラグ = 0 で
全体を削りたい訳ではない。
Copyright 2016-2018 G1Systems Inc.
SQLServer や Oracle などはサブクエリを直す
SQLServer や Oracle などの商用DBは、
44
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN
(SELECT * FROM 特待生マスタ
WHERE 削除フラグ = 0) b
ON a.ID = b.生徒ID;
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND 0 = b.削除フラグ;
パースの段階で SQL を直してから実行します。
直してくれない RDBMS は めっちゃ遅い!
特にハッシュジョインがない MySQL は OUT!
Copyright 2016-2018 G1Systems Inc.
右に置いたテーブルがWHERE句に入るとバグ
45
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
WHERE
b.ランク = 'A';
外部結合を打消してしまう。
どちらを意図しているのか、
ソースからは分からない。
Copyright 2016-2018 G1Systems Inc.
ところが、例えば、WordPress 4.9.4 での例
if ( $post_status_join ) {
$join .= " LEFT JOIN {$wpdb->posts} AS p2 ON
({$wpdb->posts}.post_parent = p2.ID)";
foreach ( $statuswheres as $index => $statuswhere ) {
$statuswheres[$index] =
"($statuswhere
OR ({$wpdb->posts}.post_status = 'inherit’
AND ".str_replace( $wpdb->posts, 'p2', $statuswhere ) . "))";
}
}
$where_status = implode( ' OR ', $statuswheres );
if ( ! empty( $where_status ) ) {
$where .= " AND ($where_status)";
} ※ class-wp-query.php
46
Copyright 2016-2018 G1Systems Inc.
WordPress 4.9.4 は、最後に LEFT JOIN に変換
/** Generates SQL clauses to be appended to a main query.
-- 中略 --
public function get_sql( $type, $primary_table, ……
-- 中略 --
/* If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should be
LEFT. Otherwise posts with no metadata will be excluded from results. */
-- 中略 --
if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
}
-- 中略 -- ※ class-wp-meta-query.php
47
Copyright 2016-2018 G1Systems Inc.
一応、Word Press がやっている変換意図を解説
48
生徒マスタ 特待生マスタ
2 C
生徒ID ランク
3 A
(4) NULL
(5) NULL
(6) NULL
1石野 扶樹
ID氏名
2岩崎 研二
3岩間 竜也
4上野 浩介
5岡崎 雅之
6岡島 慶二
1
削除フラグ
0
NULL
NULL
NULL
10 B
33 C
40 B
0
0
0
(1) NULLNULL
ランク名称マスタ
学業特待生AA
名称ランク
学業特待生BB
スポーツ特待生C
こちらINNER JOIN にすると、
B・Cに一致するものがない
ため、外部結合を打消してし
まう。こちらを LEFT JOIN
Copyright 2016-2018 G1Systems Inc.
ここまでのまとめ
何年か前、「すべての変数を Static で宣言したらしっく
りくる」とブログに書いて炎上した人がいました。
しかし、すべての JOIN を LEFT でというのは、まったく
珍しくありません。新人にそう教育している大手企業をい
くつも知っています。
どちらも間違っていますが、どちらが実害が大きいかは明
らかです。
LEFT 決め打ちは、「すべての変数を Static で宣言」よ
り技術レベルが低い。と心得ましょう。
49
Copyright 2016-2018 G1Systems Inc.
WHERE句は論理演算の塊
50
Copyright 2016-2018 G1Systems Inc.
論理演算を復習しよう!
AND(論理積)は掛け算、OR(論理和)は足し算
True は 1(0以外)
False は 0
として、簡単な数式に直して考えてみましょう。
もちろん、演算の順序も掛け算(AND)が先です。
T and T and F and T and T and T
= 1 × 1 × 0 × 1 × 1 × 1 = 0 (False)
F or F or F or T or F or F
= 0 + 0 + 0 + 1 + 0 + 0 = 1 (True)
51
Copyright 2016-2018 G1Systems Inc.
因数分解、展開も数式と同じ
(a × b) + (a × c)
= a × (b + c)
(a AND b) OR (a AND c) 男性で日本人、または、男性でアメリカ人
= a AND (b OR c) 男性で、日本人、または、アメリカ人
これらは、SQLに限らずどんな言語でも必要なので、息をす
るのと同じレベルでできるようになってください。
52
Copyright 2016-2018 G1Systems Inc.
SQLの動的生成を避ける!
ただし、Oracleではできません!
53
Copyright 2016-2018 G1Systems Inc.
SQLの動的生成を避ける1
54
keyの範囲を指定してレコードを検索する処理を考える
画面
開始値
終了値
1
10
WHERE句
開始値・終了値の有無に応じて
4種類のWHERE句が必要になる
開始値 終了値
なしなし なし
ID >= 開始値あり なし
ID <= 終了値なし あり
ID >= 開始値 AND ID <= 終了値あり あり
Copyright 2016-2018 G1Systems Inc.
sqlWhere = "";
if( 開始値 != null){
sqlWhere = "WHERE ";
sqlWhere += " col1 >= " + 開始値;
}
if( 終了値 != null){
if(sqlWhere == ""){
sqlWhere += " WHERE ";
} else {
sqlWhere += " AND ";
}
sqlWhere += " col1 <= " + 終了値;
}
if(sqlWhere != ""){
sql += sqlWhere;
}
55
古典的なSQL文生成ロジックを書くと……
Copyright 2016-2018 G1Systems Inc.
sqlWhere = " WHERE 1 = 1 ";
if( 開始値 != null){
sqlWhere += " AND col1 >= @開始値";
// パラメータ(param)を生成して
Paramaters.Add(param);
}
if( 終了値 != null){
sqlWhere += " AND col1 <= @終了値";
// パラメータ(param)を生成して
Paramaters.Add(param);
}
sql += sqlWhere;
56
SQL文生成ロジックを書くと……
Copyright 2016-2018 G1Systems Inc.
実は、動的生成は不要です。
SELECT *
FROM table_a
WHERE
(col1 >= @開始値 OR @開始値 IS NULL)
AND (col1 <= @終了値 OR @終了値 IS NULL);
57
※ 当然、FROM句、HAVING句でも使えます!
Copyright 2016-2018 G1Systems Inc.
パースされると消える!
58
(col1 >= @開始値 OR @開始値 IS NULL)
@開始値がNULL @開始値が1000
(col1 >= NULL OR NULL IS NULL) (col1 >= 1000 OR 1000 IS NULL)
(col1 >= NULL OR True) (col1 >= 1000 OR False)
意味がないので消える (col1 >= 1000)だけが残る
Copyright 2016-2018 G1Systems Inc.
SQLServerは、RECOMPILE指定する
SELECT *
FROM テーブル
WHERE
(col1 = @検索値1 OR @検索値1 IS NULL)
AND (col2 = @検索値2 OR @検索値2 IS NULL)
OPTION(RECOMPILE);
とすれば、毎回、パースからやり直してくれる。
59
Copyright 2016-2018 G1Systems Inc.
更には……(任意の OR条件を追加)
60
画面
開始値
終了値
1
10
または
区分 A
SELECT * FROM table_a
WHERE
((col1 >= @開始値 OR @開始値 IS NULL)
AND (col1 <= @終了値 OR @終了値 IS NULL))
OR (col2 = @区分 AND @区分 IS NOT NULL)
※ 当然、FROM句、HAVING句でも使えます!
Copyright 2016-2018 G1Systems Inc.
更には……
61
画面
検索値
比較演算子
1
<=
SELECT * FROM table_a
WHERE
(col1 = 検索値 OR @比較演算子 <> "=")
AND (col1 >= 検索値 OR @比較演算子 <> ">=")
AND (col1 <= 検索値 OR @比較演算子 <> "<=")
AND (col1 > 検索値 OR @比較演算子 <> ">")
AND (col1 < 検索値 OR @比較演算子 <> "<")
※ 当然、FROM句、HAVING句でも使えます!
▼
Copyright 2016-2018 G1Systems Inc.
LEFT JOIN を切り替えるなら
62
画面
過去特待生だった
らそれも表示
@区分 が 1 なら削除フラグが立っていても出力し、
以外ならしない。
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND (0 = b.削除フラグ OR @区分 = 1);
する
しない
Copyright 2016-2018 G1Systems Inc.
まとめ – SQLの動的生成は要らない
弊社で開発した ERP のスクラッチ案件
見積、受注、発注、売上、請求、入金、部品表、在庫引当、
入庫、出荷、棚卸、作業費、外注管理、交通費、利益管理
全て、ストアドプロシージャで作成。
SQLの動的生成はゼロ!
63
Copyright 2016-2018 G1Systems Inc.
ご相談ください
SQL・RDBについてのコンサルティング、教育、
パフォーマンスチューニングなどお困りのことが
ございましたら、お気軽にご相談ください。
s.ikushima@g1sys.co.jp
64
Copyright 2016-2018 G1Systems Inc.
(時間があれば) 実演します
65

Sql learning

  • 1.
    Copyright 2016-2018 G1SystemsInc. SQLの書き方 初級者・中級者向け 1
  • 2.
    Copyright 2016-2018 G1SystemsInc. はじめに SQLおじさんですw SQLServerだけではなく、RDB全般について 専門的に仕事を請けています。 金融から、製造業、ネットゲームまで、オー ルランドに対応しております。 その中で、今日はSQLの基礎についてお話し ます。 2
  • 3.
    Copyright 2016-2018 G1SystemsInc. DBサーバの構造を知ろう! 3
  • 4.
    Copyright 2016-2018 G1SystemsInc. RDBMSの内部データ構造 RDBMSは、レコード単位でデータを保存せず、 ページ単位で、データを保存しています。 SQLServerは 8KByteです。 4 空き ヘッダ情報 1レコード 2レコード 3レコード 4レコード 5レコード 6レコード 7レコード データは1ページ単位で読んでから、 レコード、カラムに切り分ける。 ページサイズ 8KByte
  • 5.
    Copyright 2016-2018 G1SystemsInc. 5 実データ (生き物) インデックス ア~ノ EEEEEEEEEEE ハ~ン FFFFFFFFFFF ア~ス DDDDDDDDDDD セ~ノ CCCCCCCCCCC ハ~ユ BBBBBBBBBBB ヨ~ン AAAAAAAAAAA アリ 99999999999 イヌ 88888888888 ウシ 77777777777 …… …… セミ 66666666666 …… …… はページ ID 名前 分類 体長 特徴 …… …… ID 名前 分類 体長 特徴 ID 名前 分類 体長 特徴 10001 アリ 昆虫 1~3mm 90012 ネコ 哺乳類 20~60cm …… …… SQLServer のプライマリキーは、基本クラスタードイン デックスになりリーフが実データになります。
  • 6.
    Copyright 2016-2018 G1SystemsInc. (余談)インメモリデータベースについて メモリが安くなっていますから、すべてメモリにキャッ シュされているシステムも少なくありません。そのため、 全てメモリにキャッシュされているなら、インメモリデー タベースにする必要がないと考える人も多いです。 しかし、それは間違いです。 インメモリデータベースは、HDDを意識する必要がない ため、ページではなく、レコード単位でデータにアクセス でき、インデックスの構造も違いますから、 無駄読み、無駄書きが少なくなるため高速になります。 6
  • 7.
    Copyright 2016-2018 G1SystemsInc. 実行プランを見てみよう! 7
  • 8.
    Copyright 2016-2018 G1SystemsInc. サンプルSQLのテーブルについて スクリプトを流すと、テーブルが2つとランダムなダミーデータが 作られます。 ■ 売上 売上日にインデックスがある。 SYSDATEから1年前のデータが10万件。 SYSDATEの1年前~2年前のデータが100件。 ■ 顧客 ID 1~1000 のデータが作られます。 1~100までが「削除フラグ = 1」になります。 8
  • 9.
    Copyright 2016-2018 G1SystemsInc. 変数を使って実行プランを見るときの注意! 9 変数を使うと、実行プランは変数にデフォルト値を使って作られ ます。(推定実行プランも) 変数によって最適な実行計画が欲しいときは、 OPTION(RECOMPILE)を指定し、 【実際の実行プランを含める】を ON にして実行してください。
  • 10.
    Copyright 2016-2018 G1SystemsInc. ヒットするデータが少ないと予想したとき 10 インデックスを抽出して プライマリーキー(実データ) をループしながら結果セットを 作っていきます。
  • 11.
    Copyright 2016-2018 G1SystemsInc. ヒットするデータが少ないと予想したとき RowIDLists[] rowIDs New RowIDLists[] ; // ヒットしたもの RetrunRows[] retRows New RetrunRows[] ; // 戻すレコード rowIDs = IX_売上日.getRange(#日付F#, #日付T#); //日付範囲 for(RowID rowID:rowIDs){ // 計算処理 retRows.add(売上.getRow(rowID)); } Return retRows; 11
  • 12.
    Copyright 2016-2018 G1SystemsInc. ヒットするデータが多いと予想したとき 12 実データを全部読み取って、要 らない行をフィルタします。 読み取った行数 100100行 実際の行数 100000行
  • 13.
    Copyright 2016-2018 G1SystemsInc. ヒットするデータが多いと予想したとき RetrunRows[] retRows New RetrunRows[]; // 戻すレコード for(Row row:売上){ // 「売上」テーブルを総スキャン if(row.売上日 >= #日付F# && row.売上日 <= #日付T#){ // 計算処理 retRows.add(row); } } Return retRows; 13
  • 14.
    Copyright 2016-2018 G1SystemsInc. SQLが実行されるまでRDBMSがすること SQL文のシンタックス(文法)チェック DBオブジェクト(テーブルなど)の存在チェック DBオブジェクトに対する権限チェック カラムなどの存在チェック パース処理(文法上の整理) テーブル・インデックスの統計情報から実行プラン作成 マシン語に翻訳(コンパイル) SQLは、実行前に超重い処理をしています! 14
  • 15.
    Copyright 2016-2018 G1SystemsInc. SQLが書きにくい理由 手続き型と同じ方法で考えているから! 15
  • 16.
    Copyright 2016-2018 G1SystemsInc. 言語水準は高い方から低い方へ考える 16 自然言語 基本設計 詳細設計 詳細設計 最後はマシン語になる!
  • 17.
    Copyright 2016-2018 G1SystemsInc. O/Rマッパーなどを使った開発のイメージ 17 材料を集め 組み立て ディスプレイ パーツを削りだし (ここだけSQL) どのように組み 合わせるか仕様 書が必要になる。
  • 18.
    Copyright 2016-2018 G1SystemsInc. SQLはプログラムをオプティマイザが書く! 18 自然言語 基本設計? 詳細設計 SQL 実行計画 SQLは言語水準が高く、 プログラムというより は、詳細設計に近い!
  • 19.
    Copyright 2016-2018 G1SystemsInc. SQLで処理することのイメージは 19 材料を集め 組み合わし 削り出す 別言語で ディスプレイ
  • 20.
    Copyright 2016-2018 G1SystemsInc. 手続き型プログラムはHow、 SQLはWhat 通常のプログラム (手続き型言語 ≒ オブジェクト指向言語)は、 「どう処理するか?(How)」を記述する。 SQLを書くときは、「何が欲しいか?(What)」を記述 するべきですが、 「どう処理するか?(How)」を先に 考えてしまう。 これは、「コーディングしてから設計を考える」ことにな り、非常に書きにくい(考えにくい) そのため、SQLを難しく感じてしまうのです。 20
  • 21.
    Copyright 2016-2018 G1SystemsInc. SQLの書き方 21 時間の制約があるので FROM句とWHERE句について解説します。
  • 22.
    Copyright 2016-2018 G1SystemsInc. FROM句について 22
  • 23.
    Copyright 2016-2018 G1SystemsInc. FROM句はデータの基 どこから (テーブル・ビュー・サブクエリなど) データを持ってくるかを記述する。 複数のソースがあるときは、その結合方法も 記述する。 (JOINのこと) 23
  • 24.
    Copyright 2016-2018 G1SystemsInc. INNER JOIN のイメージ 24 Table_A a Table_B b WHRER句の抽出条件 a.B_ID = b.ID 内側になるレコード だけが対象となる。
  • 25.
    Copyright 2016-2018 G1SystemsInc. LEFT OUTER JOIN のイメージ 25 Table_A a Table_B b WHRER句の抽出条件 a.B_ID = b.ID 左側すべてと、結合 条件に一致する右側 のレコードが対象と なる。
  • 26.
    Copyright 2016-2018 G1SystemsInc. RIGHT OUTER JOIN のイメージ 26 Table_A a Table_B b WHRER句の抽出条件 a.B_ID = b.ID 右側すべてと、結合 条件に一致する右側 のレコードが対象と なる。
  • 27.
    Copyright 2016-2018 G1SystemsInc. FULL OUTER JOIN のイメージ 27 Table_A a Table_B b WHRER句の抽出条件 a.B_ID = b.ID 両方のテーブルを対 象とし、結合条件で 結合する。
  • 28.
    Copyright 2016-2018 G1SystemsInc. CROSS JOIN のイメージ 28 Table_A a Table_B b a.B_ID = b.ID 全ての組合せが 結果として返る。
  • 29.
    Copyright 2016-2018 G1SystemsInc. ※ CROSS JOIN 左右のテーブルの全組み合わせになる。 SELECT * FROM Table_A CROSS JOIN Table_B; または、 SELECT * FROM Table_A, Table_B; Table_Aに100件、Table_Bに100件あれば、10000件出 力される。 29
  • 30.
    Copyright 2016-2018 G1SystemsInc. CROSS JOIN はダミーを作るときなど 0~9まで入ったnumテーブルを用意して…… SELECT (a.id * 100 + b.id * 10 + c.id) AS id 'ダミー文字列' || (a.id * 100 + b.id * 10 + c.id) AS col1 FROM num a, num b, num c; 30
  • 31.
    Copyright 2016-2018 G1SystemsInc. テスト(パラメータテーブル)でも使える xxx区分(A~D)、xxx種別(01~09)のすべての 組み合わせ(テストパターン)を作る。 -- CREATE TABLE wrk_テストパターン AS SELECT a.区分, a.区分名, b.種別, b.種別名 INTO wrk_テストパターン FROM xxx区分 a, xxx種別 b; 31
  • 32.
    Copyright 2016-2018 G1SystemsInc. INNER JOIN と OUTER JOIN 32 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 生徒マスタ 特待生マスタ INNER JOIN と OUTER JOIN で、結果はどう変わる? 生徒マスタ a INNER JOIN 特待生マスタ b ON a.ID = b.生徒ID 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID 生徒マスタ a RIGHT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID 1 削除フラグ 0 0 0 0 0
  • 33.
    Copyright 2016-2018 G1SystemsInc.33 生徒マスタ 特待生マスタ 生徒マスタ a INNER JOIN 特待生マスタ b ON a.ID = b.生徒ID INNER JOIN INNER JOIN は、マッチした部分だけが残る。 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 0 0 0 0
  • 34.
    Copyright 2016-2018 G1SystemsInc.34 生徒マスタ 特待生マスタ 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID LEFT OUTER JOIN 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 0 0 0 0 LEFT OUTER JOIN は、左側テーブルがすべて残る。
  • 35.
    Copyright 2016-2018 G1SystemsInc.35 生徒マスタ 特待生マスタ 生徒マスタ a RIGHT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID RIGHT OUTER JOIN 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 0 0 0 0 RIGHT OUTER JOIN は、右側テーブルがすべて残る。
  • 36.
    Copyright 2016-2018 G1SystemsInc.36 例題 すべての生徒のリストに、特待生マスタの削除フラグが 0の 特待生ランクを出力してください。 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 生徒マスタ 特待生マスタ 1 削除フラグ 0 0 0 0 0
  • 37.
    Copyright 2016-2018 G1SystemsInc. 解答 37 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ここに結合条件を追加 これを出力 したくない 生徒マスタ 特待生マスタ 2 C 生徒ID ランク 3 A 10 B 33 C 40 B 48 C 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 0 0 0 0
  • 38.
    Copyright 2016-2018 G1SystemsInc. これはNG! 38 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID WHERE b.削除フラグ = 0;
  • 39.
    Copyright 2016-2018 G1SystemsInc. なぜNGか? 39 WHERE 削除FLAG = 0 では、 NULL = 0 となる、 のレコードが出力できない。 1行だけになってしまう。 ← NULL = 0 は False ← NULL = 0 は False ← NULL = 0 は False 生徒マスタ 特待生マスタ 2 C 生徒ID ランク 3 A (4) NULL (5) NULL (6) NULL 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 NULL NULL NULL ← NULL = 0 は False 10 B 33 C 40 B 0 0 0 (1) NULL NULL ← 1= 0 は False
  • 40.
    Copyright 2016-2018 G1SystemsInc. サブクエリーは意味は正しいけれど…… 40 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN (SELECT * FROM 特待生マスタ WHERE 削除フラグ = 0) b ON a.ID = b.生徒ID;
  • 41.
    Copyright 2016-2018 G1SystemsInc. 正しくはこうする! 41 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID AND 0 = b.削除フラグ;
  • 42.
    Copyright 2016-2018 G1SystemsInc. イメージにすると…… 42 生徒マスタ a 特待生マスタ b a.ID = b.生徒ID AND b.削除フラグ = 0 a.ID = b.生徒ID AND b.削除フラグ <> 0 a.ID <> b.生徒ID AND b.削除フラグ = 0 a.ID <> b.生徒ID AND b.削除フラグ <> 0 b.削除フラグ = 0 で 全体を削りたい訳ではない。
  • 43.
    Copyright 2016-2018 G1SystemsInc. SQLServer や Oracle などはサブクエリを直す SQLServer や Oracle などの商用DBは、 44 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN (SELECT * FROM 特待生マスタ WHERE 削除フラグ = 0) b ON a.ID = b.生徒ID; SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID AND 0 = b.削除フラグ; パースの段階で SQL を直してから実行します。 直してくれない RDBMS は めっちゃ遅い! 特にハッシュジョインがない MySQL は OUT!
  • 44.
    Copyright 2016-2018 G1SystemsInc. 右に置いたテーブルがWHERE句に入るとバグ 45 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID WHERE b.ランク = 'A'; 外部結合を打消してしまう。 どちらを意図しているのか、 ソースからは分からない。
  • 45.
    Copyright 2016-2018 G1SystemsInc. ところが、例えば、WordPress 4.9.4 での例 if ( $post_status_join ) { $join .= " LEFT JOIN {$wpdb->posts} AS p2 ON ({$wpdb->posts}.post_parent = p2.ID)"; foreach ( $statuswheres as $index => $statuswhere ) { $statuswheres[$index] = "($statuswhere OR ({$wpdb->posts}.post_status = 'inherit’ AND ".str_replace( $wpdb->posts, 'p2', $statuswhere ) . "))"; } } $where_status = implode( ' OR ', $statuswheres ); if ( ! empty( $where_status ) ) { $where .= " AND ($where_status)"; } ※ class-wp-query.php 46
  • 46.
    Copyright 2016-2018 G1SystemsInc. WordPress 4.9.4 は、最後に LEFT JOIN に変換 /** Generates SQL clauses to be appended to a main query. -- 中略 -- public function get_sql( $type, $primary_table, …… -- 中略 -- /* If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should be LEFT. Otherwise posts with no metadata will be excluded from results. */ -- 中略 -- if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); } -- 中略 -- ※ class-wp-meta-query.php 47
  • 47.
    Copyright 2016-2018 G1SystemsInc. 一応、Word Press がやっている変換意図を解説 48 生徒マスタ 特待生マスタ 2 C 生徒ID ランク 3 A (4) NULL (5) NULL (6) NULL 1石野 扶樹 ID氏名 2岩崎 研二 3岩間 竜也 4上野 浩介 5岡崎 雅之 6岡島 慶二 1 削除フラグ 0 NULL NULL NULL 10 B 33 C 40 B 0 0 0 (1) NULLNULL ランク名称マスタ 学業特待生AA 名称ランク 学業特待生BB スポーツ特待生C こちらINNER JOIN にすると、 B・Cに一致するものがない ため、外部結合を打消してし まう。こちらを LEFT JOIN
  • 48.
    Copyright 2016-2018 G1SystemsInc. ここまでのまとめ 何年か前、「すべての変数を Static で宣言したらしっく りくる」とブログに書いて炎上した人がいました。 しかし、すべての JOIN を LEFT でというのは、まったく 珍しくありません。新人にそう教育している大手企業をい くつも知っています。 どちらも間違っていますが、どちらが実害が大きいかは明 らかです。 LEFT 決め打ちは、「すべての変数を Static で宣言」よ り技術レベルが低い。と心得ましょう。 49
  • 49.
    Copyright 2016-2018 G1SystemsInc. WHERE句は論理演算の塊 50
  • 50.
    Copyright 2016-2018 G1SystemsInc. 論理演算を復習しよう! AND(論理積)は掛け算、OR(論理和)は足し算 True は 1(0以外) False は 0 として、簡単な数式に直して考えてみましょう。 もちろん、演算の順序も掛け算(AND)が先です。 T and T and F and T and T and T = 1 × 1 × 0 × 1 × 1 × 1 = 0 (False) F or F or F or T or F or F = 0 + 0 + 0 + 1 + 0 + 0 = 1 (True) 51
  • 51.
    Copyright 2016-2018 G1SystemsInc. 因数分解、展開も数式と同じ (a × b) + (a × c) = a × (b + c) (a AND b) OR (a AND c) 男性で日本人、または、男性でアメリカ人 = a AND (b OR c) 男性で、日本人、または、アメリカ人 これらは、SQLに限らずどんな言語でも必要なので、息をす るのと同じレベルでできるようになってください。 52
  • 52.
    Copyright 2016-2018 G1SystemsInc. SQLの動的生成を避ける! ただし、Oracleではできません! 53
  • 53.
    Copyright 2016-2018 G1SystemsInc. SQLの動的生成を避ける1 54 keyの範囲を指定してレコードを検索する処理を考える 画面 開始値 終了値 1 10 WHERE句 開始値・終了値の有無に応じて 4種類のWHERE句が必要になる 開始値 終了値 なしなし なし ID >= 開始値あり なし ID <= 終了値なし あり ID >= 開始値 AND ID <= 終了値あり あり
  • 54.
    Copyright 2016-2018 G1SystemsInc. sqlWhere = ""; if( 開始値 != null){ sqlWhere = "WHERE "; sqlWhere += " col1 >= " + 開始値; } if( 終了値 != null){ if(sqlWhere == ""){ sqlWhere += " WHERE "; } else { sqlWhere += " AND "; } sqlWhere += " col1 <= " + 終了値; } if(sqlWhere != ""){ sql += sqlWhere; } 55 古典的なSQL文生成ロジックを書くと……
  • 55.
    Copyright 2016-2018 G1SystemsInc. sqlWhere = " WHERE 1 = 1 "; if( 開始値 != null){ sqlWhere += " AND col1 >= @開始値"; // パラメータ(param)を生成して Paramaters.Add(param); } if( 終了値 != null){ sqlWhere += " AND col1 <= @終了値"; // パラメータ(param)を生成して Paramaters.Add(param); } sql += sqlWhere; 56 SQL文生成ロジックを書くと……
  • 56.
    Copyright 2016-2018 G1SystemsInc. 実は、動的生成は不要です。 SELECT * FROM table_a WHERE (col1 >= @開始値 OR @開始値 IS NULL) AND (col1 <= @終了値 OR @終了値 IS NULL); 57 ※ 当然、FROM句、HAVING句でも使えます!
  • 57.
    Copyright 2016-2018 G1SystemsInc. パースされると消える! 58 (col1 >= @開始値 OR @開始値 IS NULL) @開始値がNULL @開始値が1000 (col1 >= NULL OR NULL IS NULL) (col1 >= 1000 OR 1000 IS NULL) (col1 >= NULL OR True) (col1 >= 1000 OR False) 意味がないので消える (col1 >= 1000)だけが残る
  • 58.
    Copyright 2016-2018 G1SystemsInc. SQLServerは、RECOMPILE指定する SELECT * FROM テーブル WHERE (col1 = @検索値1 OR @検索値1 IS NULL) AND (col2 = @検索値2 OR @検索値2 IS NULL) OPTION(RECOMPILE); とすれば、毎回、パースからやり直してくれる。 59
  • 59.
    Copyright 2016-2018 G1SystemsInc. 更には……(任意の OR条件を追加) 60 画面 開始値 終了値 1 10 または 区分 A SELECT * FROM table_a WHERE ((col1 >= @開始値 OR @開始値 IS NULL) AND (col1 <= @終了値 OR @終了値 IS NULL)) OR (col2 = @区分 AND @区分 IS NOT NULL) ※ 当然、FROM句、HAVING句でも使えます!
  • 60.
    Copyright 2016-2018 G1SystemsInc. 更には…… 61 画面 検索値 比較演算子 1 <= SELECT * FROM table_a WHERE (col1 = 検索値 OR @比較演算子 <> "=") AND (col1 >= 検索値 OR @比較演算子 <> ">=") AND (col1 <= 検索値 OR @比較演算子 <> "<=") AND (col1 > 検索値 OR @比較演算子 <> ">") AND (col1 < 検索値 OR @比較演算子 <> "<") ※ 当然、FROM句、HAVING句でも使えます! ▼
  • 61.
    Copyright 2016-2018 G1SystemsInc. LEFT JOIN を切り替えるなら 62 画面 過去特待生だった らそれも表示 @区分 が 1 なら削除フラグが立っていても出力し、 以外ならしない。 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID AND (0 = b.削除フラグ OR @区分 = 1); する しない
  • 62.
    Copyright 2016-2018 G1SystemsInc. まとめ – SQLの動的生成は要らない 弊社で開発した ERP のスクラッチ案件 見積、受注、発注、売上、請求、入金、部品表、在庫引当、 入庫、出荷、棚卸、作業費、外注管理、交通費、利益管理 全て、ストアドプロシージャで作成。 SQLの動的生成はゼロ! 63
  • 63.
    Copyright 2016-2018 G1SystemsInc. ご相談ください SQL・RDBについてのコンサルティング、教育、 パフォーマンスチューニングなどお困りのことが ございましたら、お気軽にご相談ください。 s.ikushima@g1sys.co.jp 64
  • 64.
    Copyright 2016-2018 G1SystemsInc. (時間があれば) 実演します 65