はじめに
本稿では、第1、2回で得た知識をより深めるために、MySQL 8.0で、『SQLパズル 第2版』の問題を解きます。
対象読者
- MySQLのSQLの理解を深めたい方
 
必要な環境
本稿で扱うSQLは、MySQL 8.0.15で動作確認しました。
パズル17. 人材紹介会社
以下の問題を解きます。なお、問題は『SQLパズル 第2版』76、77ページより引用、簡潔に改変したものです。
人材紹介会社のデータベースから「特定のスキルの条件を満たす登録者を見つけ出す」ためのSelect文はどのようになるでしょうか。テーブルデータは以下の通りです。
Skillsテーブル ID Skill --- -------- 100 会計 100 在庫管理 100 製造 200 会計 200 在庫管理 300 製造 400 在庫管理 400 製造 500 会計 500 製造
下記の出力条件を満たすIDを出力してみましょう。
Skill = '製造' のレコードが存在し、かつ、Skill = '在庫管理' のレコードが存在する。 または Skill = '会計' のレコードが存在する。
ID --- 100 200 400 500
create table Skills(
ID    int,
Skill char(16),
primary key(ID,Skill));
insert into Skills values(100,'会計'),
                         (100,'在庫管理'),
                         (100,'製造'),
                         (200,'会計'),
                         (200,'在庫管理'),
                         (300,'製造'),
                         (400,'在庫管理'),
                         (400,'製造'),
                         (500,'会計'),
                         (500,'製造');
  解として、以下の2つの方法が考えられます。
select ID
  from Skills
group by ID
having sum(case Skill when '製造'     then 1 else 0 end) > 0
   and sum(case Skill when '在庫管理' then 1 else 0 end) > 0
    or sum(case Skill when '会計'     then 1 else 0 end) > 0
order by ID;
  
select ID
  from Skills
group by ID
having max(Skill = '製造') and max(Skill = '在庫管理')
    or max(Skill = '会計')
order by ID;
  MySQLのBoolean型は、C++の論理型のように、trueが1、falseが0なので、max関数で存在肯定命題をチェックでき、min関数で全称肯定命題をチェックできます。
SQLのイメージは下記となります。group by IDで赤線を引いています。

パズル18. ダイレクトメール
以下の問題を解きます。なお、問題は『SQLパズル 第2版』81ページより引用、簡潔に改変したものです。
ダイレクトメールの顧客リストから、特定の人の行を削除するDelete文を考えます。
Consumersテーブル ConName Address Con_ID Fam ------- ------- ------ ---- Bob AAAA 1 null ←削除対象行 Joe BBBB 3 null ←削除対象行 Mark CCCC 5 null Mary AAAA 2 1 Vickie BBBB 4 3 Wayne DDDD 6 null
Fam列がnullで、かつ、Address列が同じ行が存在したら、削除します。
create table Consumers(
ConName char(6),
Address char(4),
Con_ID  int,
Fam     int);
insert into Consumers values('Bob'   ,'AAAA',1,null),
                            ('Joe'   ,'BBBB',3,null),
                            ('Mark'  ,'CCCC',5,null),
                            ('Mary'  ,'AAAA',2,   1),
                            ('Vickie','BBBB',4,   3),
                            ('Wayne' ,'DDDD',6,null);
  解として、以下の2つの方法が考えられます。
with DelView as(
select ConName
  from (select ConName,Fam,count(*) over(partition by Address) as Cnt
          from Consumers) tmp
 where Fam is null
   and Cnt > 1)
delete from Consumers
 where ConName in(select ConName from DelView);
  With句で定義したビューに対してDelete文を実行すると、MySQL 8.0では文法エラーになるので、In述語で定義したビューを参照しています。
with tmp as( select Address from Consumers group by Address having count(*) > 1) delete from Consumers where Fam is null and Address in(select Address from tmp);
having句でチェックする方法もあります。SQLのイメージは下記となります。group by Addressで赤線を引いています。

