MySQLの覚書

重複を除いたデータ件数 COUNT( DISTINCT XXX )

サンプルデータベース sales
id animal_id shop_id
1 1 1
2 1 2
3 1 2
4 2 2
5 3 1
6 4 3
7 4 1
8 5 2
9 6 2
10 6 3



重複を除いたanimal_idの件数を数える。
	SELECT count(DISTINCT animal_id) FROM sales WHERE 1
	
結果 → 6


GROUP BY とCOUNTの組み合わせでも重複を除いた件数を取得できるが、こちらがスマートである。

ページネーションとLIMIT

一覧画面などでページネーションを使う場面はよくある。
ページネーションで関係してくる情報はレコードの取得位置と取得数である。
取得位置と取得数が分かれば、SQLのLIMITで必要なデータを取ってこれる。
SQLでのLIMITの使い方は以下のとおりである。


	SELECT * FROM tests LIMIT 取得位置,取得数;
	
※取得位置は0から始まる行の番号である。


以下のような記述方法もある。
	SELECT * FROM tests LIMIT 取得数 OFFSET 取得位置;
	

日付の日の部分で検索

date型のフィールドに対して年、月、日を部分的に指定して検索できる。

日(3日)で絞り込む場合
	SELECT * FROM `nekos` WHERE DATE_FORMAT(test_date, '%d')=3;
	


年月(2014年4月)で絞込場合
	SELECT * FROM `nekos` WHERE DATE_FORMAT(test_date, '%Y-%m')='2014-04';
	

phpMyAdminの認証ぎれ対策

phpMyAdminをしばらく操作しないでいると、 「1440 秒以上操作をしませんでした。ログインし直してください。」 というメッセージが出て、再ログインがせままれる。

有効期間を1440秒以上に増やしたい場合、以下の操作をする。
  1. phpMyAdminにログイン
  2. ホーム→設定→機能
  3. 「ログインクッキーの有効期間」を増やす。

1つのテーブルでツリー構造データを扱う

parent_idと自分自身をLEFT JOINすることによりツリー構造データを再現する。

サンプルデータ:1行目は親要素で残りは子要素
id title category_id1 parent_id
1028 テスト・タイトル 99 NULL
1029 テスト2 99 1028
1030 テスト3 99 1028
1031 テスト4 99 1028
1032 テスト5 99 1028

ツリー構造データとして取得するSQL
	SELECT C.id,C.title,C.parent_id,P.title
	FROM recs AS C
	LEFT JOIN recs AS P ON C.parent_id=P.id
	WHERE
		C.category_id1=99
	
ツリー構造データ。一覧に親要素のタイトルが付加されている。
id title parent_id title
1028 テスト・タイトル NULL NULL
1029 テスト2 1028 テスト・タイトル
1030 テスト3 1028 テスト・タイトル
1031 テスト4 1028 テスト・タイトル
1032 テスト5 1028 テスト・タイトル

新規追加と更新を自動判別 | ON DUPLICATE KEY UPDATE

「 ON DUPLICATE KEY UPDATE 」を使うと、通常はINSERTによる行追加だが、プライマリーキーやユニーク成約で重複する場合はUPDATEによる行更新となる。
	INSERT INTO animals (id, name,price)
	VALUES (9, 'キノボリトカゲ', 9000)
	ON DUPLICATE KEY UPDATE id=9, name='キノボリトカゲ', price=9100
	
id name price
6 オオヒラタクワガタ 3500
7 リュウキュウノコギリクワガタ 800
8 タテヅノマルバネクワガタ 10000
9 キノボリトカゲ 9100

複数行のデータをコンマで連結して1行にまとめる | GROUP_CONCAT

◇サンプルデータ nekosテーブル
id val1 text1
1 1 neko
2 2 kani
4 4 buta
5 3 yagi
6 3 ari
7 3 tori
8 3 kame
9 111
10 123 PANDA
11 124 risu
◇サンプル:基本
SELECT GROUP_CONCAT(id  SEPARATOR ', ')  FROM nekos
GROUP_CONCAT(id SEPARATOR ', ')
1, 2, 4, 5, 6, 7, 8, 9, 10, 11
◇サンプル:応用
SELECT
	GROUP_CONCAT(id  SEPARATOR ',') AS ids
	,GROUP_CONCAT(text1  SEPARATOR ',') AS texts
FROM nekos
GROUP BY val1
		
ids texts
1 neko
2 kani
8,7,6,5 kame,tori,ari,yagi
4 buta
9
10 PANDA
11 risu
◇サンプル:応用2:コンマで連結した文字を並べる
SELECT
	GROUP_CONCAT(id  SEPARATOR ',') AS ids
	,GROUP_CONCAT(text1 order by text1 SEPARATOR ',') AS texts
FROM nekos
GROUP BY val1
ids texts
1 neko
2 kani
8,7,6,5 ari,kame,tori,yagi
4 buta
9
10 PANDA
11 risu
参考

フィールドを連結して検索 | CONCAT

	SELECT * FROM nekos WHERE CONCAT( neko_val, neko_name ,neko_date) LIKE '%2014%'
	
3つのフィールドであるneko_val,neko_name,neko_dateを連結して一つの文字列にし、その文字列に対してLIKE条件を指定している。
ただこの方法は問題もあり、neko_val,neko_name,neko_dateのいずれかひとつだけでもNULLがあると、連結文字列もNULLとなってしまい検索できなくなる。
この場合、IFNULLを用いることで対処できる。
	SELECT * FROM nekos WHERE CONCAT( IFNULL(neko_val, ''), IFNULL(neko_name, '') ,IFNULL(neko_date), '') LIKE '%2014%'
	


行の作成日時と更新日時を自動化する

created_at,create_atなどの日時系フィールドの型ですがtimestamp型でなくdatetime型がよい。 理由は以下の通り。
  • timestamp型には2038年問題があります。
  • 何かのタイミングでUTC/協定世界時とJST/日本標準時の間で変換されてしまうことがあります。
  • Laravelのマイグレーションなどでtimestamp型が原因で処理中断というケースがありました。
datetime型でもデフォルトで現在日時をセットする条件を付与できますので、こちらを採用するほうが無難です。
    CREATE TABLE animals (
        id INT(10),
        name VARCHAR(10),
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    )
	

旧式

※未推奨

作成日`created`フィールドを追加するSQLの例
	ALTER TABLE  `tests` ADD  `created` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP COMMENT  '作成日時'
	


更新日`modified`フィールドを追加するSQLの例
	ALTER TABLE  `tests` ADD  `modified` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT  '更新日時'
	


注意:CURRENT_TIMESTAMPは1つのカラムにしか適用できないため、上記の2種類を同時に定義することはできない。(どちらか片方のみ) トリガーを使う方法があるらしい。

他のテーブルのSELECT結果をUPDATEする

下記の例では動物テーブルのspeedに魚テーブルのswim_speedをまとめてセットする例。
テーブルの紐づけ条件はWHERE部分で行う。
	UPDATE
	    animals,
	    fishs
	SET
	    animals.speed = fishs.swim_speed
	WHERE
	    animals.fish_id = fishs.id
	

月別で集計するSQL

日を月別で集計するフィールドの例。

売上sale_amountを月毎に集計する。件数もついでに取得。
2015年で絞込。
test_dateはyyyy-mm-dd型の日付フィールド
	SELECT
	    DATE_FORMAT(test_date, '%Y-%m') as test_ym,
	    COUNT(id) as count,
	    SUM(sale_amount) as sum_sale_amount,
	FROM
	    shops
	WHERE
	    DATE_FORMAT(test_date, '%Y') = '2015'
	GROUP BY
	    DATE_FORMAT(test_date, '%Y-%m')