【问题标题】:SQL Server Query optimization : too many self inner joinSQL Server 查询优化:太多的自内连接
【发布时间】:2016-02-18 17:33:20
【问题描述】:

我目前正在尝试改进 SQL Server 上的 SQL 查询。

我的工作台是这样的:

CAT_HISTORY

DATE        ID          CATEGORY
----------- ----------- -----------
20121201    A           1
20121201    A           1
20121201    B           1
20121201    C           2
20131201    A           2
20131201    B           4
20131201    C           3
20141201    A           3
20141201    B           2
20141201    B           2
20141201    C           1

我的目标是检索他们类别的历史记录。 到目前为止,我正在这样做:

 SELECT   A.DATE
         ,COUNT(DISTINCT A.ID) AS NB_CLIENTS
         ,A.CATEGORY           AS STARTING_CAT
         ,B.CATOGORY           AS ENDING_CAT

FROM CAT_HISTORY A
INNER JOIN CAT_HISTORY B 
ON (
     A.ID= B.ID
 AND
 ( 
    ( 
          A.DATE = 20121201
      AND B.DATE = 20131201 
    )
  OR  
    (
          A.DATE  = 20131201
      AND B.DATE  = 20141201
    )

  WHERE A.DATE>= 20121201 AND B.DATE<= 20141201
  GROUP BY A.DATE, A.CATEGORY,B.CATEGORY
  ORDER BY A.DATE, A.CATEGORY,B.CATEGORY

结果是:

DATE_KEY   STARTING_CAT ENDING_CAT     NB_CLIENTS 
-----------  -----------  -----------  -----------
20121201     1            2            1
20121201     1            4            1
20121201     2            3            1
20131201     2            3            1
20131201     4            2            1
20131201     2            3            1

但问题是我有更多的日期,我为每个日期添加一个 OR(大约 15 个不同的日期),而且我有很多用户。这意味着查询有时需要长达 15 分钟才能获得结果。

我认为我对 INNER JOIN 的处理过于粗暴,并且可能有一种更优雅、更有效的方法来获得预期的结果。

我的最终目标是让 Sankey 看到随着时间的推移从一个类别到另一个类别的演变,我需要从一个类别转移到另一个类别的用户数量。

使用 Gordon Linoff 的回答,效果很好,但会重复计数

SELECT DISTINCT DATE, CATEGORY,NEXT_CATEGORY, COUNT(*) AS NB_CLIENTS
FROM (  
        SELECT DISTINCT CH.*, LEAD(CATEGORY) OVER (PARTITION BY CH.ID ORDER BY DATE) AS NEXT_CATEGORY
        FROM CAT_HISTORY CH 
        ) CH
        WHERE  NEXT_CATEGORY IS NOT NULL
        GROUP BY DATE, CATEGORY,NEXT_CATEGORY

示例: 预计

DATE_KEY     STARTING_CAT ENDING_CAT   NB_CLIENTS 
-----------  -----------  -----------  -----------
20121201     1            2            1
20121201     1            4            1
20121201     2            3            1
20131201     2            3            1
20131201     4            2            1
20131201     2            3            1

使用您的解决方案:

DATE_KEY     STARTING_CAT ENDING_CAT   NB_CLIENTS 
-----------  -----------  -----------  -----------
20121201     1            1            1
20121201     1            2            1
20121201     1            4            1
20121201     2            3            1
20131201     2            3            1
20131201     4            2            1
20131201     2            3            1
20141201     2            2            1

最后编辑:

我设法找到了解决方法:

 SELECT DISTINCT DATE, CATEGORY,NEXT_CATEGORY, COUNT(*) AS NB_CLIENTS
    FROM (  
            SELECT DISTINCT CH.*, LEAD(CATEGORY) OVER (PARTITION BY CH.ID ORDER BY DATE) AS NEXT_CATEGORY
            FROM (SELECT DISTINCT * FROM CAT_HISTORY) CH 
            ) CH
            WHERE  NEXT_CATEGORY IS NOT NULL
            GROUP BY DATE, CATEGORY,NEXT_CATEGORY

【问题讨论】:

  • 我更新了我的答案,你有问题,因为我没有添加带有结构的示例数据。关于您的输出结果,我不明白的一件事是,NB_clients 如何根据您的输入数据获得 5 ,1,1,13 个数据,只需解释一下。所以给出更好的答案。
  • 谢谢你,我成功了。我已经用相应的数据更新了我的答案。

标签: sql sql-server sql-server-2012 query-optimization


【解决方案1】:

如果您想查看成对更改,请使用lead() 而不是固定日期。在 SQL Server 2012+ 中,您可以:

select date, category, next_category, count(*)
from (select ch.*,
             lead(category) over (partition by id order by date) as next_category
      from cat_history ch
     ) ch
group by date, category, next_category;

在 SQL Server 的早期版本中,您可以将类似的逻辑用于关联子查询或 apply

【讨论】:

  • 非常感谢您的回答,它工作得很好,但我现在对工作表有重复的事实有疑问。我已经用详细信息编辑了我的帖子。
  • @AxelR 。 . .你实际上应该问另一个问题,而不是改变一个已经有答案的问题。
  • 糟糕,我不想发垃圾邮件:/
【解决方案2】:

请检查一下,我将date field 替换为datefield

declare @t table(datefield date , id varchar(10) , category int )

insert into @t values
(cast( '20121201' as date) , 'A', 1),
(cast( '20121201' as date) , 'B', 1),
(cast( '20121201' as date) , 'C', 2),
(cast( '20131201' as date) , 'A', 2),
(cast( '20131201' as date) , 'B', 4),
(cast( '20131201' as date) , 'C', 3),
(cast( '20141201' as date) , 'A', 3),
(cast( '20141201' as date) , 'B', 2),
(cast( '20141201' as date) , 'C', 1)

SELECT   A.datefield
         ,COUNT(DISTINCT A.ID) AS NB_CLIENTS
         ,A.CATEGORY           AS STARTING_CAT
         ,isnull(B.CATEGORY ,0)       AS ENDING_CAT
FROM @T A
left JOIN @T B 
ON 
    ( 
        A.ID= B.ID   AND 
        ( b.datefield =  dateadd( yy, 1 , a.datefield ) ) 
    )
 -- WHERE A.datefield>= '20121201' AND ( B.datefield<= '20141201' or B.datefield is null)
  GROUP BY A.datefield, A.CATEGORY,B.CATEGORY
  ORDER BY A.datefield, A.CATEGORY,B.CATEGORY

【讨论】:

  • 感谢您的回答。我在使用它时遇到问题,因为日期字段是我这边的整数,并且查询在执行时返回算术溢出。
  • 为什么要将日期存储为整数,这是一种非常糟糕的做法。
  • 我们正在接收来自不同时区的日期,并且由于我们希望避免日期转换,我们决定将它们的原始值插入为整数。
猜你喜欢
  • 2018-12-27
  • 2017-04-24
  • 2012-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
  • 1970-01-01
相关资源
最近更新 更多