SQL Server - Counting dates per item and arranging in groups - sql

I have a query that outputs an ID and a Date, such as:
[ID] [DateValue]
A111111 2015-01-01
A111111 2015-01-02
A111111 2015-01-03
A111111 2015-01-04
A222222 2015-01-01
A222222 2015-01-02
A333333 2015-01-01
A333333 2015-01-02
A333333 2015-01-03
From this resultset, I need to determine one, two, and greater than two date groups that groups the count of each ID. For instance, the output needs to be something like the following for the data above:
[OneDay] [TwoDays] [MoreThanTwoDays]
3 3 2
Would I have to pivot this?
Thanks in advance!

CREATE TABLE #test
(
id VARCHAR(100) ,
[DateValue] DATE
);
INSERT INTO #test
VALUES
( 'A111111', '2015-01-01' ),
( 'A111111', '2015-01-02' ),
( 'A111111', '2015-01-03' ),
( 'A111111', '2015-01-04' ),
( 'A222222', '2015-01-01' ),
( 'A222222', '2015-01-02' ),
( 'A333333', '2015-01-01' ),
( 'A333333', '2015-01-02' ),
( 'A333333', '2015-01-03' );
SELECT
SUM(CASE WHEN qty = 1 THEN 1
ELSE 0
END) AS [OneDay] ,
SUM(CASE WHEN qty = 2 THEN 1
ELSE 0
END) AS [TwoDays] ,
SUM(CASE WHEN qty > 2 THEN 1
ELSE 0
END) AS [MoreThanTwoDays]
FROM
(
SELECT
COUNT(*) AS qty
FROM
#test
GROUP BY
[DateValue]
) a;

Related

Is it possible to create counts by date on historic events table?

I have an events table which contains the date of status changes. What I'm trying to achieve is to produce summary counts for each date, however I'm struggling as it is not a straight count by date but instead a count based on the last time the status changed.
The data is as follows:
------------------------------------------
IT_ID NEW_STATUS OLD_STATUS TIMESTAMP
------------------------------------------
100 4 3 06/05/2019
100 3 2 04/05/2019
200 2 1 03/05/2019
100 2 1 02/05/2019
300 2 1 02/05/2019
200 1 - 01/05/2019
100 1 - 01/05/2019
300 1 - 01/05/2019
-------------------------------------------
I've tried grouping, but this hasn't worked due to the above, SQL below for the straight count.
select max(trunc(timestamp)), new_status ,count(new_status)
from status_hist
where trunc(timestamp) >= '01/01/2019'
group by trunc(timestamp), new_status
Ideally I would like the data in the following format, however the key here is to counts against each date. Note, as no status changes took place on the 05/05/19 then it shows the same of the 04/05/19:
---------------------------------------------------------
Date Status 1 Status 2 Status 3 Status 4
---------------------------------------------------------
06/05/2019 0 2 0 1
05/05/2019 0 2 1 0
04/05/2019 0 2 1 0
03/05/2019 0 3 0 0
02/05/2019 1 2 0 0
01/05/2019 3 0 0 0
--------------------------------------------------------
Any help would be gratefully received.
Thanks
I think about handling this problem by getting the status of each person on each date. That requires a cross join to get the person/dates combinations and then some aggregation:
WITH dates as (
SELECT min_dt + LEVEL - 1 AS dt
FROM (SELECT MIN(ts) AS min_dt, MAX(ts) AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
)
SELECT d.dt, i.it_id, max(td.new_status) keep (dense_rank first order by td.ts desc) as status
FROM dates d CROSS JOIN
(SELECT DISTINCT IT_ID FROM test_data) i LEFT JOIN
test_data td
ON td.IT_ID = i.IT_ID AND td.ts <= d.dt
GROUP BY d.dt, i.it_id;
The dates CTE is just calculating all dates. The rest is bringing in the latest status.
This can then be expanded to aggregate (or pivot) the results:
WITH dates as (
SELECT min_dt + LEVEL - 1 AS dt
FROM (SELECT MIN(ts) AS min_dt, MAX(ts) AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
),
di as (
SELECT d.dt, i.it_id, max(td.new_status) keep (dense_rank first order by td.ts desc) as status
FROM dates d CROSS JOIN
(SELECT DISTINCT IT_ID FROM test_data) i LEFT JOIN
test_data td
ON td.IT_ID = i.IT_ID AND td.ts <= d.dt
GROUP BY d.dt, i.it_id
)
select dt,
sum(case when status = 1 then 1 else 0 end) as num_1,
sum(case when status = 2 then 1 else 0 end) as num_2,
sum(case when status = 3 then 1 else 0 end) as num_3,
sum(case when status = 4 then 1 else 0 end) as num_4
from di
group by dt
order by dt desc;
Here is a db<>fiddle.
You can do it using windowed aggregation functions:
Oracle Setup:
CREATE TABLE test_data ( IT_ID, NEW_STATUS, OLD_STATUS, "TIMESTAMP" ) AS
SELECT 100, 4, 3, DATE '2019-05-06' FROM DUAL UNION ALL
SELECT 100, 3, 2, DATE '2019-05-04' FROM DUAL UNION ALL
SELECT 200, 2, 1, DATE '2019-05-03' FROM DUAL UNION ALL
SELECT 100, 2, 1, DATE '2019-05-02' FROM DUAL UNION ALL
SELECT 300, 2, 1, DATE '2019-05-02' FROM DUAL UNION ALL
SELECT 200, 1, NULL, DATE '2019-05-01' FROM DUAL UNION ALL
SELECT 100, 1, NULL, DATE '2019-05-01' FROM DUAL UNION ALL
SELECT 300, 1, NULL, DATE '2019-05-01' FROM DUAL;
Query:
SELECT DISTINCT
dt AS "TIMESTAMP",
COUNT( CASE new_status WHEN 1 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 1 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status1,
COUNT( CASE new_status WHEN 2 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 2 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status2,
COUNT( CASE new_status WHEN 3 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 3 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status3,
COUNT( CASE new_status WHEN 4 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
- COUNT( CASE old_status WHEN 4 THEN IT_ID END ) OVER ( ORDER BY dt RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW )
AS Status4
FROM test_data t
RIGHT OUTER JOIN (
SELECT min_dt + LEVEL - 1 AS dt
FROM ( SELECT MIN("TIMESTAMP") AS min_dt,
MAX("TIMESTAMP") AS max_dt
FROM test_data
)
CONNECT BY min_dt + LEVEL - 1 <= max_dt
) c
ON ( c.dt = t."TIMESTAMP" )
ORDER BY "TIMESTAMP" DESC
Output:
TIMESTAMP | STATUS1 | STATUS2 | STATUS3 | STATUS4
:-------- | ------: | ------: | ------: | ------:
06-MAY-19 | 0 | 2 | 0 | 1
05-MAY-19 | 0 | 2 | 1 | 0
04-MAY-19 | 0 | 2 | 1 | 0
03-MAY-19 | 0 | 3 | 0 | 0
02-MAY-19 | 1 | 2 | 0 | 0
01-MAY-19 | 3 | 0 | 0 | 0
db<>fiddle here
You can use the pivot function of SQL.
I don't have an oracle DB to test this:
declare #dates table(Date timestamp(3), NEW_STATUS number(10))
v_StartDate DATE := (SELECT MIN(timestamp) FROM [test].dbo)
v_EndDate DATE := (SELECT MAX(timestamp) FROM [test].dbo)
insert into #dates
SELECT nbr * INTERVAL '1' DAY(5) - 1 + v_StartDate as 'Date', null as NEW_STATUS
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= v_EndDate - v_StartDate
SELECT timestamp as 'Date', 1 AS 'Status 1', 2 AS 'Status 2', 3 AS 'Status 3', 4 AS 'Status 4'
FROM
(SELECT Date as 'timestamp', NVL(NVL(d.new_status, t.NEW_STATUS),t2.NEW_STATUS) as new_status
FROM #dates d
left outer join Table_test t on d.Date = t.TIMESTAMP
left outer join Table_test t2 on INTERVAL '-1' DAY(5) +d.Date = t2.TIMESTAMP and NVL(d.new_status, t.NEW_STATUS) is null ) p
PIVOT
(
COUNT (new_status)
FOR new_status IN
( 1, 2, 3, 4 )
) AS pvt
ORDER BY pvt.TIMESTAMP desc
My Microsoft SQL Syntax is:
declare #dates table([Date] datetime, [NEW_STATUS] int)
DECLARE #StartDate DATE = (SELECT MIN(timestamp) FROM [test].[dbo].[Table_test])
DECLARE #EndDate DATE = (SELECT MAX(timestamp) FROM [test].[dbo].[Table_test])
insert into #dates
SELECT DATEADD(DAY, nbr - 1, #StartDate) as 'Date', null as NEW_STATUS
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
SELECT timestamp as 'Date', [1] AS 'Status 1', [2] AS 'Status 2', [3] AS 'Status 3', [4] AS 'Status 4'
FROM
(SELECT Date as 'timestamp', ISNULL(ISNULL(d.new_status, t.NEW_STATUS),t2.NEW_STATUS) as new_status
FROM #dates d
left outer join Table_test t on d.Date = t.TIMESTAMP
left outer join Table_test t2 on DATEADD(DAY,-1,d.Date) = t2.TIMESTAMP and ISNULL(d.new_status, t.NEW_STATUS) is null ) p
PIVOT
(
COUNT (new_status)
FOR new_status IN
( [1], [2], [3], [4] )
) AS pvt
ORDER BY pvt.TIMESTAMP desc

Fill up date gap by month

I have table of products and their sales quantity in months.
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-05-01 5
B 2018-08-01 10
B 2018-10-01 12
...
I'd like to first fill in the data gap between each product's min and max dates like below:
Product Month Qty
A 2018-01-01 5
A 2018-02-01 3
A 2018-03-01 0
A 2018-04-01 0
A 2018-05-01 5
B 2018-08-01 10
B 2018-09-01 0
B 2018-10-01 12
...
Then I would need to perform an accumulation of each product's sales quantity by month.
Product Month total_Qty
A 2018-01-01 5
A 2018-02-01 8
A 2018-03-01 8
A 2018-04-01 8
A 2018-05-01 13
B 2018-08-01 10
B 2018-09-01 10
B 2018-10-01 22
...
I fumbled over the "cross join" clause, however it seems to generate some unexpected results for me. Could someone help to give a hint how I can achieve this in SQL?
Thanks a lot in advance.
I think a recursive CTE is a simple way to do this. The code is just:
with cte as (
select product, min(mon) as mon, max(mon) as end_mon
from t
group by product
union all
select product, dateadd(month, 1, mon), end_mon
from cte
where mon < end_mon
)
select cte.product, cte.mon, coalesce(qty, 0) as qty
from cte left join
t
on t.product = cte.product and t.mon = cte.mon;
Here is a db<>fiddle.
Hi i think this example can help you and perform what you excepted :
CREATE TABLE #MyTable
(Product varchar(10),
ProductMonth DATETIME,
Qty int
);
GO
CREATE TABLE #MyTableTempDate
(
FullMonth DATETIME
);
GO
INSERT INTO #MyTable
SELECT 'A', '2019-01-01', 214
UNION
SELECT 'A', '2019-02-01', 4
UNION
SELECT 'A', '2019-03-01', 50
UNION
SELECT 'B', '2019-01-01', 214
UNION
SELECT 'B', '2019-02-01', 10
UNION
SELECT 'C', '2019-04-01', 150
INSERT INTO #MyTableTempDate
SELECT '2019-01-01'
UNION
SELECT '2019-02-01'
UNION
SELECT '2019-03-01'
UNION
SELECT '2019-04-01'
UNION
SELECT '2019-05-01'
UNION
SELECT '2019-06-01'
UNION
SELECT '2019-07-01';
------------- FOR NEWER SQL SERVER VERSION > 2005
WITH MyCTE AS
(
SELECT T.Product, T.ProductMonth AS 'MMonth', T.Qty
FROM #MyTable T
UNION
SELECT T.Product, TD.FullMonth AS 'MMonth', 0 AS 'Qty'
FROM #MyTable T, #MyTableTempDate TD
WHERE NOT EXISTS (SELECT 1 FROM #MyTable TT WHERE TT.Product = T.Product AND TD.FullMonth = TT.ProductMonth)
)
-- SELECT * FROM MyCTE;
SELECT Product, MMonth, Qty, SUM( Qty) OVER(PARTITION BY Product ORDER BY Product
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'TotalQty'
FROM MyCTE
ORDER BY Product, MMonth ASC;
DROP TABLE #MyTable
DROP TABLE #MyTableTempDate
I have other way to perform this in lower SQL Server Version (like 2005 and lower)
It's a SELECT on SELECT if it's your case let me know and i provide some other example.
You can create the months with a recursive CTE
DECLARE #MyTable TABLE
(
ProductID CHAR(1),
Date DATE,
Amount INT
)
INSERT INTO #MyTable
VALUES
('A','2018-01-01', 5),
('A','2018-02-01', 3),
('A','2018-05-01', 5),
('B','2018-08-01', 10),
('B','2018-10-01', 12)
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT #StartDate = MIN(Date), #EndDate = MAX(Date) FROM #MyTable
;WITH dates AS (
SELECT #StartDate AS Date
UNION ALL
SELECT DATEADD(Month, 1, Date)
FROM dates
WHERE Date < #EndDate
)
SELECT A.ProductID, d.Date, COALESCE(Amount,0) AS Amount, COALESCE(SUM(Amount) OVER(PARTITION BY A.ProductID ORDER BY A.ProductID, d.Date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),0) AS Total
FROM
(
SELECT ProductID, MIN(date) as DateStart, MAX(date) as DateEnd
FROM #MyTable
GROUP BY ProductID -- As I read in your comments that you need different min and max dates per product
) A
JOIN dates d ON d.Date >= A.DateStart AND d.Date <= A.DateEnd
LEFT JOIN #MyTable T ON A.ProductID = T.ProductID AND T.Date = d.Date
ORDER BY A.ProductID, d.Date
Try this below
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
;WITH CTE(Product,[Month],Qty)
AS
(
SELECT 'A','2018-01-01', 5 UNION ALL
SELECT 'A','2018-02-01', 3 UNION ALL
SELECT 'A','2018-05-01', 5 UNION ALL
SELECT 'B','2018-08-01', 10 UNION ALL
SELECT 'D','2018-10-01', 12
)
SELECT ct.Product,[MonthDays],ct.Qty
INTO #Temp
FROM
(
SELECT c.Product,[Month],
ISNULL(Qty,0) AS Qty
FROM CTE c
)ct
RIGHT JOIN
(
SELECT -- This code is to get month data
CONVERT(VARCHAR(10),'2018-'+ RIGHT('00'+CAST(MONTH(DATEADD(MM, s.number, CONVERT(DATETIME, 0)))AS VARCHAR),2) +'-01',120) AS [MonthDays]
FROM master.dbo.spt_values s
WHERE [type] = 'P' AND s.number BETWEEN 0 AND 11
)DT
ON dt.[MonthDays] = ct.[Month]
SELECT
MAX(Product)OVER(ORDER BY [MonthDays])AS Product,
[MonthDays],
ISNULL(Qty,0) Qty,
SUM(ISNULL(Qty,0))OVER(ORDER BY [MonthDays]) As SumQty
FROM #Temp
Result
Product MonthDays Qty SumQty
------------------------------
A 2018-01-01 5 5
A 2018-02-01 3 8
A 2018-03-01 0 8
A 2018-04-01 0 8
A 2018-05-01 5 13
A 2018-06-01 0 13
A 2018-07-01 0 13
B 2018-08-01 10 23
B 2018-09-01 0 23
D 2018-10-01 12 35
D 2018-11-01 0 35
D 2018-12-01 0 35
First of all, i would divide month and year to get easier with statistics.
I will give you an example query, not based on your table but still helpful.
--here i create the table that will be used as calendar
Create Table MA_MonthYears (
Month int not null ,
year int not null
PRIMARY KEY ( month, year) )
--/////////////////
-- here i'm creating a procedure to fill the ma_monthyears table
declare #month as int
declare #year as int
set #month = 1
set #year = 2015
while ( #year != 2099 )
begin
insert into MA_MonthYears(Month, year)
select #month, #year
if #month < 12
set #month=#month+1
else
set #month=1
if #month = 1
set #year = #year + 1
end
--/////////////////
--here you are the possible result you are looking for
select SUM(Ma_saledocdetail.taxableamount) as Sold, MA_MonthYears.month , MA_MonthYears.year , item
from MA_MonthYears left outer join MA_SaleDocDetail on year(MA_SaleDocDetail.DocumentDate) = MA_MonthYears.year
and Month(ma_saledocdetail.documentdate) = MA_MonthYears.Month
group by MA_SaleDocDetail.Item, MA_MonthYears.year , MA_MonthYears.month
order by MA_MonthYears.year , MA_MonthYears.month

sql server allocate payment to items

I have been scratching my head with this one for an hour still cannot seem to figure out a way to allocate Payment amount of $30 to the rows in the following table.
Given that i have the following items. Negative amount means the customer is in debt and owes us that amount. Now given that customer pays $30. We need to allocate that to the item.
ItemId amount sDATE
BD98E890-C7F8-47F4-9125-A68A88DD178D -10 2016-01-04 00:00:00.000
7E047DE6-0DB7-4EDB-A751-C43BBD4610E5 -20 2016-01-05 00:00:00.000
5004AE1F-2A15-47E5-96FF-69A6C7D35521 -10 2016-01-06 00:00:00.000
for a payment of $30 the output should look like.
itemId BeforeAllocation AfterAllocation LeftToAllocate sDate
BD98E890-C7F8-47F4-9125-A68A88DD178D -10 0 30 2016-01-04 00:00:00.000
7E047DE6-0DB7-4EDB-A751-C43BBD4610E5 -20 0 20 2016-01-05 00:00:00.000
5004AE1F-2A15-47E5-96FF-69A6C7D35521 -10 -10 0 2016-01-06 00:00:00.000
and if customer is paying partial amount for exmaple $25 the output should be.
itemId BeforeAllocation AfterAllocation LeftToAllocate sDate
BD98E890-C7F8-47F4-9125-A68A88DD178D -10 0 25 2016-01-04 00:00:00.000
7E047DE6-0DB7-4EDB-A751-C43BBD4610E5 -20 -5 15 2016-01-05 00:00:00.000
5004AE1F-2A15-47E5-96FF-69A6C7D35521 -10 -10 0 2016-01-06 00:00:00.000
Code:
Create table #temp(ItemId UNIQUEIDENTIFIER , amount INT, sDATE DATETIME)
INSERT INTO #temp
( ItemId,
amount,
sDATE )
VALUES ( NEWID(),-10,'2016-01-04' ),
( NEWID(),-20,'2016-01-05' ),
( NEWID(),-10,'2016-01-06' )
SELECT * FROM (
SELECT 'BD98E890-C7F8-47F4-9125-A68A88DD178D' itemId, -10 BeforeAllocation, 0 AfterAllocation, 30 LeftToAllocate, '2016-01-04 00:00:00.000' sDate
UNION
SELECT '7E047DE6-0DB7-4EDB-A751-C43BBD4610E5' itemId, -20 BeforeAllocation, 0 AfterAllocation, 20 LeftToAllocate, '2016-01-05 00:00:00.000' sDate
UNION
SELECT '5004AE1F-2A15-47E5-96FF-69A6C7D35521' itemId, -10 BeforeAllocation, -10 AfterAllocation,0 LeftToAllocate, '2016-01-06 00:00:00.000' sDate
)s
ORDER BY sdate
SELECT * FROM (
SELECT 'BD98E890-C7F8-47F4-9125-A68A88DD178D' itemId, -10 BeforeAllocation, 0 AfterAllocation, 25 LeftToAllocate, '2016-01-04 00:00:00.000' sDate
UNION
SELECT '7E047DE6-0DB7-4EDB-A751-C43BBD4610E5' itemId, -20 BeforeAllocation, -5 AfterAllocation, 15 LeftToAllocate, '2016-01-05 00:00:00.000' sDate
UNION
SELECT '5004AE1F-2A15-47E5-96FF-69A6C7D35521' itemId, -10 BeforeAllocation, -10 AfterAllocation,0 LeftToAllocate, '2016-01-06 00:00:00.000' sDate
)s
ORDER BY sdate
Calculate an expression BeforeAllocationRT, to record the running total of BeforeAllocation (i.e. the sum of BeforeAllocation for this row and all preceding rows). If you're using SQL Server 2012 or later you can use window functions, otherwise you need a clumsy subexpression - see this question for exact instructions.
Calculate an expression for the amount to allocate
SELECT Allocation = CASE
WHEN BeforeAllocationRT + #Payment > 0
-- pay full amount for this item
THEN -#BeforeAllocation
WHEN BeforeAllocationRT + #Payment > #BeforeAllocation
-- pay partial amount for this item
THEN -(#BeforeAllocationRT+#Payment)
ELSE
-- pay nothing for this item
0
END
, ...
Calculate expressions for AfterAllocation and LeftToAllocate.
SELECT
AfterAllocation = BeforeAllocation + Allocation
,LeftToAllocate = CASE WHEN BeforeAllocationRT+#Payment>0 THEN #Payment-BeforeAllocationRT ELSE 0 END
,...
Combine steps 1,2,3 using CTEs or subexpressions.
Disclaimer: I don't have access to a SQL Server instance right now, so none of this is tested.
try this,
DECLARE #PaidAmout INT = 30
;WITH CTE AS
(
SELECT ItemId, amount, sDATE, ROW_NUMBER() OVER(ORDER BY sDATE) AS RowNo
FROM #temp
),
Amount AS
(
SELECT ItemId,
RowNo,
amount AS BeforeAllocation,
0 AS AfterAllocation,
#PaidAmout AS LeftToAllocate,
sDATE,
#PaidAmout + Amount AS LeftAmount
FROM CTE
WHERE RowNo = 1
UNION ALL
SELECT c.ItemId,
c.RowNo,
c.amount AS BeforeAllocation,
CASE WHEN a.LeftAmount + c.Amount < 0 THEN a.LeftAmount + c.Amount ELSE 0 END AS AfterAllocation,
CASE WHEN a.LeftAmount < 0 THEN 0 ELSE a.LeftAmount END AS LeftToAllocate,
c.sDATE,
a.LeftAmount + c.Amount AS LeftAmount
FROM CTE c
INNER JOIN Amount a ON a.RowNo + 1 = c.RowNo
)
SELECT ItemId,
BeforeAllocation,
AfterAllocation,
LeftToAllocate,
sDATE
FROM Amount

SQL Server query return next date with an event

Basic SQL question but I have a mind blank. I have a table with the following setup:
date eventType
-----------------------
01/01/2016 0
02/01/2016 0
03/01/2016 2
03/01/2016 2
04/01/2016 6
04/01/2016 6
04/01/2016 6
04/01/2016 6
05/01/2016 0
06/01/2016 ...
I want to return the "next set of events where eventType<>0"
So, if "today" was 01/01/2016, the query would return:
03/01/2016 2
03/01/2016 2
If "today" was 03/01/2016, the query would return:
04/01/2016 6
04/01/2016 6
04/01/2016 6
04/01/2016 6
Etc.
Many thanks
Hmmm. I think this may be a bit trickier than it seems. This does what you want for the data in the question:
select e.*
from events e cross join
(select top 1 eventType
from events
where date > getdate() and eventType <> 0
order by date
) as nexte
where e.date > getdate() and
e.eventType = nexte.eventType;
Or, perhaps a better fit:
select e.*
from events e cross join
(select top (1) e.*
from events
where date > getdate() and eventType <> 0
order by date
) as nexte
where e.date > nexte.date and
e.eventType = nexte.eventType;
Or, more simply:
select top (1) with ties e.*
from events e
where date > getdate() and eventType <> 0
order by date, eventType
I have a different solution, check this:
DECLARE #dtEventType DATE = '20160101'
DECLARE #table TABLE ( cDate DATE , eventType TINYINT )
INSERT INTO #table
VALUES( '20160101' , 0 )
, ( '20160102' , 0 )
, ( '20160103' , 2 )
, ( '20160103' , 2 )
, ( '20160104' , 6 )
, ( '20160104' , 6 )
, ( '20160104' , 6 )
, ( '20160104' , 6 )
, ( '20160105' , 0 )
SELECT *
FROM #table L
WHERE cDate = (
SELECT MIN( cDate ) AS mnDate
FROM #table
WHERE eventType <> 0
AND cDate > #dtEventType
)
But I liked the #GordonLiff's 3rd solution .
Maybe this will work:
SELECT eventDate, event
FROM events
WHERE eventDayte > GETDATE()+1 -- limit here to datePart date to avoid confusion with time as this can lead to issues
-- we should provide limit here to avoid return all future events
AND eventDate <= GETDATE()+2
AND eventType<>0

Work out how many days it took from one status to another : SQL

Please feast your eyes on this current structure of our DB.
Our DBA is currently away for the next two weeks, I have very limited SQL knowledge, I like to stay with the UI and middle tier.
What we are trying to figure out is how can we do the following, we need to write a query to calculate the average period (in days) all commissions have taken to transition from ‘Verified’ to ‘Paid’ for a single dealer, currently the status are
Created
Verified
Rejected
Awaiting Payment
Paid
Refunded
I think this query needs to aim directly at the Commission History Table?
I'm not sure how I would go about writing such query due to the fact my knowledge on SQL is limited...
Any help would be great.
Here's a method to achieve what you're after, although it might not be the most efficient. It seems to me that it's more of a one off query you are looking to run, rather than something that you're going to run on a frequent enough to impact database performance.
Test Table Setup:
CREATE TABLE Commission
(
CommissionId INT,
DealerId INT
)
CREATE TABLE CommissionHistory
(
CommissionId INT,
ActionDate DATETIME,
NewPaymentStatusId INT
)
Insert Dummy Data - 5 Commissions for 1 Dealer:
INSERT INTO dbo.Commission
( CommissionId ,
DealerId
)
VALUES ( 1 , 1 ),
( 2 , 1 ),
( 3 , 1 ),
( 4 , 1 ),
( 5 , 1 ),
INSERT INTO dbo.CommissionHistory
( CommissionId ,
ActionDate ,
NewPaymentStatusId
)
VALUES ( 1 , GETDATE() -25, 1 ),
( 1 , GETDATE() -21, 2 ),
( 1 , GETDATE() -18, 3 ),
( 1 , GETDATE() -16, 4 ),
( 1 , GETDATE() -5, 5 ),
( 2 , GETDATE() -10, 1 ),
( 2 , GETDATE() -9, 2 ),
( 2 , GETDATE() -8, 3 ),
( 2 , GETDATE() -7, 4 ),
( 2 , GETDATE() -6, 5 ),
( 3 , GETDATE() -10, 1 ),
( 3 , GETDATE() -8, 2 ),
( 3 , GETDATE() -6, 3 ),
( 3 , GETDATE() -4, 4 ),
( 3 , GETDATE() -2, 5 ),
( 3 , GETDATE() -25, 6 ),
( 4 , GETDATE() -10, 1 ),
( 4 , GETDATE() -7, 2 ),
( 4 , GETDATE() -6, 3 ),
( 4 , GETDATE() -4, 4 ),
( 4 , GETDATE() -1, 5 ),
( 5 , GETDATE() -1, 1 ),
( 5 , GETDATE() -1, 2 )
So with the dummy data, Commissions 1, 2 &, 4 are classified as valid records as they have status 2 and 5. 3 is excluded as it is refunded and 5 is excluded as it's not paid.
To generate the averages I wrote the below query:
-- set the required dealer id
DECLARE #DealerId INT = 1
-- return all CommissionId's in to a temp table that have statuses 2 and 5, but not 6
SELECT DISTINCT CommissionId
INTO #DealerCommissions
FROM dbo.CommissionHistory t1
WHERE CommissionId IN (SELECT CommissionId
FROM dbo.Commission
WHERE DealerId = #DealerId)
AND NOT EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 6 AND t2.CommissionId = t1.CommissionId)
AND EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 2 AND t2.CommissionId = t1.CommissionId)
AND EXISTS (SELECT CommissionId
FROM dbo.CommissionHistory t2
WHERE t2.NewPaymentStatusId = 5 AND t2.CommissionId = t1.CommissionId)
-- use the temp table to return average difference between the MIN & MAX date
;WITH cte AS (
SELECT CommissionId FROM #DealerCommissions
)
SELECT AVG(CAST(DaysToCompletion AS DECIMAL(10,8)))
FROM (
SELECT DATEDIFF(DAY, MIN(ch.ActionDate), MAX(ch.ActionDate)) DaysToCompletion
FROM cte
INNER JOIN dbo.CommissionHistory ch ON ch.CommissionId = cte.CommissionId
GROUP BY ch.CommissionId
) AS averageDays
-- remove temp table
DROP TABLE #DealerCommissions
For every commission in history table you could get the max verified date and min paid date, assuming paid date always later than verified date. Then you can join commission table to group by dealer id to get the average duration in days.
with comm as(
select
commissionid,
max(case NewPamentStatus when 'Verified' then ActionDate else null end) as verified_date,
min(case NewPamentStatus when 'Paid' then ActionDate else null end) as paid_date
--using max or min just incase that same status will be recorded more than one time.
from
CommissionHistory
group by
commistionid
)
select
c.DealerId,
avg(datediff(day,comm.verified_date,comm.paid_date))
from
comm
inner join
commission c
on c.commissionid = comm.commissionid
where
datediff(day,comm.verified_date,comm.paid_date)>0
-- to get rid off the commissions with paid date before the verified date or in same day
group by
c.DealerId