SQL SUM statements returning different results - sql

Can somebody explain why the two select statement results below are different!
I know the first statement is correct (using CASE) but I can't understand why the second statement is wrong.
CREATE TABLE #sales
(
YearSold int,
Quarter char(2),
Amount money
)
GO
INSERT INTO #sales (YearSold, Quarter, Amount) values (2003, 'Q1', 1)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2003, 'Q2', 2)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2003, 'Q3', 3)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2003, 'Q4', 4)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2004, 'Q1', 5)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2004, 'Q2', 6)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2004, 'Q3', 7)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2004, 'Q4', 8)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2005, 'Q1', 9)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2005, 'Q2', 10)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2005, 'Q3', 0)
INSERT INTO #sales (YearSold, Quarter, Amount) values (2005, 'Q4', 0)
GO
SELECT YearSold,
SUM(CASE Quarter WHEN 'Q1' THEN Amount ELSE 0 END) AS Q1,
SUM(CASE Quarter WHEN 'Q2' THEN Amount ELSE 0 END) AS Q2,
SUM(CASE Quarter WHEN 'Q3' THEN Amount ELSE 0 END) AS Q3,
SUM(CASE Quarter WHEN 'Q4' THEN Amount ELSE 0 END) AS Q4
FROM #sales
GROUP BY YearSold
SELECT t.YearSold, SUM(a.Amount) AS Q1, SUM(b.Amount) AS Q2, SUM(c.Amount) AS Q3, SUM(d.Amount) AS Q4
FROM #sales t
LEFT JOIN #sales a ON t.YearSold = a.YearSold AND a.Quarter='Q1'
LEFT JOIN (select * from #sales where Quarter='Q2') b ON t.YearSold = b.YearSold
LEFT JOIN (select * from #sales where Quarter='Q3') c ON t.YearSold = c.YearSold
LEFT JOIN (select * from #sales where Quarter='Q4') d ON t.YearSold = d.YearSold
GROUP BY t.YearSold
--select * from #sales
DROP TABLE #sales
Note: Using SQL Server 2008 Express

A left join returns every matching row in the right-hand table for each row in the left-hand table.
To see what happens, remove the group by from your second query.
SELECT *
FROM #sales t
LEFT JOIN #sales a ON t.YearSold = a.YearSold AND a.Quarter='Q1'
LEFT JOIN (select * from #sales where Quarter='Q2') b ON t.YearSold = b.YearSold
LEFT JOIN (select * from #sales where Quarter='Q3') c ON t.YearSold = c.YearSold
LEFT JOIN (select * from #sales where Quarter='Q4') d ON t.YearSold = d.YearSold
You'll see there are four rows for each year. That's because from #sales will give you four rows for each year. The end result is that the group by counts everything four times.

Related

SQL Pivot Columns with prefixes

New to SQL, struggling to fully understand the pivot clause. I have four fields (state, season, rain, snow) and am trying to pivot so that I have 5 fields (state, summer_rain, summer_snow, winter_rain, winter_snow). I'm not sure how to pivot two fields so that they are prefixed with another if that makes sense. Reprex below.
What I have now
What I'm after
My code (receiving an error when aggregating snow & rain within pivot clause):
DECLARE #myTable AS TABLE([state] VARCHAR(20), [season] VARCHAR(20), [rain] int, [snow] int)
INSERT INTO #myTable VALUES ('AL', 'summer', 1, 1)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 3)
INSERT INTO #myTable VALUES ('AZ', 'summer', 0, 1)
INSERT INTO #myTable VALUES ('AL', 'winter', 5, 4)
INSERT INTO #myTable VALUES ('AK', 'winter', 2, 2)
INSERT INTO #myTable VALUES ('AZ', 'winter', 1, 1)
INSERT INTO #myTable VALUES ('AL', 'summer', 6, 4)
INSERT INTO #myTable VALUES ('AK', 'summer', 3, 0)
INSERT INTO #myTable VALUES ('AZ', 'summer', 5, 1)
SELECT [state], [year], [month], [day]
FROM
(
SELECT * FROM #myTable
) t
PIVOT
(
sum([rain]), sum([snow]) FOR [season] IN ([summer], [winter])
) AS pvt
PIVOTS are great, but Conditional Aggregations offer a bit more flexibility and often more performant.
PIVOT
Select *
From (
SELECT State
,B.*
FROM #myTable
Cross Apply (values (concat(season,'_rain'),rain)
,(concat(season,'_snow'),snow)
) B(Item,Value)
) src
Pivot ( sum(value) for Item in ([summer_rain],[summer_snow],[winter_rain],[winter_snow]) ) pvt
Conditional Aggregation
Select State
,[summer_rain] = sum(case when season='summer' then rain end)
,[summer_snow] = sum(case when season='summer' then snow end)
,[winter_rain] = sum(case when season='winter' then rain end)
,[winter_snow] = sum(case when season='winter' then snow end)
From #myTable
Group By State

How to display months sorted in order in SQL Server?

Below is the table I have created and inserted values in it:
CREATE TABLE employees_list
(
employeeID int identity(1,1),
employeeName varchar(25)
)
GO
INSERT INTO employees_list VALUES ('Kevin'),('Charles')
GO
CREATE TABLE hourlyRates
(
employeeID int,
rate int,
rateDate date
)
INSERT INTO hourlyRates VALUES (1, 28, '2016-01-01'),
(1, 39, '2016-02-01'),
(2, 43, '2016-01-01'),
(2, 57, '2016-02-01')
CREATE TABLE workingHours
(
employeeID int,
startdate datetime,
enddate datetime
)
GO
INSERT INTO workingHours VALUES (1, '2016-01-01 09:00', '2016-01-01 17:00'),
(1, '2016-01-02 09:00', '2016-01-02 17:00'),
(1, '2016-02-01 10:00', '2016-02-01 16:00'),
(1, '2016-02-02 11:00', '2016-02-02 13:00'),
(2, '2016-01-01 10:00', '2016-01-01 16:00'),
(2, '2016-01-02 08:00', '2016-01-02 14:00'),
(2, '2016-02-01 14:00', '2016-02-01 19:00'),
(2, '2016-02-02 13:00', '2016-02-02 16:00')
GO
SELECT * FROM employees_list
SELECT * FROM hourlyRates
SELECT * FROM workingHours
Then I ran a query to calculate salaries paid to Employees each month:
SELECT
employeeName,
DATENAME(MONTH, startdate) AS 'Month',
SUM(DATEDIFF(HOUR, startdate, enddate) * rate) AS 'Total Salary'
FROM
hourlyRates, workingHours, employees_list
WHERE
hourlyRates.employeeID = workingHours.employeeID
AND employees_list.employeeID = workingHours.employeeID
AND (hourlyRates.rateDate BETWEEN DATEFROMPARTS(DATEPART(YEAR, workingHours.startDate), DATEPART(MONTH, workingHours.startDate),1)
AND DATEFROMPARTS(DATEPART(YEAR, workingHours.endDate), DATEPART(MONTH, workingHours.endDate),1))
GROUP BY
employeeName, DATENAME(MONTH, startdate)
And I got the following output:
As you can see from the screenshot above that I got the result I wanted.
But the only issue is the month is not being displayed in order.
I tried adding ORDER BY DATENAME(MONTH, startdate) and still the order of month is not being sorted.
I even tried ORDER BY DATEPART(MM, startdate) but it is showing error mentioning that it is not contained in an aggregate function or GROUP BY clause.
What minor change do I need to make in my query ?
Why add ORDER BY DATENAME(MONTH,startdate) not work
Because the ORDER depends on character instead of the month of number.
You can try to add MONTH(startdate) in ORDER BY & GROUP BY, because you might need to add non-aggregate function in GROUP BY
SELECT employeeName,DATENAME(MONTH,startdate) AS 'Month',
SUM(DATEDIFF(HOUR,startdate,enddate) * rate) AS 'Total Salary'
FROM hourlyRates
INNER JOIN workingHours
ON hourlyRates.employeeID = workingHours.employeeID
INNER JOIN employees_list
ON employees_list.employeeID = workingHours.employeeID
WHERE
(hourlyRates.rateDate
BETWEEN DATEFROMPARTS(DATEPART(YEAR, workingHours.startDate), DATEPART(MONTH,workingHours.startDate),1)
AND DATEFROMPARTS(DATEPART(YEAR, workingHours.endDate), DATEPART(MONTH,workingHours.endDate),1))
GROUP BY employeeName,DATENAME(MONTH,startdate),MONTH(startdate)
ORDER BY MONTH(startdate)
sqlfiddle
NOTE
I would use INNER JOIN ANSI syntax instead of , which mean CROSS JOIN because JOIN syntax is generally considered more readable.
As mentioned, ORDER BY DATENAME will sort by the textual name of the month not by the actual ordering of months.
It's best to just group and sort by EOMONTH, then you can pull out the month name from that in the SELECT
Further improvements:
Always use explicit join syntax, not old-style , comma joins.
Give tables short aliases, to make your query more readable.
Your date interval check might not be quite right, and you may need to also adjust the rate caluclation, but I don't know without further info.
A more accurate calculation would probably mean calculating part-dates.
SELECT
e.employeeName,
DATENAME(month, EOMONTH(wh.startdate)) AS Month,
SUM(DATEDIFF(HOUR, wh.startdate, wh.enddate) * hr.rate) AS [Total Salary]
FROM hourlyRates hr
JOIN workingHours wh ON hr.employeeID = wh.employeeID
AND hr.rateDate
BETWEEN DATEFROMPARTS(YEAR(wh.startDate), MONTH(wh.startDate), 1)
AND DATEFROMPARTS(YEAR(wh.endDate), MONTH(wh.endDate), 1)
JOIN employees_list e ON e.employeeID = wh.employeeID
GROUP BY
e.employeeId,
e.employeeName,
EOMONTH(wh.startdate)
ORDER BY
EOMONTH(wh.startdate),
e.employeeName;
db<>fiddle

How to get columns from different unrelated tables having similar column names?

I have 3 tables:
Pay Group:
PayGroupId Name Description Code
1 US Weekly US Weekly USW
2 Can Weekly Canada Weekly CANW
3 US Monthly US Monthly USM
4 Can Monthly Can Monthly CANM
Pay Type:
PayTypeId Name Description Code
1 Hourly Hourly H
2 Salary Salaried S
Pay Code:
PayCodeId Name Description Code
1 Regular Regular REG
2 PTO PTO PTO
3 Sick Sick SICK
I need a report in following format:
PayGroup PayType PayCode
US Weekly Hourly Regular
Can Weekly Salary PTO
US Monthly Sick
Can we do this?
I suspect this gets you the result you are after, but seems like an odd requirement:
WITH PG AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayGroupID ASC) AS RN
FROM PayGroup),
PT AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayTypeID ASC) AS RN
FROM PayGroup),
PC AS(
SELECT [Name],
ROW_NUMBER() OVER (ORDER BY PayCodeID ASC) AS RN
FROM PayCode)
SELECT PG.[Name] AS PayGroup,
PT.[Name] AS PayType,
PC.[Name] AS PayCode
FROM PG
FULL OUTER JOIN PT ON PG.RN = PT.RN
FULL OUTER JOIN PC ON PG.RN = PC.RN
OR PT.RN = PC.RN;
CREATE TABLE #table1
([PayGroupId] int, [Name] varchar(11), [Description] varchar(13), [Code] varchar(4))
;
INSERT INTO #table1
([PayGroupId], [Name], [Description], [Code])
VALUES
(1, 'US Weekly', 'US Weekly', 'USW'),
(2, 'Can Weekly', 'Canada Weekly', 'CANW'),
(3, 'US Monthly', 'US Monthly', 'USM'),
(4, 'Can Monthly', 'Can Monthly', 'CANM')
;
CREATE TABLE #table2
([PayTypeId] int, [Name] varchar(6), [Description] varchar(8), [Code] varchar(1))
;
INSERT INTO #table2
([PayTypeId], [Name], [Description], [Code])
VALUES
(1, 'Hourly', 'Hourly', 'H'),
(2, 'Salary', 'Salaried', 'S')
;
CREATE TABLE #table3
([PayCodeId] int, [Name] varchar(7), [Description] varchar(7), [Code] varchar(4))
;
INSERT INTO #table3
([PayCodeId], [Name], [Description], [Code])
VALUES
(1, 'Regular', 'Regular', 'REG'),
(2, 'PTO', 'PTO', 'PTO'),
(3, 'Sick', 'Sick', 'SICK')
;
select a.name PayGroup ,isnull(B.Name,'') PayType ,isnull(C.Name,'')PayCode
from #table1 A left join #table2 B on a.[PayGroupId]=b.[PayTypeId]left join
#table3 c on c.[PayCodeId]=a.[PayGroupId]
PayGroup PayType PayCode
US Weekly Hourly Regular
Can Weekly Salary PTO
US Monthly Sick
Can Monthly

How to sum up Loss amount per each claim ignoring date

I have table with Loss amount per each transaction date.
How can I create column ClaimLoss that would sum up Loss amount per each claim?
declare #TempTable1 table (ID int, ClaimNumber varchar(100), date date, Loss money)
insert into #TempTable1
values (1, 'Claim1','2017-01-01', 100),
(2, 'Claim1','2017-03-06',150),
(3, 'Claim1','2017-05-01', 50),
(4, 'Claim2','2018-01-01', 150),
(5, 'Claim2','2018-08-15', 250),
(6, 'Claim2','2018-05-03', 350),
(7, 'Claim3','2018-09-01', 330),
(8, 'Claim4','2019-01-01', 140),
(9, 'Claim4','2019-01-13', 225),
(10, 'Claim5','2019-02-01', 145)
select ID,
ClaimNumber,
Date,
Loss
from #TempTable1
I need something like this:
Is it possible to do in the same select statement?
This seems like a place to use row_number() and case:
select t.*,
(case when row_number() over (partition by ClaimNumber order by date) = 1
then sum(loss) over (partition by ClaimNumber)
else 0
end) as claimloss
from #TempTable1 t;
You can use window function:
select ID, ClaimNumber, Date, Loss,
(case when min(id) over (partition by ClaimNumber) = id
then sum(loss) over (partition by ClaimNumber)
else 0
end) as claimloss
from #TempTable1;

SQL use count or sum for specific result

I was trying to get result with native SQL query as it is presented on below picture, currently im not sure if there is any way to get this result with using only SQL.
I was around this query, but no idea currently further:
SELECT
receipts.client_code clientCode,
date_trunc('MON', receipts.create_date) monthYear,
COUNT(date_trunc('MON', receipts.create_date)) receipts,
subReceipts.total total
FROM receipts
LEFT JOIN (SELECT
receipts.client_code clientCode,
date_trunc('MON', receipts.create_date) monthYear,
COUNT(date_trunc('MON', receipts.create_date)) total
FROM receipts
GROUP BY
receipts.client_code,
date_trunc('MON' ,receipts.create_date)
ORDER BY
date_trunc('MON' ,receipts.create_date)
) subReceipts ON subReceipts.clientCode = receipts.client_code
GROUP BY
receipts.client_code,
date_trunc('MON' ,receipts.create_date),
subReceipts.total
ORDER BY
date_trunc('MON' ,receipts.create_date)
Sample sql data and db table create script:
CREATE TABLE receipts
(
receipt_id int primary key,
client_code varchar not null,
create_date date not null
);
insert into receipts (receipt_id, client_code, create_date) values (1, 'fx90', to_date('2016/01/11', 'yyyy/MM/dd'));
insert into receipts (receipt_id, client_code, create_date) values (2, 'fx90', to_date('2016/02/12', 'yyyy/MM/dd'));
insert into receipts (receipt_id, client_code, create_date) values (3, 'fx90', to_date('2016/02/20', 'yyyy/MM/dd'));
insert into receipts (receipt_id, client_code, create_date) values (4, 'fx90', to_date('2016/03/11', 'yyyy/MM/dd'));
insert into receipts (receipt_id, client_code, create_date) values (5, 'fx90', to_date('2016/03/12', 'yyyy/MM/dd'));
insert into receipts (receipt_id, client_code, create_date) values (6, 'fx90', to_date('2016/03/19', 'yyyy/MM/dd'));
Example result
Assuming mysql, you could just do:
set #running_total := 0;
SELECT
client_code,
CONCAT(MONTH(create_date), ' - ', YEAR(create_date)) as month_year,
COUNT(receipt_id) AS receipts_month,
(#running_total := #running_total + COUNT(receipt_id)) as total_receipts
FROM receipts
GROUP BY client_code, MONTH(create_date), YEAR(create_date)
ORDER BY receipt_id;
For postgresql:
SELECT clientCode, monthYear, receipts,
sum(receipts) over(order by monthYear) as total
FROM (
SELECT receipts.client_code clientCode,
date_trunc('MON', receipts.create_date) monthYear,
COUNT(1) receipts
FROM receipts
GROUP BY receipts.client_code, monthYear
) X
ORDER BY monthYear