SQL Server查询-对具有多个表中特定条件的列中的值进行计数

时间:2019-01-13 17:02:11

标签: sql sql-server

我正在尝试从SQL Server上的三个不同表生成报告,该报告显示Account_idAccount_entries表中的account表中Users的计数或出现次数来自三个表的条件。

表#1:帐户

ID          ACCOUNT_TYPE         
-------------------------
354857      Customer            
354858      Agent          
354859      Fee
354860      Customer 
354861      Customer 
354862      Agent   
354863      Cashier

表2: ACCOUNT_ENTRIES

ID     ACCOUNT_ID   narrative_TYPE    CREATED_AT  
-------------------------------------------------
35     Customer     Fee               2018-01-02  
36     Agent        Fee               2018-11-02
37     Fee          BalanceUpdate     2018-11-03
39     Customer     BalanceUpdate     2018-11-03  

表#3:用户

ID    PHONE_NUMBER  REGISTERED_BY (ACCOUNT_ID)   CREATED_AT  
------------------------------------------------------------
35    XXXXXXX       354858                       2018-01-02    
36    XXXXXXX       354877                       2018-11-02
37    XXXXXXX       354858                       2018-11-03
39    XXXXXXX       354858                       2018-11-03       

我已经尝试过此SQL查询,但无法获得所需的输出:

select 
    ac.id, count(ae.id) as counter1, count(u.registered_by) as counter2 
from 
    db2inst1.accounts ac
left outer join 
    db2inst1.account_entries ae on ac.id = ae.account_id
left outer join 
    db2inst1.users u on ac.id = u.registered_by 
where 
    ae.narrative_type = 'BalanceUpdate' 
    and ae.created_at > '2018-11-30' 
    and ae.created_at < '2019-01-01' 
    and u.created_at > '2018-11-30' 
    and u.created_at < '2019-01-01' 
    and ac.account_type = 'Agent'
group by 
    ac.id

我实际上想看到的是下面的

ACCOUNT_ID    COUNTER1  COUNTER2   COUNTER1+COUNTER2
----------------------------------------------------
354857            20         2      22 
354858            24        23      47
354859            26        11      37
354860            27        23      60  

其中计数器1计数account_idaccount_entries的出现次数,计数器2在users表上(由其注册)

请帮助

2 个答案:

答案 0 :(得分:0)

我认为获取所需方法的快捷方式是使用count(distinct)。您还需要将过滤条件移至on子句中,以免不必要地过滤掉行:

select ac.id, count(distinct ae.id) as counter1, 
       count(distinct u.registered_by) as counter2 
from db2inst1.accounts ac left outer join
     db2inst1.account_entries ae
     on ac.id = ae.account_id and
        ae.narrative_type = 'BalanceUpdate' and
        ae.created_at > '2018-11-30' and
        ae.created_at < '2019-01-01' left outer join
     db2inst1.users u
     on ac.id = u.registered_by and
        u.created_at > '2018-11-30' and
        u.created_at < '2019-01-01'
where ac.account_type = 'Agent'
group by ac.id;

答案 1 :(得分:0)

我在SELECT查询中看到了几个潜在的问题(尽管尝试非常可靠,但是很好!)

  1. 先执行LEFT JOIN,然后在WHERE子句中对LEFT JOIN中表中的列进行过滤,几乎可以将其转换为 INNER JOIN

假设account_id“ 2”在account_entries表中没有记录,请考虑左连接的这些结果:

SELECT * FROM accounts A LEFT JOIN account_entries B ON A.id = B.account_id

|-- accounts table --|  |----------- account_entries table ---------|
id   account_type        id    account_id  narrative_type  created_at
---------------------------------------------------------------------
1    Agent               101   1           Fee             2018-12-01
1    Agent               102   1           BalanceUpdate   2018-12-02
2    Customer            NULL  NULL        NULL            NULL
3    Agent               103   3           Fee             2018-12-01

在这种情况下,如果您添加到查询WHERE narrative_type = 'BalanceUpdate',则将对每条记录进行评估,并且由于NULL不等于'BalanceUpdate',它将过滤出account_id“ 2” 。这模仿了INNER JOIN

的行为

要解决此问题,您可以将过滤器移到联接的ON子句中,而不要移到WHERE子句中(例如ON A.id = B.account_id AND B.narrative_type = 'BalanceUpdate'

在某些情况下,将其保留在WHERE子句中,但使用ISNULL可以有所帮助,但我认为在这种特定用例中这没有任何意义。


  1. 由于account_entries和用户中的每个帐户可能有多个记录,因此,如果将它们都重新加入到accounts表中,则最终会得到某种笛卡尔积。

例如,如果您有以下account_entries:

id    account_id  narrative_type  created_at
--------------------------------------------
101   1           Fee             2018-12-01
102   1           BalanceUpdate   2018-12-02
103   3           Fee             2018-12-01

这些用户:

id    phone_number  registered_by  created_at
---------------------------------------------
1001  XXXXX         1              2018-12-01
1002  XXXXX         1              2018-12-01
1003  XXXXX         2              2018-12-01

将它们结合在一起,除了帐户ID外,它们之间没有任何关系,必须将每个帐户条目与每个与该帐户ID相匹配的用户进行匹配。然后,您将得到以下结果:

account_id  account_entry_id  user_id
--------------------------------------------
1           101               1001
1           101               1002
1           102               1001
1           102               1002
2           NULL              1003
3           103               NULL

要解决此问题,可以使用COUNT(DISTINCT ...),然后将其忽略。这可能很好,但也许在更大的数据集上可能会导致性能问题。

我希望在加入数据之前进行汇总。这可以通过简单的子查询来完成,也可以使用通用表表达式(“ CTE”)非常干净地完成

这是我处理查询的方法:

WITH cte_account_entries AS
    (
        SELECT
            account_id,
            COUNT(*) account_entries
        FROM account_entries 
        WHERE   narrative_type = 'BalanceUpdate'
            AND CAST(created_at AS DATE) BETWEEN '2018-12-01' AND '2018-12-31'
        GROUP BY 
            account_id   
    ),
cte_users AS 
    (
        SELECT
            registered_by,
            COUNT(*) users
        FROM users 
        WHERE   CAST(created_at AS DATE) BETWEEN '2018-12-01' AND '2018-12-31'
        GROUP BY 
            registered_by   
    )
SELECT
    A.id account_id,
    A.account_type,
    ISNULL(B.account_entries, 0) counter1,
    ISNULL(C.users, 0) counter2,
    ISNULL(B.account_entries, 0) + ISNULL(C.users, 0) [counter1+counter2]
FROM accounts A 
LEFT JOIN cte_account_entries B
ON      A.id = B.account_id
LEFT JOIN cte_users C 
ON      A.id = C.registered_by
WHERE   A.account_type = 'Agent'

cte_account_entries是第一个公用表表达式,它按帐户计算帐户条目的数量,从而实现问题中指出的过滤器。请注意,如果列中同时包含日期和时间,我会进行CAST(... AS DATE)

cte_users与之类似,但与用户表有关。

最后,它们全部合并到最后的SELECT语句中,过滤到仅“代理”帐户类型,并且LEFT JOIN s加入到CTE,每个帐户仅产生一条记录,因此不会有笛卡尔积。

ISNULL在这里也非常有帮助。例如,如果没有一个帐户的帐户条目,但是有12个用户,那么您可能最终尝试将它们加在一起,例如NULL + 12,这将产生NULL。 ISNULL会将那个NULL转换为0,所以您得到0 + 12。