如果在使用聚合函数时存在重复项,如何仅更新其中的 1 行
How to update only 1 of the rows if there are duplicates while using aggregate functions
我正在尝试创建一个基于 SUM(TotalTime)
增加午餐时间的存储过程。我的困境在于重复的记录。它增加了两次午餐。请参阅下面的屏幕截图。我希望根据总工作时间,每位员工每天只添加一次午餐。这是我目前所拥有的。
CREATE TABLE TimeCards
(
[Counter] [int] IDENTITY(1,1) NOT NULL,
EmployeeID nvarchar(50),
Date DateTime,
Lunch decimal(10,2),
TotalTime decimal(10,2)
)
INSERT INTO TimeCards (EmployeeID, Date, Lunch, TotalTime)
VALUES ('1001', '2021-02-04 00:00:00.000', Null, 8)
, ('136', '2021-02-04 00:00:00.000', Null, 4)
, ('136', '2021-02-04 00:00:00.000', Null, 4)
, ('418', '2021-02-04 00:00:00.000', Null, 5)
, ('418', '2021-02-04 00:00:00.000', Null, 5)
, ('511', '2021-02-04 00:00:00.000', Null, 5)
, ('511', '2021-02-04 00:00:00.000', Null, 6)
UPDATE TimeCards
SET Lunch = CASE
WHEN SUMTotalTime BETWEEN 5.501 AND 11 THEN .5
WHEN SUMTotalTime BETWEEN 11.01 AND 16 THEN 1
WHEN SUMTotalTime >= 16.01 THEN 1.5
END
FROM
(SELECT
tc.EmployeeID, Date, MAX(totaltime) AS totaltime,
SUM(TotalTime) AS SUMTotalTime
FROM
TimeCards tc
GROUP BY
tc.EmployeeID, Date
HAVING
SUM(Totaltime) > 5.5) grouped
WHERE
TimeCards.totaltime = grouped.totaltime
AND TimeCards.Date = grouped.Date
AND TimeCards.EmployeeID = grouped.EmployeeID
AND grouped.SUMTotalTime > 5.5
AND Lunch IS NULL
看来您想要的是可更新的 CTE:
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS TotalHours,
ROW_NUMBER() OVER (PARTITION BY TC.EmployeeID, TC.Date ORDER BY Counter DESC) AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND Lunch IS NULL
AND RN = 1;
如果某人已经可以添加午餐,并且它可能不是“最后”行,您可以检查“最多午餐”是否为 NULL
:
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS TotalHours,
MAX(Lunch) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS MaxLunch,
ROW_NUMBER() OVER (PARTITION BY TC.EmployeeID, TC.Date ORDER BY Counter DESC) AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND MaxLunch IS NULL
AND RN = 1;
只是为了完整性,因为我认为我激怒了 Larnu,他似乎不想接受你的评论 ;)
只需更改他答案的 ROW_NUMBER()
部分,使其在检查 [=14= 之前根据 Lunch 值是否为 NULL 选择“要更新的行” ] 值...
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime)
OVER (PARTITION BY TC.EmployeeID, TC.Date
)
AS TotalHours,
ROW_NUMBER()
OVER (PARTITION BY TC.EmployeeID, TC.Date
ORDER BY CASE WHEN Lunch IS NULL THEN 1 ELSE 0 END, Counter DESC
)
AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND RN = 1;
此外,我强烈建议不要对小数、日期等连续值使用 BETWEEN
。在您的场景中,CASE
将匹配第一个 true 条件,因此您可以这样做。 ..
SET Lunch = CASE WHEN TotalHours > 16.0 THEN 1.5
WHEN TotalHours > 11.0 THEN 1
WHEN TotalHours > 5.5 THEN .5
即使你确实需要范围,我也推荐这个...
SET Lunch = CASE WHEN TotalHours > 5.5 AND TotalHours <= 11.0 THEN .5
WHEN TotalHours > 11.0 AND TotalHours <= 16.0 THEN 1
WHEN TotalHours > 16.0 THEN 1.5
它稍长一些,但更明确、更健壮。
我正在尝试创建一个基于 SUM(TotalTime)
增加午餐时间的存储过程。我的困境在于重复的记录。它增加了两次午餐。请参阅下面的屏幕截图。我希望根据总工作时间,每位员工每天只添加一次午餐。这是我目前所拥有的。
CREATE TABLE TimeCards
(
[Counter] [int] IDENTITY(1,1) NOT NULL,
EmployeeID nvarchar(50),
Date DateTime,
Lunch decimal(10,2),
TotalTime decimal(10,2)
)
INSERT INTO TimeCards (EmployeeID, Date, Lunch, TotalTime)
VALUES ('1001', '2021-02-04 00:00:00.000', Null, 8)
, ('136', '2021-02-04 00:00:00.000', Null, 4)
, ('136', '2021-02-04 00:00:00.000', Null, 4)
, ('418', '2021-02-04 00:00:00.000', Null, 5)
, ('418', '2021-02-04 00:00:00.000', Null, 5)
, ('511', '2021-02-04 00:00:00.000', Null, 5)
, ('511', '2021-02-04 00:00:00.000', Null, 6)
UPDATE TimeCards
SET Lunch = CASE
WHEN SUMTotalTime BETWEEN 5.501 AND 11 THEN .5
WHEN SUMTotalTime BETWEEN 11.01 AND 16 THEN 1
WHEN SUMTotalTime >= 16.01 THEN 1.5
END
FROM
(SELECT
tc.EmployeeID, Date, MAX(totaltime) AS totaltime,
SUM(TotalTime) AS SUMTotalTime
FROM
TimeCards tc
GROUP BY
tc.EmployeeID, Date
HAVING
SUM(Totaltime) > 5.5) grouped
WHERE
TimeCards.totaltime = grouped.totaltime
AND TimeCards.Date = grouped.Date
AND TimeCards.EmployeeID = grouped.EmployeeID
AND grouped.SUMTotalTime > 5.5
AND Lunch IS NULL
看来您想要的是可更新的 CTE:
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS TotalHours,
ROW_NUMBER() OVER (PARTITION BY TC.EmployeeID, TC.Date ORDER BY Counter DESC) AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND Lunch IS NULL
AND RN = 1;
如果某人已经可以添加午餐,并且它可能不是“最后”行,您可以检查“最多午餐”是否为 NULL
:
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS TotalHours,
MAX(Lunch) OVER (PARTITION BY TC.EmployeeID, TC.Date) AS MaxLunch,
ROW_NUMBER() OVER (PARTITION BY TC.EmployeeID, TC.Date ORDER BY Counter DESC) AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND MaxLunch IS NULL
AND RN = 1;
只是为了完整性,因为我认为我激怒了 Larnu,他似乎不想接受你的评论 ;)
只需更改他答案的 ROW_NUMBER()
部分,使其在检查 [=14= 之前根据 Lunch 值是否为 NULL 选择“要更新的行” ] 值...
WITH CTE AS(
SELECT TC.EmployeeID,
TC.[Date], --If this is a date, why is it a datetime?
TC.Lunch,
TC.TotalTime,
SUM(TC.TotalTime)
OVER (PARTITION BY TC.EmployeeID, TC.Date
)
AS TotalHours,
ROW_NUMBER()
OVER (PARTITION BY TC.EmployeeID, TC.Date
ORDER BY CASE WHEN Lunch IS NULL THEN 1 ELSE 0 END, Counter DESC
)
AS RN
FROM dbo.TimeCards TC)
UPDATE CTE
SET Lunch = CASE WHEN TotalHours BETWEEN 5.501 AND 11 THEN .5
WHEN TotalHours BETWEEN 11.01 AND 16 THEN 1
WHEN TotalHours >= 16.01 THEN 1.5
END
WHERE TotalHours > 5.5
AND RN = 1;
此外,我强烈建议不要对小数、日期等连续值使用 BETWEEN
。在您的场景中,CASE
将匹配第一个 true 条件,因此您可以这样做。 ..
SET Lunch = CASE WHEN TotalHours > 16.0 THEN 1.5
WHEN TotalHours > 11.0 THEN 1
WHEN TotalHours > 5.5 THEN .5
即使你确实需要范围,我也推荐这个...
SET Lunch = CASE WHEN TotalHours > 5.5 AND TotalHours <= 11.0 THEN .5
WHEN TotalHours > 11.0 AND TotalHours <= 16.0 THEN 1
WHEN TotalHours > 16.0 THEN 1.5
它稍长一些,但更明确、更健壮。