1. 基本的な考え方
SQL文のjoinキーワードについて、よく使われるキーワードですがわかりにくいので、テーブルuser1、user2の作成例で簡単に説明します。
table1 : create table user2(id int, user_name varchar(10), over varchar(10));
insert into user1 values(1, ‘tangseng', ‘dtgdf');
insert into user1 values(2, ‘sunwukong', ‘dzsf');
insert into user1 values(1, ‘zhubajie', ‘jtsz');
insert into user1 values(1, ‘shaseng', ‘jslh');
table2 : create table user2(id int, user_name varchar(10), over varchar(10));
insert into user2 values(1, ‘sunwukong', ‘chengfo');
insert into user2 values(2, ‘niumowang', ‘chengyao');
insert into user2 values(3, ‘jiaomowang', ‘chengyao');
insert into user2 values(4, ‘pengmowang', ‘chengyao');
SQL 標準における結合の種類
- 内部結合 (内部結合または結合)
(1). 概念: 内部結合は、結合述語に基づいて 2 つのテーブルの列を結合し、新しい結果テーブルを生成します。
(2).内部接続ベン図:
(3).sql文
select a.id, a.user_name, b.over
from user1 a
inner join user2 b
on a.user_name=b.user_name;
結果:
- 外部結合
外部結合には、左外部結合、右外部結合、または完全外部結合が含まれます。
a. 左外部結合: 左結合または左外部結合
(1) 概念: 左外部結合の結果セットには、結合列と一致する行だけでなく、LEFT OUTER 句で指定された左表のすべての行が含まれます。左側のテーブルの行に右側のテーブルに一致する行がない場合、右側のテーブルのすべての選択リスト列は、関連する結果セット行で null になります。
(2) 左外部結合ベン図:
(3)SQL文:
select a.id, a.user_name, b.over
from user1 a
left join user2 b
on a.user_name=b.user_name;
結果:
b. 右外部結合:右外部結合または右外部結合
(1) 右外部結合は、左外部結合の逆結合です。右側のテーブルのすべての行が返されます。右側のテーブルの行に左側のテーブルに一致する行がない場合、左側のテーブルに対して null が返されます。
(2) 右外部結合ベン図:
(3)SQL文
select b.user_name, b.over, a.over
from user1 a
right join user2 b
on a.user_name=b.user_name;
結果:
c. 完全外部結合: 完全外部結合または完全外部結合
(1) 完全な外部結合は、左右のテーブルのすべての行を返します。行に別のテーブルに一致する行がない場合、他のテーブルの選択リスト列には NULL 値が含まれます。テーブル間に一致する行がある場合、結果セットの行全体にベース テーブルのデータ値が含まれます。
(2) 右外部結合ベン図:
(3)SQL文
select a.id, a.user_name, b.over
from user1 a
full join user2 b
on a.user_name=b.user_name
mysql で完全な接続をクエリすると、エラー 1064 が報告されます。Mysql は完全な接続クエリをサポートしていません。ステートメントの代わりに、次のようにします。
select a.user_name,a.over,b.over
from user1 a left join user2 b
on a.user_name = b.user_name
union all select b.user_name,b.over ,a.over
from user1 a
right join user2 b
on a.user_name = b.user_name;
結果:
- デカルト結合 (クロス結合)
1. 概念: WHERE 句を使用しないクロス結合では、結合に含まれるテーブルのデカルト積が生成されます。最初のテーブルの行数と 2 番目のテーブルの行数を乗算すると、デカルト積の結果セットのサイズに等しくなります。(user1 と user2 の相互接続により、4*4=16 レコードが生成されます)
2. クロス結合: クロス結合 (条件なし)
3. SQL ステートメント:
select a.user_name,b.user_name, a.over, b.over
from user1 a cross join user2 b;
2. 使用スキル
- join を使用したテーブルの更新
次のステートメントを使用して、user1 テーブルと user2 テーブルの両方のレコードを含む user1 テーブルの over フィールドを「qtda」に更新します。
update user1 set over='qtds'
where user1.user_name in (
select b.user_name
from user1 a
inner join user2 b
on a.user_name = b.user_name);
このステートメントは SQL Server と Oracle では正しく実行できますが、mysql ではエラーが報告されます。Mysql はサブクエリ テーブルの更新をサポートしていないため、次のステートメントを使用して実行できます。
update user1 a
join (
select b.user_name
from user1 a
join user2 b
on a.user_name = b.user_name) b
on a.user_name = b.user_name
set a.over = ‘qtds'
- 結合を使用してサブクエリを最適化する
サブクエリの効率が比較的低いため、次のステートメントを使用してクエリを実行します。
select a.user_name, a.over,(
select over
from user2 b
where a.user_name=b.user_name)
as over2 from user1 a;
結合を使用してサブクエリを最適化すると、同じ効果が得られます
select a.user_name, a.over, b.over as over2
from user1 a
left join user2 b
on a.user_name = b.user_name;
- 結合を使用して集計サブクエリを最適化する
新しいテーブルを導入します。
user_kills
create table user_kills(user_id int, timestr varchar(20), kills int(10));
insert into user_kills values(2, ‘2015-5-12', 20);
insert into user_kills values(2, ‘2015-5-15', 18);
insert into user_kills values(3, ‘2015-5-11', 16);
insert into user_kills values(3, ‘2015-5-14', 13);
insert into user_kills values(3, ‘2015-5-16', 17);
insert into user_kills values(4, ‘2015-5-12', 16);
insert into user_kills values(4, ‘2015-5-10', 13);
user1 の各人物に対応する user_kills テーブル内の最大キル数の日付をクエリするには、集約サブクエリ ステートメントを使用します。
select a.user_name,b.timestr, b.kills
from user1 a
join user_kills b
on a.id = b.user_id
where b.kills = (
select MAX(c.kills)
from user_kills c
where c.user_id = b.user_id);
結合を使用して集計サブクエリを最適化する (サブクエリを避ける)
select a.user_name, b.timestr, b.kills
from user1 a
join user_kills b
on a.id = b.user_id
join user_kills c
on c.user_id = b.user_id
group by a.user_name, b.timestr, b.kills
having b.kills = max(c.kills);
結果:
- グループ選択データの実装
(この要件は、当時の職場では非常に苦痛でした。状況は変わりつつあります。複数の状況のバージョンについては後で整理します。)
user1 の各人物の最多キルの 2 日前をクエリする必要があります。
まず、次のステートメントを使用して、人が最も多く殺害された 2 日をクエリします。
select a.user_name, b.timestr, b.kills
from user1 a
join user_kills b
on a.id = b.user_id
where a.user_name ='sunwukong'
order by b.kills desc limit 2;
では、1 つのステートメントを使用して、すべての人の中で最も多くのキルがあった 2 日間をクエリするにはどうすればよいでしょうか? 次のステートメントを見てください。
WITH tmp AS (
select a.user_name, b.timestr, b.kills, ROW_NUMBER() over(
partition by a.user_name order by b.kills) cnt
from user1 a join user_kills b on a.id = b.user_id)
select * from tmp where cnt <= 2;
上記のステートメントは、SQL Server と Oracle の両方でサポートされていますが、mysql バージョン 5.7 以下では、グループ化ソート関数 ROW_NUMBER() と一時テーブルの作成をサポートしていません。代替方法を以下に示します。
select d.user_name,c.timestr, kills
from (select user_id, timestr, kills, (
select count(*)
from user_kills b
where b.user_id = a.user_id and a.kills <= b.kills) as cnt from user_kills a
group by user_id, timestr, kills) c
join user1 d
on c.user_id = d.id
where cnt <= 2;
結果:
スクリプトハウスより転載