如何获取不包含在消费表中的日期列表。

huangapple go评论76阅读模式
英文:

How do I obtain a list of dates not included in a consumption table

问题

我有一个包含每个现有“连接”的每日消耗的表格。

```sql
create table dbo.Consumptiondata
(
    ConnectionId nvarchar(125) not null,
    StartDateTime datetime not null,
    EndDateTime datetime not null,
    Value numeric(20, 10) not null
)

我需要编写一个查询,以获取每个连接的没有消耗记录(在表中没有记录的日期)的日期。

我首先编写了一个查询,生成了一个临时表,其中包含给定开始和结束日期之间的所有预期日期:

-- 临时表
declare @DatesRange table (DateValue datetime);

-- 构建日期范围
set @currentDateTime = @startDateTime
while @currentDateTime <= @endDateTime
begin
    insert into @DatesRange values (@currentDateTime);
    set @currentDateTime = dateadd(day, 1, @currentDateTime);
end

然后我尝试通过与消耗表进行连接(左连接或右连接)来完成任务,但由于连接字段,我无法使其正常工作。

然后我修改了我的临时表,为每个连接添加了所需的日期范围:

-- 日期过滤器
declare @startDateTime varchar(100) = '2023-05-01 00:00:00.000';
declare @endDateTime varchar(100) = '2023-06-01 00:00:00.000'
declare @currentDate datetime;

-- 临时表
declare @ExpectedDatesPerConnectionId table (ExternalConnectionId nvarchar(125), ExpectedDate datetime);

set @currentDate = @StartDateTime;
while @currentDate < @EndDateTime 
begin
    insert into @ExpectedDatesPerConnectionId
    (ConnectionId, ExpectedDate)
    (select distinct ConnectionId, @currentDate
    from Consumptiondata)
    
    set @currentDate = dateadd(day, 1, @currentDate)
end

然后我只需执行以下查询,即可获得预期结果:

select b.ConnectionId, b.ExpectedDate
from @ExpectedDatesPerConnectionId b
where b.ExpectedDate not in (select DateConsumption from Consumptiondata where ConnectionId = b.ExternalConnectionId)
order by b.ConnectionId, b.ExpectedDate

问题是?运行时间超过8分钟。

因此,我想知道这里是否有人知道获得相同结果的其他方法?


<details>
<summary>英文:</summary>

I have a table that contains the daily consumptions for each existing &quot;connection&quot;.

create table dbo.Consumptiondata
(
ConnectionId nvarchar(125) not null,
StartDateTime datetime not null,
EndDateTime datetime not null,
Value numeric(20, 10) not null
)


I need to write a query that yields the days without consumptions (which have no records in the table) for each connection.

I started by writing a query generating a temporary table of all expected dates between a given start and end date:

```Sql
--  Temporary tables
declare @DatesRange table (DateValue datetime);

--  Build date range
set @currentDateTime = @startDateTime
while @currentDateTime &lt;= @endDateTime
    begin
        insert into @DatesRange values (@currentDateTime);
        set @currentDateTime = dateadd(day, 1, @currentDateTime);
    end

I tried then to get the job done by linking (left or right join) with the consumption table, but I didn't find a way to make it work due to the connection field.

I then modified my temporary table to add the needed date range for each connection:

-- Date filters
declare @startDateTime varchar(100) = &#39;2023-05-01 00:00:00.000&#39;;
declare @endDateTime varchar(100) = &#39;2023-06-01 00:00:00.000&#39;
declare @currentDate datetime;

--  Temporary tables
declare @ExpectedDatesPerConnectionId table (ExternalConnectionId nvarchar(125), ExpectedDate datetime);

set @currentDate = @StartDateTime;
while @currentDate &lt; @EndDateTime 
begin
    insert into @ExpectedDatesPerConnectionId
    (ConnectionId, ExpectedDate)
    (select distinct ConnectionId, @currentDate
    from Consumptiondata)
    
    set @currentDate = dateadd(day, 1, @currentDate)
end

Then I merely execute the following query, which gives me the expected result:

select b.ConnectionId, b.ExpectedDate
from @ExpectedDatesPerConnectionId b
where b.ExpectedDate not in (select DateConsumption from Consumptiondata where ConnectionId = b.ExternalConnectionId)
order by b.ConnectionId, b.ExpectedDate

The problem? It takes over 8 minutes to run.

So, I'm wondering if anyone here would know of another means of obtaining the same result?

答案1

得分: 1

你可以使用日历列出起始/结束日期之间的所有日期,然后简单地提取在消耗表中未使用的日期。

;with
n as (select ROW_NUMBER() over (order by object_id) -1 n from sys.objects),
cal as (
	select *, dateadd(DAY, n, @startDateTime) theday
	from n
	where n &lt; datediff(DAY, @startDateTime, @endDateTime)
)
select *
from cal c
where not exists (
	select null
	from Consumptiondata d
	where c.theday between d.startDateTime and d.endDateTime
	and ConnectionId = @externalConnectionId
)
英文:

You can use a calendar to list all days between your start/end dates, and then simply extrat days that are not used in your consumption table.

;with
n as (select ROW_NUMBER() over (order by object_id) -1 n from sys.objects),
cal as (
	select *, dateadd(DAY, n, @startDateTime) theday
	from n
	where n &lt; datediff(DAY, @startDateTime, @endDateTime)
)
select *
from cal c
where not exists (
	select null
	from Consumptiondata d
	where c.theday between d.startDateTime and d.endDateTime
	and ConnectionId = @externalConnectionId
)

P.S.
sys.objects is just a quick and simple solution to build the calendar, you can use a tally table or any other way you like

答案2

得分: 0

以下是翻译好的部分:

只需在其中提取所有日期的地方。通常情况下,用于此目的的是使用某种形式的日历表格。我有一个喜欢的函数来处理这个。

然后,它只是一个提取非匹配项的练习:

DECLARE @Table TABLE (Date DATE);
INSERT INTO @Table (Date) VALUES ('2023-06-01'),('2023-06-03'),('2023-06-04'),('2023-06-09'),('2023-06-12'),('2023-06-13'),('2023-06-14'),('2023-06-15'),('2023-06-28'),('2023-06-29');

SELECT c.Date
  FROM Calendar(1,1) c
    LEFT OUTER JOIN @Table t
	  ON c.Date = t.Date
 WHERE c.Month = 6
   AND c.Year = 2023
   AND t.Date IS NULL;

在这里,我们只返回不在 @Table 中的所有行。

如果您需要我提到的函数:

CREATE OR ALTER FUNCTION [dbo].[Calendar] (@StartYear INT, @EndYear INT)
RETURNS @Calender TABLE (
Date DATE, DateEndTime DATETIME2, Year INT, Month INT, Day INT, Quarter INT, WeekNumber INT, MonthName NVARCHAR(20), DayName NVARCHAR(20), WeekStartDate DATE, WeekEndDate DATE, MonthStartDate DATE, MonthEndDate DATE,
 QuarterStartDate DATE, QuarterEndDate DATE, YearStartDate DATE, YearEndDate DATE, WeekStartTime DATETIME2, WeekEndTime DATETIME2, MonthStartTime DATETIME2, MonthEndTime DATETIME2, QuarterStartTime DATETIME2, QuarterEndTime DATETIME2,
 YearStartTime DATETIME2, YearEndTime DATETIME2, IsWeekDay BIT) 
AS
BEGIN

WITH CalendarHistory AS (
SELECT CAST(GETUTCDATE() AS DATE) AS Date
UNION ALL
SELECT DATEADD(DAY,-1,Date)
  FROM CalendarHistory
 WHERE DATEADD(DAY,-1,Date) > DATEADD(DAY,-1,DATEFROMPARTS(DATEPART(YEAR,DATEADD(YEAR,-@StartYear,GETUTCDATE())),1,1))
), CalendarFuture AS (
SELECT CAST(DATEADD(DAY,1,GETUTCDATE()) AS DATE) AS Date
UNION ALL
SELECT DATEADD(DAY,1,Date)
  FROM CalendarFuture
 WHERE DATEADD(DAY,1,Date) < DATEFROMPARTS(DATEPART(YEAR,DATEADD(YEAR,@EndYear,GETUTCDATE())),1,1)
), Calendar AS (
SELECT Date
  FROM CalendarHistory
UNION ALL
SELECT Date
  FROM CalendarFuture
)

INSERT INTO @Calender
SELECT Date, 
       DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,1,Date) AS DATETIME2)) AS DateEndTime,
       DATEPART(YEAR,Date) AS Year, DATEPART(MONTH,Date) AS Month, DATEPART(DAY,Date) AS Day, DATEPART(QUARTER,Date) AS Quarter, DATEPART(WEEK,Date) AS WeekNumber,
	   DATENAME(MONTH,Date) AS MonthName, DATENAME(WEEKDAY,Date) AS DayName,
       DATEADD(DAY,1-DATEPART(WEEKDAY,Date),Date) AS WeekStartDate, DATEADD(DAY,7-DATEPART(WEEKDAY,Date),Date) AS WeekEndDate,
	   DATEADD(DAY,1-DATEPART(DAY,Date),Date) AS MonthStartDate, DATEADD(DAY,-1,DATEADD(MONTH,1,DATEADD(DAY,1-DATEPART(DAY,Date),Date))) AS MonthEndDate,
	   DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)) AS QuarterStartDate, DATEADD(DAY,-1,DATEADD(MONTH,3,DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)))) AS QuarterEndDate,
	   DATEFROMPARTS(DATEPART(YEAR,Date),1,1) AS YearStartDate, DATEADD(DAY,-1,DATEFROMPARTS(DATEPART(YEAR,Date)+1,1,1)) AS YearEndDate,
	   CAST(DATEADD(DAY,1-DATEPART(WEEKDAY,Date),Date) AS DATETIME2) AS WeekStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,1,DATEADD(DAY,7-DATEPART(WEEKDAY,Date),Date)) AS DATETIME2)) AS WeekEndTime,
	   CAST(DATEADD(DAY,1-DATEPART(DAY,Date),Date) AS DATETIME2) AS MonthStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEADD(MONTH,1,DATEADD(DAY,1-DATEPART(DAY,Date),Date))) AS DATETIME2)) AS MonthEndTime,
	   CAST(DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)) AS DATETIME2) AS QuarterStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEADD(MONTH,3,DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)))) AS DATETIME2)) AS QuarterEndTime,
	   CAST(DATEFROMPARTS(DATEPART(YEAR,Date),1,1) AS DATETIME2) AS YearStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEFROMPARTS(DATEPART(YEAR,Date)+1,1,1)) AS DATETIME2)) AS YearEndTime,
	   CASE WHEN DATEPART(WEEKDAY,Date) IN (1,7) THEN 0 ELSE 1 END AS IsWeekDay
  FROM Calendar
 ORDER BY Date
 OPTION (MAXRECURSION 0)
 RETURN
END;

它接受两个整数,表示要向前/向后移动的年数。

英文:

You just need somewhere to pull all the dates from. It's pretty common to use a calendar table of some description for this. I have a favored function to handle that for me.

Then, it just becomes an exercise in pulling the non-matches:

DECLARE @Table TABLE (Date DATE);
INSERT INTO @Table (Date) VALUES (&#39;2023-06-01&#39;),(&#39;2023-06-03&#39;),(&#39;2023-06-04&#39;),(&#39;2023-06-09&#39;),(&#39;2023-06-12&#39;),(&#39;2023-06-13&#39;),(&#39;2023-06-14&#39;),(&#39;2023-06-15&#39;),(&#39;2023-06-28&#39;),(&#39;2023-06-29&#39;);

SELECT c.Date
  FROM Calendar(1,1) c
    LEFT OUTER JOIN @Table t
	  ON c.Date = t.Date
 WHERE c.Month = 6
   AND c.Year = 2023
   AND t.Date IS NULL;

Here we just return all the rows not in the @Table.

If you'd like the function I reference:

CREATE OR ALTER FUNCTION [dbo].[Calendar] (@StartYear INT, @EndYear INT)
RETURNS @Calender TABLE (
Date DATE, DateEndTime DATETIME2, Year INT, Month INT, Day INT, Quarter INT, WeekNumber INT, MonthName NVARCHAR(20), DayName NVARCHAR(20), WeekStartDate DATE, WeekEndDate DATE, MonthStartDate DATE, MonthEndDate DATE,
 QuarterStartDate DATE, QuarterEndDate DATE, YearStartDate DATE, YearEndDate DATE, WeekStartTime DATETIME2, WeekEndTime DATETIME2, MonthStartTime DATETIME2, MonthEndTime DATETIME2, QuarterStartTime DATETIME2, QuarterEndTime DATETIME2,
 YearStartTime DATETIME2, YearEndTime DATETIME2, IsWeekDay BIT) 
AS
BEGIN

WITH CalendarHistory AS (
SELECT CAST(GETUTCDATE() AS DATE) AS Date
UNION ALL
SELECT DATEADD(DAY,-1,Date)
  FROM CalendarHistory
 WHERE DATEADD(DAY,-1,Date) &gt; DATEADD(DAY,-1,DATEFROMPARTS(DATEPART(YEAR,DATEADD(YEAR,-@StartYear,GETUTCDATE())),1,1))
), CalendarFuture AS (
SELECT CAST(DATEADD(DAY,1,GETUTCDATE()) AS DATE) AS Date
UNION ALL
SELECT DATEADD(DAY,1,Date)
  FROM CalendarFuture
 WHERE DATEADD(DAY,1,Date) &lt; DATEFROMPARTS(DATEPART(YEAR,DATEADD(YEAR,@EndYear,GETUTCDATE())),1,1)
), Calendar AS (
SELECT Date
  FROM CalendarHistory
UNION ALL
SELECT Date
  FROM CalendarFuture
)

INSERT INTO @Calender
SELECT Date, 
       DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,1,Date) AS DATETIME2)) AS DateEndTime,
       DATEPART(YEAR,Date) AS Year, DATEPART(MONTH,Date) AS Month, DATEPART(DAY,Date) AS Day, DATEPART(QUARTER,Date) AS Quarter, DATEPART(WEEK,Date) AS WeekNumber,
	   DATENAME(MONTH,Date) AS MonthName, DATENAME(WEEKDAY,Date) AS DayName,
       DATEADD(DAY,1-DATEPART(WEEKDAY,Date),Date) AS WeekStartDate, DATEADD(DAY,7-DATEPART(WEEKDAY,Date),Date) AS WeekEndDate,
	   DATEADD(DAY,1-DATEPART(DAY,Date),Date) AS MonthStartDate, DATEADD(DAY,-1,DATEADD(MONTH,1,DATEADD(DAY,1-DATEPART(DAY,Date),Date))) AS MonthEndDate,
	   DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)) AS QuarterStartDate, DATEADD(DAY,-1,DATEADD(MONTH,3,DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)))) AS QuarterEndDate,
	   DATEFROMPARTS(DATEPART(YEAR,Date),1,1) AS YearStartDate, DATEADD(DAY,-1,DATEFROMPARTS(DATEPART(YEAR,Date)+1,1,1)) AS YearEndDate,
	   CAST(DATEADD(DAY,1-DATEPART(WEEKDAY,Date),Date) AS DATETIME2) AS WeekStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,1,DATEADD(DAY,7-DATEPART(WEEKDAY,Date),Date)) AS DATETIME2)) AS WeekEndTime,
	   CAST(DATEADD(DAY,1-DATEPART(DAY,Date),Date) AS DATETIME2) AS MonthStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEADD(MONTH,1,DATEADD(DAY,1-DATEPART(DAY,Date),Date))) AS DATETIME2)) AS MonthEndTime,
	   CAST(DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)) AS DATETIME2) AS QuarterStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEADD(MONTH,3,DATEADD(MONTH,(((DATEPART(MONTH,Date)-1)/3))*3,DATEFROMPARTS(DATEPART(YEAR,Date),1,1)))) AS DATETIME2)) AS QuarterEndTime,
	   CAST(DATEFROMPARTS(DATEPART(YEAR,Date),1,1) AS DATETIME2) AS YearStartTime, DATEADD(MICROSECOND,-3,CAST(DATEADD(DAY,0,DATEFROMPARTS(DATEPART(YEAR,Date)+1,1,1)) AS DATETIME2)) AS YearEndTime,
	   CASE WHEN DATEPART(WEEKDAY,Date) IN (1,7) THEN 0 ELSE 1 END AS IsWeekDay
  FROM Calendar
 ORDER BY Date
 OPTION (MAXRECURSION 0)
 RETURN
END;

It accepts two ints, representing the number of years back/forward you'd like it to go.

答案3

得分: 0

同我的一位同事一起研究了这个问题,他提出了以下解决方案:

-- 日期筛选器
declare @startDateTime datetime = '2021-08-01 00:00:00.000';
declare @endDateTime datetime = '2021-09-01 00:00:00.000';

drop table if exists #daysInRange;

with daysInRange
as (
select @startDateTime as 'StartDateTime', dateadd(day, 1, @startDateTime) as 'EndDateTime'
union all
select dateadd(day, 1, StartDateTime) as 'StartDateTime',
dateadd(day, 1, EndDateTime) as 'EndDateTime'
from daysInRange
where StartDateTime < @endDateTime
)

select *
into #daysInRange
from daysInRange

select conn.ConnectionId,
r.StartDateTime,
r.EndDateTime
from dbo.ConnectionData conn,
cross apply (
select *
from #daysInRange
) as r
left outer join dbo.ConsumptionData cons on cons.ConnectionId = conn.ConnectionId and cons.StartDateTime = r.StartDateTime and cons.EndDateTime = r.EndDateTime
where conn.STARTDATETIME < @endDateTime
and conn.ENDDATETIME > @startDateTime
and conn.Active = 1
and cons.ConnectionMember is null
group by conn.ConnectionId,
r.StartDateTime,
r.EndDateTime
order by r.StartDateTime
go

我最初在帖子中省略了ConnectionData表,因为在我的查询中它只作为ConsumptionData的附加筛选器。但在这里,它是查询的一个不可或缺的部分,所以这次我将它留了下来。

不管怎样,执行时间从8分钟减少到不到30秒。

英文:

Looked into it with one of my colleagues, and he proposed the following solution:

-- Date filters
declare @startDateTime datetime = &#39;2021-08-01 00:00:00.000&#39;;
declare @endDateTime datetime = &#39;2021-09-01 00:00:00.000&#39;;
drop table if exists #daysInRange;
with daysInRange
as (
select @startDateTime as &#39;StartDateTime&#39;, dateadd(day, 1, @startDateTime) as &#39;EndDateTime&#39;
union all
select dateadd(day, 1, StartDateTime) as &#39;StartDateTime&#39;,
dateadd(day, 1, EndDateTime) as &#39;EndDateTime&#39;
from daysInRange
where StartDateTime &lt; @endDateTime
)
select *
into #daysInRange
from daysInRange
select conn.ConnectionId,
r.StartDateTime,
r.EndDateTime
from dbo.ConnectionData conn,
cross apply (
select *
from #daysInRange
) as r
left outer join dbo.ConsumptionData cons on cons.ConnectionId = conn.ConnectionId and cons.StartDateTime = r.StartDateTime and cons.EndDateTime = r.EndDateTime
where conn.STARTDATETIME &lt; @endDateTime
and conn.ENDDATETIME &gt; @startDateTime
and conn.Active = 1
and cons.ConnectionMember is null
group by conn.ConnectionId,
r.StartDateTime,
r.EndDateTime
order by r.StartDateTime
go

I originally ommitted the ConnectionData table from my posting since in my query it only served as an additional filter on the ConsumptionData. Here it's an integral part of the query so I left it in this time.

Regardless, the execution time dropped from 8 minutes to less than 3O seconds.

huangapple
  • 本文由 发表于 2023年6月29日 21:06:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76581360.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定