使用现有查询的概念
我做了类似的事情来找出在几年之间购买东西的不同客户的数量,我对其进行了修改以使用您的年份概念,您添加的变量将是一年中的开始日期和开始月份以及开始年份和年底。
从技术上讲,有一种方法可以避免使用循环,但这很清楚,你不能超过 9999 年,所以不要觉得编写巧妙的代码来避免循环是有意义的
加快查询速度的技巧
此外,当匹配日期时,请确保您正在比较日期,而不是比较列的函数评估,因为这意味着在每个记录集上运行该函数,并且如果索引存在于日期上(它们应该存在),则它们将变得无用。使用日期加零来启动您的目标日期,从年份中减去 1900,从月份中减去 1,从目标日期中减去 1。
然后在日期创建有效范围(即 yearlessthan 到 yearmorethan)的表上进行自联接,并使用子查询根据该范围创建总和。由于您希望从第一年到最后一年的累积限制结果从第一年开始。
最后,您将丢失第一年,因为根据我们的定义,它不符合范围的条件,要解决此问题,只需在您创建的临时表上执行 union all 以添加丢失的年份和其中不同值的数量。
DECLARE @yearStartMonth INT = 6, @yearStartDay INT = 1
DECLARE @yearStart INT = 2008, @yearEnd INT = 2012
DECLARE @firstYearStart DATE =
DATEADD(day,@yearStartDay-1,
DATEADD(month, @yearStartMonth-1,
DATEADD(year, @yearStart- 1900,0)))
DECLARE @lastYearEnd DATE =
DATEADD(day, @yearStartDay-2,
DATEADD(month, @yearStartMonth-1,
DATEADD(year, @yearEnd -1900,0)))
DECLARE @firstdayofcurrentyear DATE = @firstYearStart
DECLARE @lastdayofcurrentyear DATE = DATEADD(day,-1,DATEADD(year,1,@firstdayofcurrentyear))
DECLARE @yearnumber INT = YEAR(@firstdayofcurrentyear)
DECLARE @tempTableYearBounds TABLE
(
startDate DATE NOT NULL,
endDate DATE NOT NULL,
YearNumber INT NOT NULL
)
WHILE @firstdayofcurrentyear < @lastYearEnd
BEGIN
INSERT INTO @tempTableYearBounds
VALUES(@firstdayofcurrentyear,@lastdayofcurrentyear,@yearNumber)
SET @firstdayofcurrentyear = DATEADD(year,1,@firstdayofcurrentyear)
SET @lastdayofcurrentyear = DATEADD(year,1,@lastdayofcurrentyear)
SET @yearNumber = @yearNumber + 1
END
DECLARE @tempTableCustomerCount TABLE
(
[Year] INT NOT NULL,
[CustomerCount] INT NOT NULL
)
INSERT INTO @tempTableCustomerCount
SELECT
YearNumber as [Year],
COUNT(DISTINCT CustomerNumber) as CutomerCount
FROM Ticket
JOIN @tempTableYearBounds ON
TicketDate >= startDate AND TicketDate <=endDate
GROUP BY YearNumber
SELECT * FROM(
SELECT t2.Year as [Year],
(SELECT
SUM(CustomerCount)
FROM @tempTableCustomerCount
WHERE Year>=t1.Year
AND Year <=t2.Year) AS CustomerCount
FROM @tempTableCustomerCount t1 JOIN @tempTableCustomerCount t2
ON t1.Year < t2.Year
WHERE t1.Year = @yearStart
UNION
SELECT [Year], [CustomerCount]
FROM @tempTableCustomerCount
WHERE [YEAR] = @yearStart
) tt
ORDER BY tt.Year
它效率不高,但最后您正在处理的临时表是如此之小,我认为这并不重要,并且与您使用的方法相比,它增加了更多的多功能性。
更新:我用我的数据集更新了查询以反映您想要的结果,我基本上是在测试这是否更快,它快了 10 秒,但我正在处理的数据集相对较小。(从 12 秒到 2 秒)。
使用您的数据
我将您提供的表更改为临时表,因此它不会影响我的环境,并且我删除了外键,因为临时表不支持它们,逻辑与包含的示例相同,但只是针对您的数据集进行了更改。
DECLARE @startYear INT = 2013, @endYear INT = 2016
DECLARE @yearStartMonth INT = 10 , @yearStartDay INT = 1
DECLARE @startDate DATETIME = DATEADD(day,@yearStartDay-1,
DATEADD(month, @yearStartMonth-1,
DATEADD(year,@startYear-1900,0)))
DECLARE @endDate DATETIME = DATEADD(day,@yearStartDay-1,
DATEADD(month,@yearStartMonth-1,
DATEADD(year,@endYear-1899,0)))
DECLARE @tempDateRangeTable TABLE
(
[Year] INT NOT NULL,
StartDate DATETIME NOT NULL,
EndDate DATETIME NOT NULL
)
DECLARE @currentDate DATETIME = @startDate
WHILE @currentDate < @endDate
BEGIN
DECLARE @nextDate DATETIME = DATEADD(YEAR, 1, @currentDate)
INSERT INTO @tempDateRangeTable(Year,StartDate,EndDate)
VALUES(YEAR(@currentDate),@currentDate,@nextDate)
SET @currentDate = @nextDate
END
CREATE TABLE Users
(
uID int identity primary key,
uFirstName varchar(75),
uLastName varchar(75)
);
INSERT INTO Users (uFirstName, uLastName)
VALUES
('User1', 'User1'),
('User2', 'User2'),
('User3', 'User3'),
('User4', 'User4');
CREATE TABLE UserDataIDMatch
(
udimID int indentity primary key,
udim.udim_FK_uID int foreign key references Users(uID),
udimUserSystemID varchar(75)
);
INSERT INTO UserDataIDMatch (udim_FK_uID, udimUserSystemID)
VALUES
(1, 'SystemID1'),
(2, 'SystemID2'),
(3, 'SystemID3'),
(4, 'SystemID4');
CREATE TABLE DataDump
(
ddID int identity primary key,
ddSystemID varchar(75),
ddEnd datetime
);
INSERT INTO DataDump (ddSystemID, ddEnd)
VALUES
('SystemID1', '10-01-2013'),
('SystemID2', '10-01-2014'),
('SystemID3', '10-01-2015'),
('SystemID4', '10-01-2016');
DECLARE @tempIndividCount TABLE
(
[Year] INT NOT NULL,
UserCount INT NOT NULL
)
-- no longer need to filter out other because you are using an
--inclusion statement rather than an exclusion one, this will
--also make your query faster (when using real tables not temp ones)
INSERT INTO @tempIndividCount(Year,UserCount)
SELECT tdr.Year, COUNT(DISTINCT UId) FROM
Users u JOIN UserDataIDMatch um
ON um.udim_FK_uID = u.uID
JOIN DataDump dd ON
um.udimUserSystemID = dd.ddSystemID
JOIN @tempDateRangeTable tdr ON
dd.ddEnd >= tdr.StartDate AND dd.ddEnd < tdr.EndDate
GROUP BY tdr.Year
-- will show you your result
SELECT * FROM @tempIndividCount
--add any ranges that did not have an entry but were in your range
--can easily remove this by taking this part out.
INSERT INTO @tempIndividCount
SELECT t1.Year,0 FROM
@tempDateRangeTable t1 LEFT OUTER JOIN @tempIndividCount t2
ON t1.Year = t2.Year
WHERE t2.Year IS NULL
SELECT YearNumber,UserCount FROM (
SELECT 'Year'+CAST(((t2.Year-t1.Year)+1) AS CHAR) [YearNumber] ,t2.Year,(
SELECT SUM(UserCount)
FROM @tempIndividCount
WHERE Year >= t1.Year AND Year <=t2.Year
) AS UserCount
FROM @tempIndividCount t1
JOIN @tempIndividCount t2
ON t1.Year < t2.Year
WHERE t1.Year = @startYear
UNION ALL
--add the missing first year, union it to include the value
SELECT 'Year1',Year, UserCount FROM @tempIndividCount
WHERE Year = @startYear) tt
ORDER BY tt.Year
使用基于 WHEN CASE 的方法的好处
更强大
不需要明确地确定每一年的结束和开始日期,就像在一个逻辑年中只需要知道开始和结束日期。可以通过一些简单的修改轻松更改您正在寻找的内容(即说您想要所有 2 年范围或 3 年)。
如果数据库被正确索引会更快
由于您基于相同的数据类型进行搜索,因此您可以利用应在数据库中的日期列上创建的索引。
缺点
更复杂
查询要复杂得多,尽管它更健壮,但实际查询中有很多额外的逻辑。
在某些情况下不会很好地提高执行时间
如果数据集非常小,或者被比较的日期数量并不重要,那么这将无法节省足够的时间来值得。