8. Copyright 2016-2018 G1Systems Inc.
サブクエリーは意味は正しいけれど……
8
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN
(SELECT * FROM 特待生マスタ
WHERE 削除フラグ = 0) b
ON a.ID = b.生徒ID;
9. Copyright 2016-2018 G1Systems Inc.
正しくはこうする!
9
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND 0 = b.削除フラグ;
10. Copyright 2016-2018 G1Systems Inc.
イメージにすると……
10
生徒マスタ 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 で
全体を削りたい訳ではない。
11. Copyright 2016-2018 G1Systems Inc.
SQLServer や Oracle などはサブクエリを直す
SQLServer や Oracle などの商用DBは、
11
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!
12. Copyright 2016-2018 G1Systems Inc.
右に置いたテーブルがWHERE句に入るとバグ
12
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
WHERE
b.ランク = 'A';
外部結合を打消してしまう。
どちらを意図しているのか、
ソースからは分からない。
13. 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
13
14. 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
14
15. Copyright 2016-2018 G1Systems Inc.
一応、Word Press がやっている変換意図を解説
15
生徒マスタ 特待生マスタ
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
16. Copyright 2016-2018 G1Systems Inc.
ここまでのまとめ
何年か前、「すべての変数を Static で宣言したらしっく
りくる」とブログに書いて炎上した人がいました。
しかし、すべての JOIN を LEFT でというのは、まったく
珍しくありません。新人にそう教育している大手企業をい
くつも知っています。
どちらも間違っていますが、どちらが実害が大きいかは明
らかです。
LEFT 決め打ちは、「すべての変数を Static で宣言」よ
り技術レベルが低い。と心得ましょう。
16
18. 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)
18
19. 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に限らずどんな言語でも必要なので、息をす
るのと同じレベルでできるようになってください。
19
24. Copyright 2016-2018 G1Systems Inc.
実は、動的生成は不要です。
SELECT *
FROM table_a
WHERE
(col1 >= @開始値 OR @開始値 IS NULL)
AND (col1 <= @終了値 OR @終了値 IS NULL);
24
※ 当然、FROM句、HAVING句でも使えます!
25. Copyright 2016-2018 G1Systems Inc.
パースされると消える!
25
(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)だけが残る
26. 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);
とすれば、毎回、パースからやり直してくれる。
26
27. Copyright 2016-2018 G1Systems Inc.
更には……(任意の OR条件を追加)
27
画面
開始値
終了値
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句でも使えます!
28. Copyright 2016-2018 G1Systems Inc.
更には……
28
画面
検索値
比較演算子
1
<=
SELECT * FROM table_a
WHERE
(col1 = 検索値 OR @比較演算子 <> "=")
AND (col1 >= 検索値 OR @比較演算子 <> ">=")
AND (col1 <= 検索値 OR @比較演算子 <> "<=")
AND (col1 > 検索値 OR @比較演算子 <> ">")
AND (col1 < 検索値 OR @比較演算子 <> "<")
※ 当然、FROM句、HAVING句でも使えます!
▼
29. Copyright 2016-2018 G1Systems Inc.
LEFT JOIN を切り替えるなら
29
画面
過去特待生だった
らそれも表示
@区分 が 1 なら削除フラグが立っていても出力し、
以外ならしない。
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND (0 = b.削除フラグ OR @区分 = 1);
する
しない
49. Copyright 2016-2018 G1Systems Inc.
マクロでソースを自動生成する(ダミービュー)
CREATE VIEW xpr_find_customer_VIEW AS
SELECT
CAST(NULL AS SIGNED) AS ID
, CAST(NULL AS CHAR) AS NAME ……
UNION ALL SELECT 1000, '山田 太郎'……
UNION ALL SELECT 1001, '佐藤 二郎'……
UNION ALL SELECT 1002, '鈴木 一郎'……
49
50. Copyright 2016-2018 G1Systems Inc.
マクロでソースを自動生成する(ストアドプロシージャ)
CREATE PROCEDURE pr_find_customer(IN _Name TEXT -- 名前で検索
, IN _Address TEXT -- 住所で検索)
BEGIN
-- 本番時は以下のSQLを修正し、このコメントを削除する。
SELECT
ID AS ID
, NAME AS NAME ……
FROM xpr_find_customer_VIEW
WHERE
ID IS NOT NULL
-- AND (NAME LIKE _Name OR _Name IS NULL)
-- AND (ADDRESS LIKE _Address OR _Address IS NULL);
End
$$
DELIMITER ;
50