web-dev-qa-db-ja.com

各GROUP BYに異なるWHERE句がある複数のCOUNT(*)列を選択する方法は?

次のスキーマがあります:

CREATE TABLE Person (
  PersonId int PRIMARY KEY
)

CREATE TABLE Action (
  ActionId int PRIMARY KEY,
  PersonId int NOT NULL FOREIGN KEY REFERENCES Person(PersonId),
  ActionTime datetime NOT NULL
)

および次のデータ:

INSERT INTO Person (PersonId) VALUES
(1),
(2),
(3),
(4)

INSERT INTO Action (ActionId, PersonId, ActionTime) VALUES
(1, 1, '2014-02-01'),
(2, 1, '2014-02-02'),
(3, 2, '2014-02-02'),
(4, 3, '2014-03-05')

各人が毎月15日に実行するアクションの数を示すクエリを実行したいと思います。たとえば、私は次のことを試しています:

SELECT
    Person.PersonId,
    COALESCE(GroupA_Actions_Made, 0) AS GroupA_Actions_Made,
    COALESCE(GroupB_Actions_Made, 0) AS GroupB_Actions_Made
FROM
    Person
    RIGHT OUTER JOIN (
        SELECT
            PersonId,
            COUNT(*) AS GroupA_Actions_Made
        FROM
            Action
        WHERE
            ActionTime BETWEEN '2014-01-15 00:00:00' AND '2014-02-14 23:59:59'
        GROUP BY
            PersonId
    ) GroupA ON GroupA.PersonId = Person.PersonId
    RIGHT OUTER JOIN (
        SELECT
            PersonId,
            COUNT(*) AS GroupB_Actions_Made
        FROM
            Action
        WHERE
            ActionTime BETWEEN '2014-02-15 00:00:00' AND '2014-03-14 23:59:59'
        GROUP BY
            PersonId
    ) GroupB ON GroupB.PersonId = Person.PersonId

しかし、私が試しているクエリは次を返しています:

PersonId | GroupA_Actions_Made | GroupB_Actions_Made
(null)     0                     1

でも欲しい

PersonId | GroupA_Actions_Made | GroupB_Actions_Made
1          2                     0
2          1                     0
3          0                     1

(アクションを実行していない人には結果が何も返さないようにしたい)。

希望する形式で結果を取得するにはどうすればよいですか?

[〜#〜]更新[〜#〜]

次のようにラップする必要があることを除いて、それぞれの回答は機能します。

SELECT
    PersonId,
    GroupA_Actions_Made,
    GroupB_Actions_Made
FROM (
    -- (answer)
) t
WHERE
    GroupA_Actions_Made > 0
    OR GroupB_Actions_Made > 0

SQL Serverプロファイラを使用すると、受け入れられた回答は、大規模なデータセットでのクエリ時間が最も速いようです。

6
cm007

これであなたが望んだ結果が得られますが、それが最も柔軟なコードであるかどうかはわかりません。

SELECT p.PersonId,
    SUM(CASE 
          WHEN a.ActionTime >= '2014-01-15 00:00:00' 
            AND a.ActionTime < '2014-02-15 00:00:00'
          THEN 1 
          ELSE 0 
        END) AS GroupA_Actions_Made,
    SUM(CASE 
          WHEN a.ActionTime >= '2014-02-15 00:00:00' 
            AND a.ActionTime < '2014-03-15 00:00:00'
          THEN 1 
          ELSE 0 
        END) AS GroupB_Actions_Made
FROM
    Person p
JOIN
    Action a on p.PersonId = a.PersonId
GROUP BY p.PersonId
6
James Anderson

少し異なるアプローチ:

select
    PersonId
    ,GroupA_Actions_Made
    ,GroupB_Actions_Made
    from (
        select
            p.PersonId
            ,[groupName] = 'Group' + case
                when a.ActionTime >= '2014-01-15' and a.ActionTime < '2014-02-15'
                    then 'A'
                when a.ActionTime >= '2014-02-15' and a.ActionTime < '2014-03-15'
                    then 'B'
                end + '_Actions_Made'
            from dbo.Person p
            inner join dbo.Action a
                on p.PersonId = a.PersonId
        ) pdata
    pivot(count([groupName]) for [groupName] in ([GroupA_Actions_Made],[GroupB_Actions_Made]))pvt

また、 間に注意

1
SQLFox

これはやや柔軟なはずです。

SELECT PersonId, [2014.02] AS GroupA_Actions_Made, [2014.03] AS GroupB_Actions_Made
FROM (
    SELECT ActionId, PersonId, 
        CONVERT(char(7), DATEADD(month,1,DATEADD(dd,-14,ActionTime)), 102) Grouping
    FROM Action
    ) p
PIVOT (COUNT(ActionId) 
    FOR Grouping IN ([2014.02],[2014.03])
    ) AS pvt
1
Kenneth Fisher