在 CTE 中使用 Order By 和 Left or right join

Posted

技术标签:

【中文标题】在 CTE 中使用 Order By 和 Left or right join【英文标题】:using Order By and Left or right join in CTE 【发布时间】:2020-11-14 07:37:01 【问题描述】:

请考虑这张表:

Min            Max            Desc
------------------------------------------------
10             24             Total
10             14             Between 10 and 14
15             19             Between 15 and 19
20             24             Between 20 and 24

还有这个CTE:

with p as
(
   SELECT top 100 percent [min],
                          [max],
                          [Desc]
    FROM   [MyBoundaries]
    ORDER BY rownumber
)
select 
    p.[Desc],
    COUNT(CASE when (tbl.col_2 >= 0 AND tbl.col_2 < 5) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 5 AND tbl.col_2 < 10) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 10 AND tbl.col_2 < 15) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 15 AND tbl.col_2 < 20) THEN 1 END)
from [dbo].[MyTable] AS tbl
   inner join p on tbl.col_1 >= p.min and tbl.col_1 <= p.max
where tbl.[YEAR] = 2020
group by p.[Desc]

我对此CTE 有多个问题:

    [MyBoundaries] 中记录的顺序未维护,我希望结果与表 [MyBoundaries] 的顺序完全一致。

    如果我没有在边界(20, 24) 中记录,那么这个边界不包含在输出中,但我想要:

    Between 20 and 24           0           0           0          0
    

我将 inner join 更改为 left 或 'right` 加入,但输出未更改。

我该如何解决这些问题?

谢谢

【问题讨论】:

表格中的行没有顺序。表操作符没有任何顺序感;所以不清楚你认为如果你有订单你会输入什么。结果集具有(部分)顺序。 Order by without limit/top 不在最外层是无操作的。解释您的期望和期望的原因 - 使用参考权威文档的理由。否则,我们无法解决您的误解,只能重写文档——不知道您在误解什么。另外:错误的代码和没有期望并不能告诉我们您想要什么。 minimal reproducible example 请注意所有建议的解决方案如何在外部查询中包含 order by 子句。这与 phil 的评论直接相关并对其进行了扩展。结果集中的行也没有特定的顺序,除非生成它的查询也有 order by 子句。由于查询计划,GROUP by 子句往往会导致出现顺序 - 但如果没有 ORDER BY 子句,则无法保证。 【参考方案1】:

你可以使用:

select 
    p.[Desc],
    COUNT(CASE when (tbl.col_2 >= 0 AND tbl.col_2 < 5) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 5 AND tbl.col_2 < 10) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 10 AND tbl.col_2 < 15) THEN 1 END),
    COUNT(CASE when (tbl.col_2 >= 15 AND tbl.col_2 < 20) THEN 1 END),
from (SELECT * FROM [dbo].[MyTable] WHERE [YEAR] = 2020) AS tbl -- filter on source
right join [MyBoundaries] p on tbl.col_1 >= p.min and tbl.col_1 <= p.max -- right join
group by p.[Desc]
order by MIN(p.[rownumber]);

【讨论】:

【参考方案2】:

这看起来是横向连接的好地方:

select b.[Desc], t.*
from [MyBoundaries] b
outer apply (
    select 
        sum(case when t.col_2 >=  0 and t.col_2 <  5 then 1 else 0 end),
        sum(case when t.col_2 >=  5 and t.col_2 < 10 then 1 else 0 end),
        sum(case when t.col_2 >= 10 and t.col_2 < 15 then 1 else 0 end),
        sum(case when t.col_2 >= 15 and t.col_2 < 20 then 1 else 0 end)
    from [dbo].[MyTable] t
    where t.col_1 >= b.min and t.col_1 <= b.max and t.year = 2020
) t
order by b.rownumber

【讨论】:

【参考方案3】:

有多种改变可以做你想做的事:

您可以使用left join,但您要保留所有行的表需要放在第一位。 CTE 需要返回 rownumber,因此您可以将其包含在 order by 中。 对另一个表的过滤需要在on 子句中。

所以:

with b as (
      SELECT b.*
      FROM [MyBoundaries] b
      ORDER BY rownumber
     )
select b.[Desc],
       COUNT(CASE when tbl.col_2 >= 0 AND tbl.col_2 < 5 THEN 1 END),
       COUNT(CASE when tbl.col_2 >= 5 AND tbl.col_2 < 10 THEN 1 END),
       COUNT(CASE when tbl.col_2 >= 10 AND tbl.col_2 < 15 THEN 1 END),
       COUNT(CASE when tbl.col_2 >= 15 AND tbl.col_2 < 20 THEN 1 END)
from b left join
     [dbo].[MyTable]  tbl
     on tbl.col_1 >= b.min and tbl.col_1 <= b.max and
        tbl.[YEAR] = 2020
group by b.[Desc] 
order by b.rownumber;

我将 CTE 名称更改为 b,因为这对“边界”更有意义。此外,我建议您避免使用像 descminmax 这样的 SQL 保留字作为列名 - 以及 year 这是一个关键字,将来可能会被保留.

【讨论】:

【参考方案4】:

也许查询看起来像这样

with p_cte as (
    select top 100 percent [min], [max], [Desc]
    from MyBoundaries)
select pc.[Desc]
       sum(case when (t.col_2 >= 0 and t.col_2 < 5) then 1 end) ZeroToFive,
       sum(case when (t.col_2 >= 5 and t.col_2 < 10) then 1 end) FiveToTen,
       sum(case when (t.col_2 >= 10 and t.col_2 < 15) then 1 end) TenToFifteen,
       sum(case when (t.col_2 >= 15 and t.col_2 < 20) then 1 end) FiftennToTwenty
from p_cte pc
     left join [dbo].[MyTable] t on pc.[min] <= t.col_1 
                                    and pc.[max] >= t.col_1
                                    and t.[YEAR] = 2020
group by pc.[Desc]
order by pc.[min], pc.[Desc] desc;

【讨论】:

以上是关于在 CTE 中使用 Order By 和 Left or right join的主要内容,如果未能解决你的问题,请参考以下文章

使用 LEFT JOIN 和 ORDER BY...LIMIT 查询慢,使用 Filesort

使用 LEFT JOIN + ORDER BY 时如何避免 FileSort?

ORDER BY date SQL with LEFT JOIN 用于消息传递

将 ORDER BY 添加到 LEFT OUTER JOIN

Django order_by 导致 LEFT JOIN

在 OVER(PARTITION BY) 中使用 CTE