1つのSQLクエリで複数のカウントを取得するにはどうすればよいですか?


316

このクエリの書き方を知りたいです。

私はこの実際の構文が偽物であることを知っていますが、それは私が何を望んでいるかを理解するのに役立ちます。非常に大きなクエリの一部なので、この形式で必要です。

SELECT distributor_id, 
COUNT(*) AS TOTAL, 
COUNT(*) WHERE level = 'exec', 
COUNT(*) WHERE level = 'personal'

これをすべて1つのクエリで返す必要があります。

また、1行に収める必要があるため、以下は機能しません。

'SELECT distributor_id, COUNT(*)
GROUP BY distributor_id'

1
このクエリは正しく機能しましたか?SELECT distributor_id, COUNT(*) AS TOTAL, COUNT(*) WHERE level = 'exec', COUNT(*) WHERE level = 'personal'
Pratik 2016年

回答:


690

CASE集計関数でステートメントを使用できます。これは基本的PIVOTに一部のRDBMSの関数と同じです。

SELECT distributor_id,
    count(*) AS total,
    sum(case when level = 'exec' then 1 else 0 end) AS ExecCount,
    sum(case when level = 'personal' then 1 else 0 end) AS PersonalCount
FROM yourtable
GROUP BY distributor_id

55
素晴らしい、これは素晴らしいです。すばらしい答えです。ここで偶然見つけた人へのメモです。カウントはすべての行をカウントし、合計はケースステートメントで使用した場合のカウントと同じことを行います。
John Ballinger

1
素晴らしいソリューション!この方法は、多くのテーブルを1つのクエリで組み合わせる場合と同じようにうまく機能することに注意してください。サブクエリを使用すると、そのインスタンスでかなり面倒になる可能性があるからです。
Darren Crabb、2015年

7
この非常にエレガントなソリューションをありがとう。ところで、これはTSQLでも機能します。
Annie Lagang 2016

6
なぜこれが最善の答えではないのか:常に全表スキャン。count-subqueriesの結合、またはselect内のネストされたcountsを検討してください。ただし、インデックスが存在しない場合は、複数のテーブルスキャンに対して1つのテーブルスキャンのみを保証しているため、これが最適な場合があります。@KevinBalmforthからの回答を参照
YoYo

1
@JohnBallinger、「カウントはすべての行をカウントします」- 賢くCOUNTカウントしますdistributor_id。テーブルのすべての行ではありませんよね?
イスティアケアーメド2017

88

確かに機能する1つの方法

SELECT a.distributor_id,
    (SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
    (SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
    (SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
FROM (SELECT DISTINCT distributor_id FROM myTable) a ;

編集:
この方法を使用したくない可能性があり、代わりに@ Taryn♦の回答を選択する必要がある理由については、@ KevinBalmforthのパフォーマンスの内訳を参照してください。人々が彼らの選択肢を理解できるように、私はこれを残します。


2
これは、複数のカウントを実行し、各カウントが列である単一のSELECTステートメントでそれらを出力する方法を解決するのに役立ちました。すばらしい作品です。ありがとうございます。
マーク・

2
ここで提供されたものを私のプロジェクトで使用することができました。これで、すべてが複数のクエリではなく、1つのクエリに含まれるようになりました。ページは1秒未満で読み込まれますが、複数のクエリでは5〜8秒かかります。大好きです。ありがとう、Notme。
ウェインバロン2017年

1
各サブクエリが実際にインデックスにヒットする場合、これはうまくいくかもしれません。そうでない場合は、sum(case...)ソリューションを検討する必要があります。
YoYo 2017

1
個別の代わりに、修正を加えたので、@ Mihaiが示すようにgroup by、ネストされたクエリ全体を単純なクエリに置き換えるという利点がありcount(*)ます。さらに、MySQLのみの構文簡略化を使用できます。
YoYo 2017

43
SELECT 
    distributor_id, 
    COUNT(*) AS TOTAL, 
    COUNT(IF(level='exec',1,null)),
    COUNT(IF(level='personal',1,null))
FROM sometable;

COUNTnon null値のみをカウントし、条件が満たされた場合にのみDECODEnull以外の値を返し1ます。


そのdistributor_idクエリのショーがしますか?合計で1行表示されます。
Istiaque Ahmed

OPには、私の回答で省略されていた列にgroup byがあります。
Majid Laissi 2017年

あなたは私の命を救った、他のすべての回答者はMySQLで複数の行を返します。どうもありがとう
アブナー

1
@Abnerはこれが8年経ってもまだ役に立てて嬉しい:)
Majid Laissi

@MajidLaissiはい、そうしました。クエリ時間を1分から1秒未満に変更しました。:)
アブナー

25

他の投稿された回答に基づいて構築します。

これらはどちらも正しい値を生成します。

select distributor_id,
    count(*) total,
    sum(case when level = 'exec' then 1 else 0 end) ExecCount,
    sum(case when level = 'personal' then 1 else 0 end) PersonalCount
from yourtable
group by distributor_id

SELECT a.distributor_id,
          (SELECT COUNT(*) FROM myTable WHERE level='personal' and distributor_id = a.distributor_id) as PersonalCount,
          (SELECT COUNT(*) FROM myTable WHERE level='exec' and distributor_id = a.distributor_id) as ExecCount,
          (SELECT COUNT(*) FROM myTable WHERE distributor_id = a.distributor_id) as TotalCount
       FROM myTable a ; 

ただし、パフォーマンスはかなり異なります。これは、明らかにデータの量が増えるにつれて、より適切になります。

テーブルにインデックスが定義されていない場合、SUMを使用したクエリは単一のテーブルスキャンを実行し、COUNTを使用したクエリは複数のテーブルスキャンを実行することがわかりました。

例として、次のスクリプトを実行します。

IF OBJECT_ID (N't1', N'U') IS NOT NULL 
drop table t1

create table t1 (f1 int)


    insert into t1 values (1) 
    insert into t1 values (1) 
    insert into t1 values (2)
    insert into t1 values (2)
    insert into t1 values (2)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (3)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)
    insert into t1 values (4)


SELECT SUM(CASE WHEN f1 = 1 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 2 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 3 THEN 1 else 0 end),
SUM(CASE WHEN f1 = 4 THEN 1 else 0 end)
from t1

SELECT 
(select COUNT(*) from t1 where f1 = 1),
(select COUNT(*) from t1 where f1 = 2),
(select COUNT(*) from t1 where f1 = 3),
(select COUNT(*) from t1 where f1 = 4)

2つのSELECTステートメントを強調表示し、[推定実行プランの表示]アイコンをクリックします。最初のステートメントは1つのテーブルスキャンを実行し、2番目のステートメントは4を実行することがわかります。明らかに、1つのテーブルスキャンは4よりも優れています。

クラスタ化インデックスを追加することも興味深いです。例えば

Create clustered index t1f1 on t1(f1);
Update Statistics t1;

上記の最初のSELECTは、単一のクラスター化インデックススキャンを実行します。2番目のSELECTは4つのクラスター化インデックスシークを実行しますが、1つのクラスター化インデックススキャンよりもコストがかかります。800万行のテーブルで同じことを試しましたが、2番目のSELECTの方がはるかに高価でした。


23

MySQLの場合、これは次のように短縮できます。

SELECT distributor_id,
    COUNT(*) total,
    SUM(level = 'exec') ExecCount,
    SUM(level = 'personal') PersonalCount
FROM yourtable
GROUP BY distributor_id

1
このクエリでは「group by distribution_id」が本当に必要でしたか?それはそれなしでも機能します
user1451111

2
@ user1451111元の質問はそれを理解したので、答えは質問自体に依存します
Al-Mothafar

11

まあ、1つのクエリにすべてを含める必要がある場合は、結合を行うことができます。

SELECT distributor_id, COUNT() FROM ... UNION
SELECT COUNT() AS EXEC_COUNT FROM ... WHERE level = 'exec' UNION
SELECT COUNT(*) AS PERSONAL_COUNT FROM ... WHERE level = 'personal';

または、処理後に実行できる場合:

SELECT distributor_id, COUNT(*) FROM ... GROUP BY level;

各レベルのカウントを取得し、合計を取得するにはそれらをすべて合計する必要があります。


関数のUNION複数のインスタンスを含むレポートを生成するときに非常に役立つことがわかりましたCOUNT(*)
ジェームズO

結果は示しています#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ') FROM distributors UNION SELECT COUNT() AS EXEC_COUNT FROM distributors WHERE ' at line 1
イスティアケアーメド2017

UNIONが適用されるすべてのクエリから返される列の数は等しくなければなりません。@IstiaqueAhmedおそらくそれがエラーの背後にある理由です。
user1451111

将来この答えに出くわした人のためのメモ。ここで説明する「後処理」手法は、「レベル」列の一部の値がNULLの場合に問題を引き起こす可能性があります。その場合、すべてのサブカウントの合計は合計行カウントと等しくなりません。
user1451111

6

列Aで識別するために、各テーブルに文字列名と列のカウントを与えるだけです。次に、それらをすべて結合してスタックします。結果は私の意見ではかなりです-他のオプションと比較してどれほど効率的かはわかりませんが、必要なものを手に入れました。

select 'table1', count (*) from table1
union select 'table2', count (*) from table2
union select 'table3', count (*) from table3
union select 'table4', count (*) from table4
union select 'table5', count (*) from table5
union select 'table6', count (*) from table6
union select 'table7', count (*) from table7;

結果:

-------------------
| String  | Count |
-------------------
| table1  | 123   |
| table2  | 234   |
| table3  | 345   |
| table4  | 456   |
| table5  | 567   |
-------------------

1
a query that I created makes ...-そのクエリはどこですか?
Istiaque Ahmed 2017

2
すべてのテーブルに場所を追加する方法

3

Bluefeetの受け入れられた応答に基づいて、以下を使用してニュアンスを追加しましたOVER()

SELECT distributor_id,
    COUNT(*) total,
    SUM(case when level = 'exec' then 1 else 0 end) OVER() ExecCount,
    SUM(case when level = 'personal' then 1 else 0 end) OVER () PersonalCount
FROM yourtable
GROUP BY distributor_id

OVER()()で何も使用せずに使用すると、データセット全体の総数が得られます。


1

これもうまくいくと思います select count(*) as anc,(select count(*) from Patient where sex='F')as patientF,(select count(*) from Patient where sex='M') as patientM from anc

そして、あなたはこのように関連するテーブルを選択して数えることができます select count(*) as anc,(select count(*) from Patient where Patient.Id=anc.PatientId)as patientF,(select count(*) from Patient where sex='M') as patientM from anc

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.