Grouping by id and date, but with date of each group? - sql

I have this query:
declare #values table
(
Id int,
Dept varchar(1),
CounterL int,
CounterU int,
InsertDate datetime
)
insert into #values
select 1, 'L', 5, null, '2017-10-28 4:00:00.000'
union
select 1, 'L', 8, null, '2017-10-28 4:00:00.000'
union
select 1, 'U', null, 30, '2017-10-28 3:00:00.000'
union
select 1, 'U', null, 40, '2017-10-28 3:00:00.000'
select id, sum(counterl), sum(counteru) from #values
where (datepart(hh, insertdate) = 4 or datepart(hh, insertdate) = 3)
group by id, cast(InsertDate as date)
The following returns the sum of both columns, but I would like to be able to include the date of each of these groupings.
The example would look something like this:
id ColumnL, ColumnU, Date ValueU ValueL
1 13 70 2017-10-28 '2017-10-28 3:00:00.000' '2017-10-28 4:00:00.000'
There will always be two hours for the day, either HR 3 or 4.
Thanks.

Isn't this sufficient?
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);
I mean, you can also add the hour:
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte,
dateadd(hour, 3, cast(InsertDate as date)),
dateadd(hour, 4, cast(InsertDate as date))
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);
But that seems unnecessary.
Notice that I replaced the or expressions with a single in. And, I've spelled out hour so the code is easier to read.
EDIT:
Based on your comment, you want conditional aggregation:
select id, sum(counterl), sum(counteru), cast(InsertDate as date) as dte,
min(case when dept = 'L' then InsertDate end) as l_insertdate,
min(case when dept = 'U' then InsertDate end) as u_insertdate
from #values v
where datepart(hour, insertdate) in (3, 4)
group by id, cast(InsertDate as date);

SELECT DISTINCT Id,
SUM(CounterL) OVER(PARTITION BY ID, CAST(InsertDate AS DATE)) AS [ColumnL],
SUM(CounterU) OVER(PARTITION BY ID, CAST(InsertDate AS DATE)) As [ColumnU],
CAST(InsertDate AS DATE) [Date],
DATEADD(HOUR, 3-DATEPART(HOUR, InsertDate), InsertDate) AS [ValueU],
DATEADD(HOUR, 4-DATEPART(HOUR, InsertDate), InsertDate) AS [ValueL]
FROM #values
WHERE DATEPART(HH, INSERTDATE) IN (3,4)

Related

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 select all records which exists in last 6 months?

Can someone suggest me how to select all the records which exists in the last N month?
N would be parameter.
I've written below sub-query to return the desired result. but this is not dynamic since i can not pass N number of month to select last n month records.
SELECT DISTINCT supplier_code
FROM API_StockAndSaleHeader
WHERE supplier_code IN
(SELECT supplier_code
FROM API_StockAndSaleHeader
WHERE dbo.ConvertStringToDate(period_start_date) = '2020-03-01')
AND supplier_code IN
(SELECT supplier_code
FROM API_StockAndSaleHeader
WHERE dbo.ConvertStringToDate(period_start_date) = '2020-02-01')
AND supplier_code IN
(SELECT supplier_code
FROM API_StockAndSaleHeader
WHERE dbo.ConvertStringToDate(period_start_date) = '2020-01-01')
AND supplier_code IN
(SELECT supplier_code
FROM API_StockAndSaleHeader
WHERE dbo.ConvertStringToDate(period_start_date) = '2019-12-01')
AND supplier_code IN
(SELECT supplier_code
FROM API_StockAndSaleHeader
WHERE dbo.ConvertStringToDate(period_start_date) = '2019-11-01')
This will get you the suppliers which have at least one entry for each of the last N months:
declare #N as INT = 6;
WITH CTE AS
(
SELECT DISTINCT supplier_code, CONVERT(varchar(6), CONVERT(date, period_start_date), 112) start_month
FROM API_StockAndSaleHeader
WHERE CONVERT(date, period_start_date) >= DATEADD(month, -#N, CONVERT(date, GETDATE()))
)
SELECT supplier_code
FROM CTE
GROUP BY supplier_code
HAVING COUNT(*) >= #N;
This first gets an auxiliary set of distinct supplier_codes + months, in order to know which suppliers had at least one record in the last 6 months. The trick is to convert the date to varchar and trim it to 6 characters, getting a yyyymm format. Then you just need those with at least N records, which will mean all N months have data. I used >= just to play safe, it depends on how you are dividing months. Every 30/31? 30 days counting from today?
Note I'm using CONVERT to convert to a date instead. BTW, storing dates as other types is a bad thing.
With this data, it will display just Supplier 1:
select 'Supplier 1' supplier_code, '10/10/2019' period_start_date into API_StockAndSaleHeader
union all
select 'Supplier 1', '11/11/2019'
union all
select 'Supplier 1', '12/12/2019'
union all
select 'Supplier 1', '01/01/2020'
union all
select 'Supplier 1', '02/01/2020'
union all
select 'Supplier 1', '03/01/2020'
union all
select 'Supplier 1', '03/15/2020'
union all
select 'Supplier 1', '04/01/2020'
union all
select 'Supplier 1', '05/01/2020'
union all
select 'Supplier 2', '02/01/2020'
union all
select 'Supplier 2', '03/01/2020'
union all
select 'Supplier 2', '03/15/2020'
union all
select 'Supplier 2', '03/22/2020'
union all
select 'Supplier 2', '03/26/2020'
union all
select 'Supplier 2', '04/01/2020'
union all
select 'Supplier 2', '05/01/2020'
Note Supplier 2 has more than 6 entries in the last month, but has no entries for each of the last N months.
CREATE TABLE #API_StockAndSaleHeader
(
supplier_code int,
period_start_date DATETIME
)
INSERT INTO #API_StockAndSaleHeader (supplier_code, period_start_date) VALUES
(1, '2020-05-01'),
(1, '2020-04-01'),
(1, '2020-04-15'), -- This one should not show up.
(2, '2020-05-01'),
(2, '2020-04-01'),
(2, '2020-03-01'), -- This one should show up.
(2, '2020-02-01'),
(2, '2020-01-01')
DECLARE #months int = 3
SELECT supplier_code
FROM
(
SELECT supplier_code, DATEPART(year, period_start_date) as [Year], DATEPART(month, period_start_date) as [month]--DISTINCT supplier_code
FROM #API_StockAndSaleHeader
WHERE DATEADD(MONTH, #months, period_start_date) >= GETDATE()
GROUP BY supplier_code, DATEPART(year, period_start_date), DATEPART(month, period_start_date)
) A
GROUP BY supplier_code
HAVING COUNT(supplier_code) >= #months
IF(OBJECT_ID('tempdb..#API_StockAndSaleHeader') IS NOT NULL)
BEGIN
DROP TABLE #Temp
END

How to use ROW_NUMBER when grouping records?

I have the following:
DECLARE #items TABLE
(
ItemId int NOT NULL,
[Description] varchar(255) NOT NULL,
Amount money NOT NULL
);
INSERT INTO #items SELECT 1, 'A', 10;
INSERT INTO #items SELECT 2, 'A', 10;
INSERT INTO #items SELECT 3, 'B', 11;
INSERT INTO #items SELECT 4, 'B', 11;
INSERT INTO #items SELECT 5, 'B', 11;
INSERT INTO #items SELECT 6, 'C', 12;
INSERT INTO #items SELECT 7, 'C', 12;
INSERT INTO #items SELECT 8, 'A', 10;
INSERT INTO #items SELECT 9, 'A', 10;
SELECT
ROW_NUMBER() OVER(PARTITION BY b.ItemId ORDER BY b.[Description]),
[Description],
COUNT(ItemId) OVER(PARTITION BY b.ItemId),
SUM(Amount) OVER(PARTITION BY b.ItemId)
FROM #items b
The result should be:
1, A, 4, 40
2, B, 3, 33
3, C, 2, 24
However the items are not being grouped.
So how to I need to use ROW_NUMBER to group records?
Is this what you want?
SELECT ROW_NUMBER() OVER (ORDER BY i.Description),
i.Description,
COUNT(*),
SUM(i.Amount)
FROM #items i
GROUP BY Description
ORDER BY Description;
Here is a rextester.
If you don't want use GROUP BY by itself you may do a subquery with two row_number(), something like this:
select ROW_NUMBER() over(order by t.[Description]), t.Description, t.cnt, t.summ
from (
SELECT
ROW_NUMBER() OVER(PARTITION BY b.[Description] ORDER BY b.[Description] ) rn,
[Description],
COUNT(ItemId) OVER(PARTITION BY b.[Description]) cnt,
SUM(Amount) OVER(PARTITION BY b.[Description]) summ
FROM #items b
) t where rn = 1
And anyway you shouldn't group data by the ItemId - it's a wrong way to achieve your aim

Fill exhaustive data history from sparse date-vs-value data in SQL

I have a table with the following information
ID, USER, DATE, AMOUNT
id1, user2, 01/02/2015, 100
id2, user1, 03/02/2015, 200
id3, user2, 06/02/2015, 400
id4, user3, 04/02/2015, 900
what I'd like to obtain is a table listing all dates in the [min_date, max_date] interval and the corresponding values for each user.
USER, user1, user2, user3
01/02/2015, 0, 100, 0
02/02/2015, 0, 0, 0
03/02/2015, 300, 0, 0
04/02/2015, 0, 0, 900
05/02/2015, 0, 0, 0
06/02/2015, 0, 400, 0
I am using the technique suggested in this post to generate a column of dates.
WITH Dates AS (
SELECT
[Date] = CONVERT(DATE,'01/01/2011', 103)
UNION ALL SELECT
[Date] = DATEADD(DAY, 1, [Date])
FROM
Dates
WHERE
Date < CONVERT(date, '30/11/2016', 103)
) SELECT
[Date]
FROM
Dates
OPTION (MAXRECURSION 10000)
--OPTION (MAXRECURSION 45)
Try this:
CREATE TABLE IDS (ID CHAR(100), USERS CHAR(100), DATES CHAR(100), AMOUNT INT)
INSERT INTO IDS VALUES ('id1', 'user2', '01/02/2015', 100)
INSERT INTO IDS VALUES ('id2', 'user1', '03/02/2015', 200)
INSERT INTO IDS VALUES ('id3', 'user2', '06/02/2015', 400)
INSERT INTO IDS VALUES ('id4', 'user3', '04/02/2015', 900)
WITH Dates AS (
SELECT
[Date] = CONVERT(DATE,'02/01/2015', 103)
UNION ALL SELECT
[Date] = DATEADD(MONTH, 1, [Date])
FROM
Dates
WHERE
Date < CONVERT(date, '30/12/2015', 103)
)
SELECT [Date] AS [USER] INTO [DATE] FROM Dates OPTION (MAXRECURSION 1200)
select [DATE].[USER],COALESCE ([user1],0) AS 'user1',COALESCE ([user2],0) AS 'user2',COALESCE ([user3],0) AS 'user3'
from
(
select DATES, USERS, AMOUNT
from IDS
) src
pivot
(
sum(AMOUNT)
for USERS in ([user1], [user2], [user3])
) piv
RIGHT JOIN [DATE] ON [DATE].[USER]=PIV.DATES

Summing up the records as per given conditions

I have a table like below, What I need that for any particular fund and up to any particular date logic will sum the amount value. Let say I need the sum for 3 dates as 01/28/2015,03/30/2015 and 04/01/2015. Then logic will check for up to first date how many records are there in table . If it found more than one record then it'll sum the amount value. Then for next date it'll sum up to the next date but from the previous date it had summed up.
Id Fund Date Amount
1 A 01/20/2015 250
2 A 02/28/2015 300
3 A 03/20/2015 400
4 A 03/30/2015 200
5 B 04/01/2015 500
6 B 04/01/2015 600
I want result to be like below
Id Fund Date SumOfAmount
1 A 02/28/2015 550
2 A 03/30/2015 600
3 B 04/01/2015 1100
Based on your question, it seems that you want to select a set of dates, and then for each fund and selected date, get the sum of the fund amounts from the selected date to the previous selected date. Here is the result set I think you should be expecting:
Fund Date SumOfAmount
A 2015-02-28 550.00
A 2015-03-30 600.00
B 2015-04-01 1100.00
Here is the code to produce this output:
DECLARE #Dates TABLE
(
SelectedDate DATE PRIMARY KEY
)
INSERT INTO #Dates
VALUES
('02/28/2015')
,('03/30/2015')
,('04/01/2015')
DECLARE #FundAmounts TABLE
(
Id INT PRIMARY KEY
,Fund VARCHAR(5)
,Date DATE
,Amount MONEY
);
INSERT INTO #FundAmounts
VALUES
(1, 'A', '01/20/2015', 250)
,(2, 'A', '02/28/2015', 300)
,(3, 'A', '03/20/2015', 400)
,(4, 'A', '03/30/2015', 200)
,(5, 'B', '04/01/2015', 500)
,(6, 'B', '04/01/2015', 600);
SELECT
F.Fund
,D.SelectedDate AS Date
,SUM(F.Amount) AS SumOfAmount
FROM
(
SELECT
SelectedDate
,LAG(SelectedDate,1,'1/1/1900') OVER (ORDER BY SelectedDate ASC) AS PreviousDate
FROM #Dates
) D
JOIN
#FundAmounts F
ON
F.Date BETWEEN DATEADD(DAY,1,D.PreviousDate) AND D.SelectedDate
GROUP BY
D.SelectedDate
,F.Fund
EDIT: Here is alternative to the LAG function for this example:
FROM
(
SELECT
SelectedDate
,ISNULL((SELECT TOP 1 SelectedDate FROM #Dates WHERE SelectedDate < Dates.SelectedDate ORDER BY SelectedDate DESC),'1/1/1900') AS PreviousDate
FROM #Dates Dates
) D
If i change your incorrect sample data to ...
CREATE TABLE TableName
([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
;
INSERT INTO TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '2015-01-28 00:00:00', 250),
(2, 'A', '2015-01-28 00:00:00', 300),
(3, 'A', '2015-03-30 00:00:00', 400),
(4, 'A', '2015-03-30 00:00:00', 200),
(5, 'B', '2015-04-01 00:00:00', 500),
(6, 'B', '2015-04-01 00:00:00', 600)
;
this query using GROUP BY works:
SELECT MIN(Id) AS Id,
MIN(Fund) AS Fund,
[Date],
SUM(Amount) AS SumOfAmount
FROM dbo.TableName t
WHERE [Date] IN ('01/28/2015','03/30/2015','04/01/2015')
GROUP BY [Date]
Demo
Initially i have used Row_number and month function to pick max date of every month and in 2nd cte i did sum of amounts and joined them..may be this result set matches your out put
declare #t table (Id int,Fund Varchar(1),Dated date,amount int)
insert into #t (id,Fund,dated,amount) values (1,'A','01/20/2015',250),
(2,'A','01/28/2015',300),
(3,'A','03/20/2015',400),
(4,'A','03/30/2015',200),
(5,'B','04/01/2015',600),
(6,'B','04/01/2015',500)
;with cte as (
select ID,Fund,Amount,Dated,ROW_NUMBER() OVER
(PARTITION BY DATEDIFF(MONTH, '20000101', dated)ORDER BY dated desc)AS RN from #t
group by ID,Fund,DATED,Amount
),
CTE2 AS
(select SUM(amount)Amt from #t
GROUP BY MONTH(dated))
,CTE3 AS
(Select Amt,ROW_NUMBER()OVER (ORDER BY amt)R from cte2)
,CTE4 AS
(
Select DISTINCT C.ID As ID,
C.Fund As Fund,
C.Dated As Dated
,ROW_NUMBER()OVER (PARTITION BY RN ORDER BY (SELECT NULL))R
from cte C INNER JOIN CTE3 CC ON c.RN = CC.R
Where C.RN = 1
GROUP BY C.ID,C.Fund,C.RN,C.Dated )
select C.R,C.Fund,C.Dated,cc.Amt from CTE4 C INNER JOIN CTE3 CC
ON c.R = cc.R
declare #TableName table([Id] int, [Fund] varchar(1), [Date] datetime, [Amount] int)
declare #Sample table([SampleDate] datetime)
INSERT INTO #TableName
([Id], [Fund], [Date], [Amount])
VALUES
(1, 'A', '20150120 00:00:00', 250),
(2, 'A', '20150128 00:00:00', 300),
(3, 'A', '20150320 00:00:00', 400),
(4, 'A', '20150330 00:00:00', 200),
(5, 'B', '20150401 00:00:00', 500),
(6, 'B', '20150401 00:00:00', 600)
INSERT INTO #Sample ([SampleDate])
values ('20150128 00:00:00'), ('20150330 00:00:00'), ('20150401 00:00:00')
-- select * from #TableName
-- select * from #Sample
;WITH groups AS (
SELECT [Fund], [Date], [AMOUNT], MIN([SampleDate]) [SampleDate] FROM #TableName
JOIN #Sample ON [Date] <= [SampleDate]
GROUP BY [Fund], [Date], [AMOUNT])
SELECT [Fund], [SampleDate], SUM([AMOUNT]) FROM groups
GROUP BY [Fund], [SampleDate]
Explanation:
The CTE groups finds the earliest SampleDate which is later than (or equals to) your
data's date and enriches your data accordingly, thus giving them the group to be summed up in.
After that, you can group on the derived date.