本エントリはFirebird Advent Calendar 2017の14日目です。
DISTINCT, COUNT, そしてNULL
唐突ですが、次の文章のうち間違っているものは? SELECT文で。。。。
(1) 行数を数えるときにはCOUNT(*)
(2) 行数を数えるときにはCOUNT(カラム名)でも(1)と同じ
(3) カラムのユニークな行数を数えるときはCOUNT(DISTINCT カラム名)
(4) 複数カラムのユニークな行数を数えるときはCOUNT(DISTINCT カラム名1, カラム名2)
じゃ確認してみましょう。
create table t1(i1 int, i2 int);
insert into t1 values(1,1);
insert into t1 values(1,2);
insert into t1 values(2,3);
select count(*) from t1;
COUNT
=====================
3
select count(i1) from t1;
COUNT
=====================
3
(1),(2)ともによさそうに見えますね。。。。ってちょっと待ったー! もう一行nullを含む行を追加してみましょう。
insert into t1 values(null,3);
select count(*) from t1;
COUNT
=====================
4
select count(i1) from t1;
COUNT
=====================
3
そう、実は集約関数はNULLを除外します。ただし「COUNT(*)」は例外的にNULLを除外しないのです。
そのため、上記のような違いが現れます。Firebirdに限らず、標準SQLに準拠しているものはすべてそうなります。
SQLのCOUNT(*)とCOUNT(列名)では結果が異なる(山本隆の開発日誌)
select count(distinct i1) from t1;
COUNT
=====================
2
これはまず、distinctで1,2,nullの三行になり、count(列名)はnullを除外するので、2となります。
これはcountを除いた以下のクエリで一目両全です。(3)はOKそうですね。
select distinct i1 from t1;
I1
============
<null>
1
2
次に(4)にトライしてみると。。。。エラーですね。
select count(distinct i1,i2) from t1;
Statement failed, SQLSTATE = 42000
Dynamic SQL Error
-SQL error code = -104
-Token unknown - line 1, column 25
-,
そう、Firebirdを含めたほとんどのRDBMSではCOUNT(DISTINCT マルチカラム)は許されていないのです。
そのため、よく代案として利用されるのが、DISTINCTをサブクエリにした以下のクエリや、DISTINCTの代わりにGROUP BYを使うものです。
select count(*) from (select distinct i1,i2 from t1);
COUNT
=====================
4
もう一つはカラムを、そのカラムに出てこないデリミタを入れてCONCATするものです。
select count(distinct i1 || '-' || i2) from t1;
COUNT
=====================
3
OK, って一行減ってますね。。。。これもcountを除くと意味がわかります。
select distinct i1 || '-' || i2 from t1;
CONCATENATION
=======================
<null>
1-1
1-2
2-2
NULLをCONCATするとNULLになり、それはCOUNTに計上されません。そのため巷のDISTINCTをサブクエリに
する方法は、データにNULLがあると正しくないのです。厳密にいうと、こうなります。
select count(*) from (select distinct i1,i2 from t1 where i1 is not null and i2 is not null);
COUNT
=====================
3
本ブログエントリの最初のほうで「ほとんどのRDBMSではCOUNT(DISTINCT マルチカラム)は許されていない」
と記述しましたが、唯一許されているのがMySQLです。
create table t1(i1 int, i2 int);
insert into t1 values(1,1);
insert into t1 values(1,2);
insert into t1 values(2,3);
insert into t1 values(null,3);
select count(distinct i1,i2) from t1;
+-----------------------+
| count(distinct i1,i2) |
+-----------------------+
| 3 |
+-----------------------+
1 row in set (0.03 sec)
ということで、正しくでるのですが、DISTINCTしたマルチカラムそれぞれにIS NOT NULLをANDでつけていないクエリで
NULLを含むデータを投入して「結果が違う〜」という向きがいるので、それはこのような違いがある、ということを
覚えておいてください!
結果(1),(3)はOK, (2)は違う(カラムがnot nullならOK), (4) はMySQLだけ、ということになります。
DISTINCT, COUNT, そしてNULL
唐突ですが、次の文章のうち間違っているものは? SELECT文で。。。。
(1) 行数を数えるときにはCOUNT(*)
(2) 行数を数えるときにはCOUNT(カラム名)でも(1)と同じ
(3) カラムのユニークな行数を数えるときはCOUNT(DISTINCT カラム名)
(4) 複数カラムのユニークな行数を数えるときはCOUNT(DISTINCT カラム名1, カラム名2)
じゃ確認してみましょう。
create table t1(i1 int, i2 int);
insert into t1 values(1,1);
insert into t1 values(1,2);
insert into t1 values(2,3);
select count(*) from t1;
COUNT
=====================
3
select count(i1) from t1;
COUNT
=====================
3
(1),(2)ともによさそうに見えますね。。。。ってちょっと待ったー! もう一行nullを含む行を追加してみましょう。
insert into t1 values(null,3);
select count(*) from t1;
COUNT
=====================
4
select count(i1) from t1;
COUNT
=====================
3
そう、実は集約関数はNULLを除外します。ただし「COUNT(*)」は例外的にNULLを除外しないのです。
そのため、上記のような違いが現れます。Firebirdに限らず、標準SQLに準拠しているものはすべてそうなります。
SQLのCOUNT(*)とCOUNT(列名)では結果が異なる(山本隆の開発日誌)
select count(distinct i1) from t1;
COUNT
=====================
2
これはまず、distinctで1,2,nullの三行になり、count(列名)はnullを除外するので、2となります。
これはcountを除いた以下のクエリで一目両全です。(3)はOKそうですね。
select distinct i1 from t1;
I1
============
<null>
1
2
次に(4)にトライしてみると。。。。エラーですね。
select count(distinct i1,i2) from t1;
Statement failed, SQLSTATE = 42000
Dynamic SQL Error
-SQL error code = -104
-Token unknown - line 1, column 25
-,
そう、Firebirdを含めたほとんどのRDBMSではCOUNT(DISTINCT マルチカラム)は許されていないのです。
そのため、よく代案として利用されるのが、DISTINCTをサブクエリにした以下のクエリや、DISTINCTの代わりにGROUP BYを使うものです。
select count(*) from (select distinct i1,i2 from t1);
COUNT
=====================
4
もう一つはカラムを、そのカラムに出てこないデリミタを入れてCONCATするものです。
select count(distinct i1 || '-' || i2) from t1;
COUNT
=====================
3
OK, って一行減ってますね。。。。これもcountを除くと意味がわかります。
select distinct i1 || '-' || i2 from t1;
CONCATENATION
=======================
<null>
1-1
1-2
2-2
NULLをCONCATするとNULLになり、それはCOUNTに計上されません。そのため巷のDISTINCTをサブクエリに
する方法は、データにNULLがあると正しくないのです。厳密にいうと、こうなります。
select count(*) from (select distinct i1,i2 from t1 where i1 is not null and i2 is not null);
COUNT
=====================
3
本ブログエントリの最初のほうで「ほとんどのRDBMSではCOUNT(DISTINCT マルチカラム)は許されていない」
と記述しましたが、唯一許されているのがMySQLです。
create table t1(i1 int, i2 int);
insert into t1 values(1,1);
insert into t1 values(1,2);
insert into t1 values(2,3);
insert into t1 values(null,3);
select count(distinct i1,i2) from t1;
+-----------------------+
| count(distinct i1,i2) |
+-----------------------+
| 3 |
+-----------------------+
1 row in set (0.03 sec)
ということで、正しくでるのですが、DISTINCTしたマルチカラムそれぞれにIS NOT NULLをANDでつけていないクエリで
NULLを含むデータを投入して「結果が違う〜」という向きがいるので、それはこのような違いがある、ということを
覚えておいてください!
結果(1),(3)はOK, (2)は違う(カラムがnot nullならOK), (4) はMySQLだけ、ということになります。
JUGEMテーマ:コンピュータ