'SQL Sum over several columns of an entry with ad hoc created columns
I have the following table, like:
| woker | date | amount |
|---|---|---|
| jeff | 04-04-2022 | 4.00 |
| jeff | 04-05-2022 | 2.00 |
| jeff | 04-08-2022 | 3.50 |
| dave | 04-04-2022 | 1.00 |
| dave | 04-07-2022 | 6.50 |
It contains the date and the amount of hours worked by a worker.
Now I want to create a table like the following with a select to show hours per weekday. The column "count" should represent the amount of days where the worker has hours. The column "sum" should sum the hours for this week. So the final result should be like this:
| worker | mon | tue | wed | thu | fri | sat | sun | count | sum |
|---|---|---|---|---|---|---|---|---|---|
| jeff | 4.00 | 2.00 | null | null | 3.50 | null | null | 3 | 9.50 |
| dave | 1.00 | null | null | 6.50 | null | null | null | 2 | 7.50 |
My statement so far is:
SELECT worker,
CASE WHEN DATEPART(weekday,date) = 1 THEN amount END AS mon,
CASE WHEN DATEPART(weekday,date) = 2 THEN amount END AS tue,
CASE WHEN DATEPART(weekday,date) = 3 THEN amount END AS wed,
CASE WHEN DATEPART(weekday,date) = 4 THEN amount END AS thu,
CASE WHEN DATEPART(weekday,date) = 5 THEN amount END AS fri,
CASE WHEN DATEPART(weekday,date) = 6 THEN amount END AS sat,
CASE WHEN DATEPART(weekday,date) = 7 THEN amount END AS sun
FROM table
So now I need help to get the last two columns. Can anybody explain, how to sum up / count values over multiple columns withi one entry?
Thank you.
Solution 1:[1]
We use the function SUM so as to be able to agregate the different lines. We add the week number in the SELECT and the GROUP BY so as to separate the weeks and know which week of the year we are looking at. We could also add the year if the same table will be used for long enough.
SET DATEFIRST 1;
SELECT
DATEPART(week, date) AS "week",
worker,
CASE WHEN DATEPART(weekday,date) = 1 THEN amount END ) AS mon,
SUM( CASE WHEN DATEPART(weekday,date) = 2 THEN amount END ) AS tue,
SUM( CASE WHEN DATEPART(weekday,date) = 3 THEN amount END ) AS wed,
SUM( CASE WHEN DATEPART(weekday,date) = 4 THEN amount END ) AS thu,
SUM( CASE WHEN DATEPART(weekday,date) = 5 THEN amount END ) AS fri,
SUM( CASE WHEN DATEPART(weekday,date) = 6 THEN amount END ) AS sat,
SUM( CASE WHEN DATEPART(weekday,date) = 7 THEN amount END ) AS sun,
COUNT(DISTINCT DATEPART(weekday,date) ) AS "count",
amount AS "sum"
FROM table
GROUP BY
DATEPART(week, date),
worker
ORDER BY
DATEPART(week, date),
worker;
Solution 2:[2]
Conditional aggregation is an option (don't forget to set the first day of the week using SET DATEFIRST):
SET DATEFIRST 1
SELECT
worker,
SUM(CASE WHEN DATEPART(weekday, date) = 1 THEN amount END) AS mon,
SUM(CASE WHEN DATEPART(weekday, date) = 2 THEN amount END) AS tue,
SUM(CASE WHEN DATEPART(weekday, date) = 3 THEN amount END) AS wed,
SUM(CASE WHEN DATEPART(weekday, date) = 4 THEN amount END) AS thu,
SUM(CASE WHEN DATEPART(weekday, date) = 5 THEN amount END) AS fri,
SUM(CASE WHEN DATEPART(weekday, date) = 6 THEN amount END) AS sat,
SUM(CASE WHEN DATEPART(weekday, date) = 7 THEN amount END) AS sun,
COUNT(DISTINCT DATEPART(weekday, date)) AS [count],
SUM(amount) AS [sum]
FROM (VALUES
('jeff', CONVERT(date, '20220404'), 4.00),
('jeff', CONVERT(date, '20220405'), 2.00),
('jeff', CONVERT(date, '20220408'), 3.50),
('dave', CONVERT(date, '20220404'), 1.00),
('dave', CONVERT(date, '20220407'), 6.50)
) t (worker, date, amount)
GROUP BY worker
ORDER BY worker
Result:
| worker | mon | tue | wed | thu | fri | sat | sun | count | sum |
|---|---|---|---|---|---|---|---|---|---|
| dave | 1.00 | 6.50 | 2 | 7.50 | |||||
| jeff | 4.00 | 2.00 | 3.50 | 3 | 9.50 |
If you want to summarize the input data based on time interval, you need a different statement:
SET DATEFIRST 1
SELECT
worker,
--DATEPART(week, date) AS week,
DATEPART(month, date) AS month,
SUM(CASE WHEN DATEPART(weekday, date) = 1 THEN amount END) AS mon,
SUM(CASE WHEN DATEPART(weekday, date) = 2 THEN amount END) AS tue,
SUM(CASE WHEN DATEPART(weekday, date) = 3 THEN amount END) AS wed,
SUM(CASE WHEN DATEPART(weekday, date) = 4 THEN amount END) AS thu,
SUM(CASE WHEN DATEPART(weekday, date) = 5 THEN amount END) AS fri,
SUM(CASE WHEN DATEPART(weekday, date) = 6 THEN amount END) AS sat,
SUM(CASE WHEN DATEPART(weekday, date) = 7 THEN amount END) AS sun,
COUNT(DISTINCT DATEPART(weekday, date)) AS [count],
SUM(amount) AS [sum]
FROM (VALUES
('jeff', CONVERT(date, '20220404'), 4.00),
('jeff', CONVERT(date, '20220405'), 2.00),
('jeff', CONVERT(date, '20220408'), 3.50),
('dave', CONVERT(date, '20220404'), 1.00),
('dave', CONVERT(date, '20220407'), 6.50)
) t (worker, date, amount)
--GROUP BY worker, DATEPART(week, date)
--ORDER BY worker, DATEPART(week, date)
GROUP BY worker, DATEPART(month, date)
ORDER BY worker, DATEPART(month, date)
Solution 3:[3]
Instead om doing alot aggregates, cases and dateparts I would do a pivot table out of it like below. (Note that my days-weekdays ref may not be the same as yours)
Test data:
declare @tbl table (worker varchar(20), [date] date, amount dec(5,2))
INSERT INTO @tbl
SELECT 'jeff', '04-04-2022', 4.00
UNION SELECT 'jeff', '05-04-2022', 2.00
UNION SELECT 'jeff', '08-04-2022', 3.50
UNION SELECT 'dave', '04-04-2022', 1.00
UNION SELECT 'dave', '07-04-2022', 6.50
The actual code:
SELECT
worker
, [1] as sun
, [2] as mon
, [3] as tue
, [4] as wed
, [5] as thu
, [6] as fri
, [7] as sat
, [count]
, [sum]
FROM (select
worker
, amount
, datepart(weekday, t.[date]) dp
, COUNT(amount) OVER( partition by worker) as [count]
, SUM(amount) OVER( partition by worker) as [sum]
from @tbl t
) p
PIVOT(
SUM(amount)
FOR dp in ([1], [2], [3], [4], [5], [6], [7])
) piv
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | |
| Solution 3 | Soerman |
