如何在 SQL Server 中使用“Except all”?

时间:2021-03-05 13:36:09

标签: sql sql-server tsql

我正在尝试使用此查询语句来排除休息日和持续时间,我们将其称为特定月份日历中的假期。 这是我尝试过的:

select Weekday from Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' 
AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1)) 
except (select dayId as dayId from days) 
--select Date from holiday,Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1)) where Date between startDate and endDate 

这是Get_Calendar_Date

的功能
ALTER FUNCTION [dbo].[Get_Calendar_Date]
(
    @StartDate DATETIME
,   @EndDate DATETIME
)
RETURNS TABLE
AS
 
RETURN
(
    SELECT  Tbl_Obj.RNo
        ,   DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate) AS [Date]
        ,   DATEPART(quarter,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [Quarter]
        ,   DATEPART(dayofyear,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [DayofYear]
        ,   DATEPART(WEEK,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [WeekofYear]
        ,   DATEPART(YEAR,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [Year]
        ,   DATEPART(MONTH,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [Month]
        ,   DATEPART(DAY,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [Day]
        ,   DATEPART(weekday,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [Weekday]
        ,   DATENAME(MONTH,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [MonthName]
        ,   DATENAME(weekday,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)) AS [WeekdayName]
        ,   (RIGHT( REPLICATE('0',(4)) +
                CONVERT([VARCHAR],DATEPART(YEAR,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)),0)
                ,(4)
             )+
             RIGHT( REPLICATE('0',(2)) +
                CONVERT([VARCHAR],DATEPART(MONTH,DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate)),0)
                ,(2)
             )
            ) AS [Vintage]
 
    FROM    ( SELECT ROW_NUMBER() OVER (ORDER BY [object_id]) AS [RNo]
              FROM sys.all_objects WITH (NOLOCK)
            ) Tbl_Obj
          
    WHERE   DATEADD(DAY,Tbl_Obj.RNo-1,@StartDate) <= @EndDate
)
  • 除了休息日(周末),我使用了 except ,但我得到的是这样的:

enter image description here

并且预期的结果应该是这样的,因为我在本月有四个 Friday (dayId=6),我需要使用 6 获取所有 dayId(代表休息日的所有月份的星期五):

异常结果:

Weekday
   6
   6
   6
   6

没有Except的执行结果

    select Weekday from Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' 
AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1))

enter image description here

3 个答案:

答案 0 :(得分:1)

请瞄准未来的 minimal, reproducable example

示例数据

(可能更小。)

create table set1
(
  day int
);
insert into set1 (day) values (1),(2),(3),(1),(2),(3),(4); -- contains 2x 3

create table set2
(
  day int
);
insert into set2 (day) values (1),(2),(1),(2),(4); -- removed 3's

问题再现

select set1.day
from set1
  except
select set2.day
from set2;

结果

day
---
3

只有唯一值会保留在您希望所有出现的位置。

解决方案

not exists

select set1.day
from set1
where not exists ( select 'x'
                   from set2
                   where set2.day = set1.day );

left join

select set1.day
from set1
left join set2
  on set2.day = set1.day
where set2.day is null;

结果

day
---
3
3

Fiddle 查看实际情况。

答案 1 :(得分:1)

您可以使用不存在

select Weekday from Get_Calendar_Date(DATEADD(MONTH, DATEDIFF(MONTH, 0, CAST('2021-01-01' 
AS datetime))+2, 0) ,DATEADD(MONTH, DATEDIFF(MONTH, -1, CAST('2021-01-01' AS datetime))+2, -1))
where  not exists (select 1 from days where dayid=weekday) 

答案 2 :(得分:0)

首先,@Sander 的回答非常好,告诉您您需要了解 IMO。我想更深入地研究这个问题。 EXCEPT 是一个 SET Operator,就像 INTERSECTUNION 一样(UNION ALL 是一个多集运算符 - 不同的主题)。除了 Itzik Ben-Gan 的 T-SQL Fundamentals 书籍之外,几乎没有关于集合运算符主题的文档。请注意这些已发布的示例:TSQLFundamentals20160601

Set Operators 返回一个 Set,根据定义,它是唯一的(不同的)。这就是为什么,在 Sander 的例子中,EXCEPT 返回一个不同的值,而 LEFT JOIN 和 NOT EXISTS 不返回一个不同的集合。 JOIN(INNER、OUTER 和 CROSS)是 表运算符,它们返回一个多重集,也就是“包”——一个非不同的集合。让我们比较 EXCEPT、NOT EXISTS 和 THE LEFT JOIN 的执行计划。

enter image description here

EXCEPT 和 NOT EXISTS 在执行计划中利用 Anti Semi Join 表运算符。反连接是你说“把这里不存在的所有东西都给我”。半连接是从左表(NOT EXISTS 语句之前的表)返回一个不同的集合。LEFT JOIN 使用传统的 JOIN 运算符。

使反半连接计划更好的是它们能够完成工作,同时从 set2 表中检索更少的行。 EXCEPT 解决方案,忽略“估计成本” - EXCEPT 计划是最有效的,因为它通过为 NOT EXIST 检索 20 行与 28 行为 LEFT JOIN 检索 42 行来完成工作。

接下来是 STATISTICS IO(读取次数):

--==== NOT EXISTS
Table 'set2'. Scan count 1, logical reads 7, physical reads 0...
Table 'set1'. Scan count 1, logical reads 1, physical reads 0...

--==== LEFT JOIN
Table 'set2'. Scan count 1, logical reads 7, physical reads 0...
Table 'set1'. Scan count 1, logical reads 1, physical reads 0...

--==== EXCEPT
Table 'set2'. Scan count 1, logical reads 4, physical reads 0...
Table 'set1'. Scan count 1, logical reads 1, physical reads 0...

在幕后,EXCEPT 解决方案正在生成 1/2 的 IO。集合运算符可以很强大。