Copyright 2016-2018 G1Systems Inc.
ストアドプロシージャを使っ
た開発について
中級者向け
1
Copyright 2016-2018 G1Systems Inc.
はじめに
SQLおじさんですw
SQLServerだけではなく、RDB全般について
専門的に仕事を請けています。
金融から、製造業、ネットゲームまで、オー
ルランドに対応しております。
その中で、今日はストアドプロシージャを
使った開発についてお話します。
2
Copyright 2016-2018 G1Systems Inc.
前回の復習
3
Copyright 2016-2018 G1Systems Inc.4
例題
すべての生徒のリストに、特待生マスタの削除フラグが 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.
解答
5
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!
6
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
WHERE
b.削除フラグ = 0;
Copyright 2016-2018 G1Systems Inc.
なぜNGか?
7
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.
サブクエリーは意味は正しいけれど……
8
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN
(SELECT * FROM 特待生マスタ
WHERE 削除フラグ = 0) b
ON a.ID = b.生徒ID;
Copyright 2016-2018 G1Systems Inc.
正しくはこうする!
9
SELECT
a.氏名, b.ランク
FROM 生徒マスタ a
LEFT OUTER JOIN 特待生マスタ b
ON a.ID = b.生徒ID
AND 0 = b.削除フラグ;
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 で
全体を削りたい訳ではない。
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!
Copyright 2016-2018 G1Systems Inc.
右に置いたテーブルがWHERE句に入るとバグ
12
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
13
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
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
Copyright 2016-2018 G1Systems Inc.
ここまでのまとめ
何年か前、「すべての変数を Static で宣言したらしっく
りくる」とブログに書いて炎上した人がいました。
しかし、すべての JOIN を LEFT でというのは、まったく
珍しくありません。新人にそう教育している大手企業をい
くつも知っています。
どちらも間違っていますが、どちらが実害が大きいかは明
らかです。
LEFT 決め打ちは、「すべての変数を Static で宣言」よ
り技術レベルが低い。と心得ましょう。
16
Copyright 2016-2018 G1Systems Inc.
WHERE句は論理演算の塊
17
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
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
Copyright 2016-2018 G1Systems Inc.
SQLの動的生成を避ける!
ただし、Oracleではできません!
20
Copyright 2016-2018 G1Systems Inc.
SQLの動的生成を避ける1
21
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;
}
22
古典的な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;
23
SQL文生成ロジックを書くと……
Copyright 2016-2018 G1Systems Inc.
実は、動的生成は不要です。
SELECT *
FROM table_a
WHERE
(col1 >= @開始値 OR @開始値 IS NULL)
AND (col1 <= @終了値 OR @終了値 IS NULL);
24
※ 当然、FROM句、HAVING句でも使えます!
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)だけが残る
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
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句でも使えます!
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句でも使えます!
▼
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);
する
しない
Copyright 2016-2018 G1Systems Inc.
まとめ – SQLの動的生成は要らない
弊社で開発した ERP のスクラッチ案件
見積、受注、発注、売上、請求、入金、部品表、在庫引当、
入庫、出荷、棚卸、作業費、外注管理、交通費、利益管理
全て、ストアドプロシージャで作成。
SQLの動的生成はゼロ!
30
Copyright 2016-2018 G1Systems Inc.
実演します
SQLのイメージの仕方
31
Copyright 2016-2018 G1Systems Inc.
MVCは間違っている
32
Copyright 2016-2018 G1Systems Inc.
ヒットするデータが少ないと予想したとき
33
インデックスを抽出して
プライマリーキー(実データ)
をループしながら結果セットを
作っていきます。
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;
34
Copyright 2016-2018 G1Systems Inc.
MVCは1980年代のスタンドアローン向け概念
35
スタンドアローンPC
View
Control
Model
Copyright 2016-2018 G1Systems Inc.
DBサーバ
Webに適用するとインピーダンスミスマッチ
36
WEBサーバ APサーバ
View
Control
Model
SQL
SQLは界面が
合わない異物
Copyright 2016-2018 G1Systems Inc.
O/Rマッパで解消
DBサーバWEBサーバ APサーバ
View
Control
Model
SQL
O/Rマッパで
異物を覆う
Copyright 2016-2018 G1Systems Inc.
本来はDBを疎結合にすべき(異物は隠蔽すべき)
38
DBサーバWEB
サーバ
APサーバ
View
Control
Model
RDBMS
DBサーバ
RDBMS
Inter
face
Inter
face
Copyright 2016-2018 G1Systems Inc.
MVCは1980年代に生まれた概念
MVCは1980年代に生まれた概念で、Webも、DBも意識
して作られていません。
「疎結合にすべき」という概念なのに、メモリー空間も、
処理構造も全く違う RDBMS を「オブジェクト指向言語
に埋め込もう」としている時点で、論理矛盾が起きていま
す。
SQL は、本来は API の位置づけですが、APIというには
柔軟過ぎます。
SQLをストアドプロシージャに隠蔽し、オブジェクト指向
言語と疎結合にするべきです。
39
Copyright 2016-2018 G1Systems Inc.
RDBMS も APIファーストで
テーブル設計はコーディングのあとで
40
Copyright 2016-2018 G1Systems Inc.
デスマーチの原因は「要件定義の甘さ」と言われるが……
ウォータフォール開発で
は、要件定義が甘いと、
手戻りが起きる。
特に、テーブル設計の変
更は、波及範囲が大きい。
これがデススパイラルの
元になる。
41
Copyright 2016-2018 G1Systems Inc.
デスマーチになる本当の原因
テーブル設計に変更が入ること!
例えば、某金融機関の開発では……
DDL変更依頼-毎週水曜日15時締切
DBAチームで精査検討会議 - 毎週木曜日
開発サーバへDDLの適用-毎週金曜日
開発メンバで設計書・コードへ反映
さらなる矛盾が顕在化!
デスマーチが鳴り響く!!
42
Copyright 2016-2018 G1Systems Inc.
デスマーチの原因は、要件定義とテーブル設計
43
エクセル方眼紙で
要件定義
顧客が最終形を
イメージできない
手戻りが発生する!
先にプログラムを
見せられれば良い!
あいまいな要件定義
を元にテーブル設計
手戻りの度に
修正が発生
多数のプログラムに
波及しデスマーチへ
コーディングの後に
テーブル設計を!
要件定義
テーブル設計
解決策
Copyright 2016-2018 G1Systems Inc.
インタフェースから先にアジャイル開発を進めよう
AP
RDBMS
インタフェース
仕様書
(Excel)
こんなふうに
使いたいんだけど
スタブ
自動生成
AP
アジャイル開発
(要件を固める/
UIを作るフェーズ)
RDBMS
AP table table
テーブル構造の
確定は要件が
固まってからでいい
アジャイル開発
(SQL開発フェーズ)
44
Copyright 2016-2018 G1Systems Inc.
RDBMSのAPIに必要な要素は4つ
1.プロシージャ名(ファンクション名)
2.引数と型
3.戻り値と型
4.ダミーデータ
45
Copyright 2016-2018 G1Systems Inc.
エクセルで設定する(プロシージャ名と引数)
46
Copyright 2016-2018 G1Systems Inc.
エクセルで設定する(戻り値)
47
Copyright 2016-2018 G1Systems Inc.
エクセルで設定する(ダミーデータ)
48
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
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
Copyright 2016-2018 G1Systems Inc.
テーブルなしでUIの開発ができる
51
アプリケーションに入るSQLはこれだけになる。
アプリケーションから SQL を完全に排除できる!
Copyright 2016-2018 G1Systems Inc.
ストアドプロシージャは開発効率が悪いのでは?
年商50億円の企業の基幹システム
約120機能を、この手法で開発。
※ 見積、受発注、請求、入金、入出庫、製造、棚卸
スクラッチ開発で3ヵ月10人月!
作ったストアドプロシージャは440本
内フェッチしたもの(手続型処理)は4本だけ
52
Copyright 2016-2018 G1Systems Inc.
実演します!
53
Copyright 2016-2018 G1Systems Inc.
ご相談ください
SQL・RDBについてのコンサルティング、教育、
パフォーマンスチューニングなどお困りのことが
ございましたら、お気軽にご相談ください。
s.ikushima@g1sys.co.jp
54

Sql learning2

  • 1.
    Copyright 2016-2018 G1SystemsInc. ストアドプロシージャを使っ た開発について 中級者向け 1
  • 2.
    Copyright 2016-2018 G1SystemsInc. はじめに SQLおじさんですw SQLServerだけではなく、RDB全般について 専門的に仕事を請けています。 金融から、製造業、ネットゲームまで、オー ルランドに対応しております。 その中で、今日はストアドプロシージャを 使った開発についてお話します。 2
  • 3.
    Copyright 2016-2018 G1SystemsInc. 前回の復習 3
  • 4.
    Copyright 2016-2018 G1SystemsInc.4 例題 すべての生徒のリストに、特待生マスタの削除フラグが 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
  • 5.
    Copyright 2016-2018 G1SystemsInc. 解答 5 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
  • 6.
    Copyright 2016-2018 G1SystemsInc. これはNG! 6 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID WHERE b.削除フラグ = 0;
  • 7.
    Copyright 2016-2018 G1SystemsInc. なぜNGか? 7 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
  • 8.
    Copyright 2016-2018 G1SystemsInc. サブクエリーは意味は正しいけれど…… 8 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN (SELECT * FROM 特待生マスタ WHERE 削除フラグ = 0) b ON a.ID = b.生徒ID;
  • 9.
    Copyright 2016-2018 G1SystemsInc. 正しくはこうする! 9 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID AND 0 = b.削除フラグ;
  • 10.
    Copyright 2016-2018 G1SystemsInc. イメージにすると…… 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 G1SystemsInc. 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 G1SystemsInc. 右に置いたテーブルがWHERE句に入るとバグ 12 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID WHERE b.ランク = 'A'; 外部結合を打消してしまう。 どちらを意図しているのか、 ソースからは分からない。
  • 13.
    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 13
  • 14.
    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 14
  • 15.
    Copyright 2016-2018 G1SystemsInc. 一応、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 G1SystemsInc. ここまでのまとめ 何年か前、「すべての変数を Static で宣言したらしっく りくる」とブログに書いて炎上した人がいました。 しかし、すべての JOIN を LEFT でというのは、まったく 珍しくありません。新人にそう教育している大手企業をい くつも知っています。 どちらも間違っていますが、どちらが実害が大きいかは明 らかです。 LEFT 決め打ちは、「すべての変数を Static で宣言」よ り技術レベルが低い。と心得ましょう。 16
  • 17.
    Copyright 2016-2018 G1SystemsInc. WHERE句は論理演算の塊 17
  • 18.
    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) 18
  • 19.
    Copyright 2016-2018 G1SystemsInc. 因数分解、展開も数式と同じ (a × b) + (a × c) = a × (b + c) (a AND b) OR (a AND c) 男性で日本人、または、男性でアメリカ人 = a AND (b OR c) 男性で、日本人、または、アメリカ人 これらは、SQLに限らずどんな言語でも必要なので、息をす るのと同じレベルでできるようになってください。 19
  • 20.
    Copyright 2016-2018 G1SystemsInc. SQLの動的生成を避ける! ただし、Oracleではできません! 20
  • 21.
    Copyright 2016-2018 G1SystemsInc. SQLの動的生成を避ける1 21 keyの範囲を指定してレコードを検索する処理を考える 画面 開始値 終了値 1 10 WHERE句 開始値・終了値の有無に応じて 4種類のWHERE句が必要になる 開始値 終了値 なしなし なし ID >= 開始値あり なし ID <= 終了値なし あり ID >= 開始値 AND ID <= 終了値あり あり
  • 22.
    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; } 22 古典的なSQL文生成ロジックを書くと……
  • 23.
    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; 23 SQL文生成ロジックを書くと……
  • 24.
    Copyright 2016-2018 G1SystemsInc. 実は、動的生成は不要です。 SELECT * FROM table_a WHERE (col1 >= @開始値 OR @開始値 IS NULL) AND (col1 <= @終了値 OR @終了値 IS NULL); 24 ※ 当然、FROM句、HAVING句でも使えます!
  • 25.
    Copyright 2016-2018 G1SystemsInc. パースされると消える! 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 G1SystemsInc. 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 G1SystemsInc. 更には……(任意の 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 G1SystemsInc. 更には…… 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 G1SystemsInc. LEFT JOIN を切り替えるなら 29 画面 過去特待生だった らそれも表示 @区分 が 1 なら削除フラグが立っていても出力し、 以外ならしない。 SELECT a.氏名, b.ランク FROM 生徒マスタ a LEFT OUTER JOIN 特待生マスタ b ON a.ID = b.生徒ID AND (0 = b.削除フラグ OR @区分 = 1); する しない
  • 30.
    Copyright 2016-2018 G1SystemsInc. まとめ – SQLの動的生成は要らない 弊社で開発した ERP のスクラッチ案件 見積、受注、発注、売上、請求、入金、部品表、在庫引当、 入庫、出荷、棚卸、作業費、外注管理、交通費、利益管理 全て、ストアドプロシージャで作成。 SQLの動的生成はゼロ! 30
  • 31.
    Copyright 2016-2018 G1SystemsInc. 実演します SQLのイメージの仕方 31
  • 32.
    Copyright 2016-2018 G1SystemsInc. MVCは間違っている 32
  • 33.
    Copyright 2016-2018 G1SystemsInc. ヒットするデータが少ないと予想したとき 33 インデックスを抽出して プライマリーキー(実データ) をループしながら結果セットを 作っていきます。
  • 34.
    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; 34
  • 35.
    Copyright 2016-2018 G1SystemsInc. MVCは1980年代のスタンドアローン向け概念 35 スタンドアローンPC View Control Model
  • 36.
    Copyright 2016-2018 G1SystemsInc. DBサーバ Webに適用するとインピーダンスミスマッチ 36 WEBサーバ APサーバ View Control Model SQL SQLは界面が 合わない異物
  • 37.
    Copyright 2016-2018 G1SystemsInc. O/Rマッパで解消 DBサーバWEBサーバ APサーバ View Control Model SQL O/Rマッパで 異物を覆う
  • 38.
    Copyright 2016-2018 G1SystemsInc. 本来はDBを疎結合にすべき(異物は隠蔽すべき) 38 DBサーバWEB サーバ APサーバ View Control Model RDBMS DBサーバ RDBMS Inter face Inter face
  • 39.
    Copyright 2016-2018 G1SystemsInc. MVCは1980年代に生まれた概念 MVCは1980年代に生まれた概念で、Webも、DBも意識 して作られていません。 「疎結合にすべき」という概念なのに、メモリー空間も、 処理構造も全く違う RDBMS を「オブジェクト指向言語 に埋め込もう」としている時点で、論理矛盾が起きていま す。 SQL は、本来は API の位置づけですが、APIというには 柔軟過ぎます。 SQLをストアドプロシージャに隠蔽し、オブジェクト指向 言語と疎結合にするべきです。 39
  • 40.
    Copyright 2016-2018 G1SystemsInc. RDBMS も APIファーストで テーブル設計はコーディングのあとで 40
  • 41.
    Copyright 2016-2018 G1SystemsInc. デスマーチの原因は「要件定義の甘さ」と言われるが…… ウォータフォール開発で は、要件定義が甘いと、 手戻りが起きる。 特に、テーブル設計の変 更は、波及範囲が大きい。 これがデススパイラルの 元になる。 41
  • 42.
    Copyright 2016-2018 G1SystemsInc. デスマーチになる本当の原因 テーブル設計に変更が入ること! 例えば、某金融機関の開発では…… DDL変更依頼-毎週水曜日15時締切 DBAチームで精査検討会議 - 毎週木曜日 開発サーバへDDLの適用-毎週金曜日 開発メンバで設計書・コードへ反映 さらなる矛盾が顕在化! デスマーチが鳴り響く!! 42
  • 43.
    Copyright 2016-2018 G1SystemsInc. デスマーチの原因は、要件定義とテーブル設計 43 エクセル方眼紙で 要件定義 顧客が最終形を イメージできない 手戻りが発生する! 先にプログラムを 見せられれば良い! あいまいな要件定義 を元にテーブル設計 手戻りの度に 修正が発生 多数のプログラムに 波及しデスマーチへ コーディングの後に テーブル設計を! 要件定義 テーブル設計 解決策
  • 44.
    Copyright 2016-2018 G1SystemsInc. インタフェースから先にアジャイル開発を進めよう AP RDBMS インタフェース 仕様書 (Excel) こんなふうに 使いたいんだけど スタブ 自動生成 AP アジャイル開発 (要件を固める/ UIを作るフェーズ) RDBMS AP table table テーブル構造の 確定は要件が 固まってからでいい アジャイル開発 (SQL開発フェーズ) 44
  • 45.
    Copyright 2016-2018 G1SystemsInc. RDBMSのAPIに必要な要素は4つ 1.プロシージャ名(ファンクション名) 2.引数と型 3.戻り値と型 4.ダミーデータ 45
  • 46.
    Copyright 2016-2018 G1SystemsInc. エクセルで設定する(プロシージャ名と引数) 46
  • 47.
    Copyright 2016-2018 G1SystemsInc. エクセルで設定する(戻り値) 47
  • 48.
    Copyright 2016-2018 G1SystemsInc. エクセルで設定する(ダミーデータ) 48
  • 49.
    Copyright 2016-2018 G1SystemsInc. マクロでソースを自動生成する(ダミービュー) 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 G1SystemsInc. マクロでソースを自動生成する(ストアドプロシージャ) 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
  • 51.
    Copyright 2016-2018 G1SystemsInc. テーブルなしでUIの開発ができる 51 アプリケーションに入るSQLはこれだけになる。 アプリケーションから SQL を完全に排除できる!
  • 52.
    Copyright 2016-2018 G1SystemsInc. ストアドプロシージャは開発効率が悪いのでは? 年商50億円の企業の基幹システム 約120機能を、この手法で開発。 ※ 見積、受発注、請求、入金、入出庫、製造、棚卸 スクラッチ開発で3ヵ月10人月! 作ったストアドプロシージャは440本 内フェッチしたもの(手続型処理)は4本だけ 52
  • 53.
    Copyright 2016-2018 G1SystemsInc. 実演します! 53
  • 54.
    Copyright 2016-2018 G1SystemsInc. ご相談ください SQL・RDBについてのコンサルティング、教育、 パフォーマンスチューニングなどお困りのことが ございましたら、お気軽にご相談ください。 s.ikushima@g1sys.co.jp 54