Related
I need to calculate a column that has a value column subtracted from a Total column but can skip rows until it can no longer find a smaller value. The sequence relates to dates so the order must be preserved. The value (Need) cannot be larger than the total as those are deleted prior.
This is for SQL Server 2016. My initial thought process was to use window functions and a running total, but I cannot figure out how to skip the 400 and continue to the 2 rows below. I included my attempts in the CASE statement as TransferQty and the running total as ReferenceCol.
Code to reproduce:
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES (500,100)
,(500,200)
,(500,50)
,(500,400)
,(500,50)
,(500,50)
SELECT
sequence
,Total
,Need
,CASE
WHEN Total - SUM(Need) OVER (ORDER BY sequence) > 0
THEN Need
ELSE 0
END AS TransferQty
,Total - SUM(Need) OVER (ORDER BY sequence) as ReferenceCol
FROM #i
Current Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | -250 |
| 5 | 500 | 50 | 0 | -300 |
| 6 | 500 | 50 | 0 | -350 |
+----------+-------+------+-------------+--------------+
Desired Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | 150 | --skip calc
| 5 | 500 | 50 | 50 | 100 |
| 6 | 500 | 50 | 50 | 50 |
+----------+-------+------+-------------+--------------+
You should be able to use this code if you have a single skip, but when you have multi skips then you have to loop through and perform the delete of record based on the existence of rolling value exceeding the total.
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES
(500,100 )
,(500,200 )
,(500,50 )
,(500,400 )
,(500,50 )
,(500,50 )
select sequence,Total,Need
into #temp_original
from #i
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
delete from a
from #i a
join (
select min(sequence) min_sequence
from #temp
where how_much_needed = 0
) minseq
on minseq.min_sequence = a.sequence
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp2
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
select a.sequence,a.Total,a.Need, case when isnull (b.rollingvalue , 0) = 0 then 0 else case when b.rollingvalue > a.Total then 0 else a.Need end end as TransferQty , ISNULL( case when b.how_much_needed = b.Total then a.Need else b.how_much_needed end, case when ( select how_much_needed from #temp2 where sequence = a.sequence -1) = a.Total then 0 else (select how_much_needed from #temp where sequence = a.sequence -1) end ) ReferenceCol
from #temp_original a
LEFT join #temp2 b
on a.sequence = b.sequence
join #temp c
on c.sequence = a.sequence
drop table #temp
drop table #temp2
drop table
Here is the solution I went with which is based off of the "Quirky Update" from the original comment.
DROP TABLE IF EXISTS #i
GO
CREATE TABLE #i
(
sequence INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
,Total INT
,Need INT
,RunningTransfer INT NULL
)
INSERT INTO #i
VALUES
(500,100,NULL)
,(500,200,NULL)
,(500,50,NULL)
,(500,400,NULL)
,(500,50,NULL)
,(500,50,NULL)
,(500,100,NULL)
,(500,49,NULL)
,(500,50,NULL)
DECLARE #TransferRunningTotal INT
UPDATE #i
SET #TransferRunningTotal = RunningTransfer = CASE
--this skips values larger than running total
WHEN #TransferRunningTotal < Need THEN #TransferRunningTotal
--this creates the running total
WHEN #TransferRunningTotal > Need THEN #TransferRunningTotal - Need
--creates the initial value
ELSE Total - Need
END
FROM #i WITH (TABLOCKX)
OPTION (MAXDOP 1)
SELECT sequence
,Total
,Need
,CASE
WHEN need <= RunningTransfer THEN Need
ELSE 0
END AS TsfQty
,RunningTransfer
FROM #i
I have the following table which looks at calls and attendances. I got this by using union all on a 'calls' and 'attendances' tables and then used row number on the ID and ordered by dates.
Table1:
Type | ID | Call/AttendanceDate | RowNum
------------|----|---------------------|--------
Attendance | 12 | 2018-09-16 10:11:00 | 82
Call | 12 | 2018-09-18 14:11:47 | 83
Call | 12 | 2018-10-02 17:26:13 | 84
Call | 12 | 2018-10-05 14:58:31 | 85
Attendance | 12 | 2018-10-13 01:41:00 | 86
Call | 12 | 2018-10-13 02:39:12 | 87
Call | 12 | 2018-10-13 04:31:22 | 88
Attendance | 12 | 2018-10-13 14:29:00 | 89
Call | 12 | 2018-10-13 14:59:19 | 90
Attendance | 12 | 2018-10-15 15:50:00 | 91
The code I used for this is:
WITH CTE1 AS
(
SELECT 'Call' as [Type], ID, CallDate AS Date1
FROM CallsTable
UNION ALL
SELECT 'Attendance' as [Type], ID, AttendanceDate AS Date2
FROM AttendanceTable]
)
,CTE2 AS
(
SELECT [Type], Date1, ID, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Date1 ASC) AS RowNum
FROM CTE1
)
--------------------------------OUTPUT--------------------------------
SELECT a.[Type], a.ID, a.Date1, a.RowNum
FROM CTE2 a
JOIN CTE2 b
ON a.ID= b.ID
AND a.RowNum = b.RowNum + 1
WHERE a.ID = '12'
ORDER BY ID, RowNum
I want to modify this to look like the below output, so that whenever an attendance follows a call, it should be in the same row.
Table2:
Type | ID | CallDate | RowNum | Type | AttendanceDate | RowNum
------|----|------------------|--------|------------|------------------|--------
NULL | 12 | NULL | NULL | Attendance | 16/09/2018 10:11 | 82
Call | 12 | 18/09/2018 14:11 | 83 | NULL | NULL | NULL
Call | 12 | 02/10/2018 17:26 | 84 | NULL | NULL | NULL
Call | 12 | 05/10/2018 14:58 | 85 | Attendance | 13/10/2018 01:41 | 86
Call | 12 | 13/10/2018 02:39 | 87 | NULL | NULL | NULL
Call | 12 | 13/10/2018 04:31 | 88 | Attendance | 13/10/2018 14:29 | 89
Call | 12 | 13/10/2018 14:59 | 90 | Attendance | 15/10/2018 15:50 | 91
Is this possible? What code could I use?
Use FULL JOIN
SELECT
*
FROM
(SELECT * FROM CTE2 WHERE Type = 'CALL') A
FULL JOIN
(SELECT * FROM CTE2 WHERE Type = 'ATTENDANCE') B
ON A.ID = B.ID AND A.RowNum = B.RowNum - 1
You can use APPLY :
SELECT C.[Type], C.ID, C.CallDate, C.RowNum,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.[TYPE] end) [TYPE],
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.CallDate end) AttendanceDate,
(CASE WHEN C2.RowNum - C.RowNum = 1 THEN C2.RowNum end) RowNum
FROM CTE2 C OUTER APPLY
(SELECT TOP (1) C2.*
FROM CTE2 C2
WHERE C2.ID = C.ID AND C2.[Type] = 'Attendance' AND C2.RowNum > C.RowNum
ORDER BY C2.RowNum
) C2
WHERE C.ID = 12 AND C.[Type] = 'Call';
Not as elegant, but works for me, a table valued function
alter FUNCTION GetCallActivity()
RETURNS #activityTable TABLE
(
call_type varchar(16),
call_id int,
call_date datetime,
call_rownum int,
atnd_type varchar(16),
atnd_id int,
atnd_date datetime,
atnd_rownum int
)
AS
BEGIN
-- initialize the return table
insert into #activityTable
(call_type, call_id, call_date, call_rownum )
select a.type, a.id, a.activity_date, a.rownum
from stack_calls a
where a.type = 'Call'
order by a.activity_date;
-- match to the attendence recs to the call recs
update #activityTable
set atnd_type = b.type,
atnd_id = b.id,
atnd_date = b.activity_date,
atnd_rownum = b.rownum
from stack_calls b
join #activityTable a
on b.rownum = a.call_rownum + 1
where b.type = 'Attendance';
-- deal with the edge cases
insert into #activityTable
( atnd_type, atnd_id, atnd_date, atnd_rownum )
select x.type,
x.id,
x.activity_date,
x.rownum
from
(
select a.type,
a.id,
a.activity_date,
a.rownum,
lag(a.type, 1) over (order by a.activity_date) as prev_type
from stack_calls a
where a.type = 'Attendance'
) x
where x.prev_type is null
RETURN
END
GO
As per my below query, How i can get the last row with value "0" if there is no data in "inc" table between the selected dates.
My query:
select
Calls = count(*)
, Cust = i.Cust_id
, Contract = c.con_id
, Serv_Time = sum(Serv_Time)
from inc as i
inner join contract as c
on i.item_id = c.item_id
and i.inc_date >= c.[start]
and i.inc_date <= c.[end]
where c.[start]>='20160101'
group by i.Cust_id, c.con_id
order by i.Cust_Id, c.con_id
Expected output:
+-------+---------+------------+-----------+
| Calls | Cust | Contract | Serv_Time |
+-------+---------+------------+-----------+
| 1 | Amir | MS2016 | 300 |
| 2 | John | HP2016 | 180 |
| 1 | Kerlee | OR2016 | 40 |
| 1 | Nick | CIS2016 | 120 |
| 2 | samanta | EMC2016 | 200 |
| 0 | Amir | MS2016-New | 0 |
+-------+---------+------------+-----------+
Table structures:
create table inc
(
inc_id int
, cust_id varchar(16)
, item_id varchar(16)
, serv_time int
, inc_date date
);
insert into inc values
(1,'john','HP', 40 ,'17-Apr-2015')
,(2,'John','HP', 60 ,'10-Jan-2016')
,(3,'Nick','Cisco', 120 ,'11-Jan-2016')
,(4,'samanta','EMC', 180 ,'12-Jan-2016')
,(5,'Kerlee','Oracle', 40 ,'13-Jan-2016')
,(6,'Amir','Microsoft', 300 ,'14-Jan-2016')
,(7,'John','HP', 120 ,'15-Jan-2016')
,(8,'samanta','EMC', 20 ,'16-Jan-2016')
,(9,'Kerlee','Oracle', 10 ,'02-Feb-2017');
create table contract (
item_id varchar(16)
, con_id varchar(16)
, [Start] date
, [End] date
);
insert into contract values
('Dell','DE2015','20150101','20151231')
,('HP','HP2015','20150101','20151231')
,('Cisco','CIS2016','20160101','20161231')
,('EMC','EMC2016','20160101','20161231')
,('HP','HP2016','20160101','20161231')
,('Oracle','OR2016','20160101','20161231')
,('Microsoft','MS2016','20160101','20161231')
,('Microsoft','MS2016-New','20160101','20161231')
,('Microsoft','MS2017','20170101','20171231');
Use LEFT OUTER JOIN and function ISNULL() to set NULL to zero. It would not be a problem get zero value if you can generate the lines (left outer join)
I am new with SQL Pivot, so i would like to have a detail explaination regarding to it. My table is like the following:
PID | NAME | PlateNumber | COUNTRY|
------------------------------------------
111 | Alex | ab123456 | GB |
------------------------------------------
111 | Alex | fe123344 | ES |
------------------------------------------
111 | Alex | r223456e | US |
------------------------------------------
112 | Simon | t22er563 | GB |
------------------------------------------
112 | Simon | q32345ke | DE |
------------------------------------------
113 | Ben | ve923456 | IT |
------------------------------------------
And i would to have the result in the following format:
PID |NAME |PlateNumber1|PlateNumber2| PlateNumber3|COUNTRY1| COUNTRY2| COUNTRY3|
--------------------------------------------------------------------------------
111 | Alex | ab123456 | fe123344 | r223456e | GB | ES | US |
--------------------------------------------------------------------------------
112 | Simon | t22er563| q32345ke | | GB | DE | |
--------------------------------------------------------------------------------
113 | Ben | ve923456| | | IT | | |
--------------------------------------------------------------------------------
Could you please help me with this?
Thank you in advance!
The traditional cross tab / conditional aggregation version would be like so:
test setup: http://rextester.com/SKMUL25726
select
pid
, name
, PlateNumber1 = max(case when rn = 1 then PlateNumber end)
, PlateNumber2 = max(case when rn = 2 then PlateNumber end)
, PlateNumber3 = max(case when rn = 3 then PlateNumber end)
, Country1 = max(case when rn = 1 then Country end)
, Country2 = max(case when rn = 2 then Country end)
, Country3 = max(case when rn = 3 then Country end)
from (
select t.*
, rn=row_number() over (partition by PID order by platenumber)
from t
) as t
group by pid, name
returns:
+-----+-------+--------------+--------------+--------------+----------+----------+----------+
| pid | name | PlateNumber1 | PlateNumber2 | PlateNumber3 | Country1 | Country2 | Country3 |
+-----+-------+--------------+--------------+--------------+----------+----------+----------+
| 111 | Alex | ab123456 | fe123344 | r223456e | GB | ES | US |
| 113 | Ben | ve923456 | NULL | NULL | IT | NULL | NULL |
| 112 | Simon | q32345ke | t22er563 | NULL | DE | GB | NULL |
+-----+-------+--------------+--------------+--------------+----------+----------+----------+
Using PIVOT:
with your_table(PID , NAME , PlateNumber , Country) as (
select 111 , 'Alex' , 'ab123456' , 'GB' union all
select 111 , 'Alex' , 'fe123344' , 'ES' union all
select 111 , 'Alex' , 'r223456e' , 'US' union all
select 112 , 'Simon' , 't22er563' , 'GB' union all
select 112 , 'Simon' , 'q32345ke' , 'DE' union all
select 113 , 'Ben' , 've923456' , 'IT'
)
select pid,
name,
max(PlateNumber1) PlateNumber1,
max(PlateNumber2) PlateNumber2,
max(PlateNumber3) PlateNumber3,
max(Country1) Country1,
max(Country2) Country2,
max(Country3) Country3
from (
select *,
'Country' + cast(row_number() over (
partition by PID order by pnum
) as varchar(30)) cn
from (
select t.*,PlateNumber pnum,
'PlateNumber' + cast(row_number() over (
partition by PID order by PlateNumber
) as varchar(30)) pn
from your_table t
) t
pivot(max(PlateNumber) for pn in ([PlateNumber1], [PlateNumber2], [PlateNumber3])) x
) t
pivot(max(Country) for cn in ([Country1], [Country2], [Country3])) x
group by pid,
name;
Demo
If the PlateNumber count is not fixed of PID, you can use dynamic statement.
CREATE TABLE #tt(PID INT,[Name] VARCHAR(10),PlateNumber VARCHAR(10),Country VARCHAR(5))
INSERT INTO #tt
select 111 , 'Alex' , 'ab123456' , 'GB' union all
select 111 , 'Alex' , 'fe123344' , 'ES' union all
select 111 , 'Alex' , 'r223456e' , 'US' union all
select 112 , 'Simon' , 't22er563' , 'GB' union all
select 112 , 'Simon' , 'q32345ke' , 'DE' union all
select 113 , 'Ben' , 've923456' , 'IT'
DECLARE #SQL VARCHAR(max),#col VARCHAR(max)
---- Get the max line count of all the PID
DECLARE #MaxNumber INT =0
SELECT #MaxNumber=CASE WHEN COUNT(0)>#MaxNumber THEN count(0) ELSE #MaxNumber END FROM #tt AS t GROUP BY t.Name,t.PID
PRINT #MaxNumber
SELECT #col=ISNULL(#col+',','')+'PlateNumber'+LTRIM(sv.number)+',Country'+LTRIM(sv.number)
FROM master.dbo.spt_values AS sv WHERE sv.type='P' AND sv.number BETWEEN 1 AND #MaxNumber
PRINT #col
SET #SQL='
SELECT * FROM (
SELECT pid,[name],c.col_value,c.col_title+LTRIM(ROW_NUMBER()OVER(PARTITION BY t.PID,[name],c.col_title ORDER BY (SELECT 0))) AS col_title FROM #tt AS t
CROSS APPLY(VALUES(''PlateNumber'',t.PlateNumber),(''Country'',t.Country))c(col_title,col_value)
) AS t
PIVOT(MAX(col_value) FOR col_title IN ('+#col+')) p'
EXEC(#SQL)
pid name PlateNumber1 Country1 PlateNumber2 Country2 PlateNumber3 Country3
----------- ---------- ------------ ---------- ------------ ---------- ------------ ----------
111 Alex r223456e GB fe123344 ES ab123456 US
113 Ben ve923456 IT NULL NULL NULL NULL
112 Simon q32345ke DE t22er563 GB NULL NULL
I have a table with the following format
YEAR, MONTH, ITEM, REQ_QTY1, REQ_QTY2 , ....REQ_QTY31 ,CONVERTED1, CONVERTED2 ....CONVERTED31
Where the suffix of each column is the day of the month.
I need to convert it to the following format, where Day_of_month is the numeric suffix of each column
YEAR, MONTH, DAY_OF_MONTH, ITEM, REQ_QTY, CONVERTED
I thought of using CROSS APPLY to retrieve the data, but I can't use CROSS APPLY to get the "Day of Month"
SELECT A.YEAR, A.MONTH, A.ITEM, B.REQ_QTY, B.CONVERTED
FROM TEST A
CROSS APPLY
(VALUES
(REQ_QTY1, CONVERTED1),
(REQ_QTY2, CONVERTED2),
(REQ_QTY3, CONVERTED3),
......
(REQ_QTY31, CONVERTED31)
)B (REQ_QTY, CONVERTED)
The only way I found is to use a nested select with inner join
SELECT A.YEAR, A.MONTH, A.DAY_OF_MONTH, A.ITEM,A.REQ_QTY, D.CONVERTED FROM
(SELECT YEAR, MONTH, ITEM, SUBSTRING(DAY_OF_MONTH,8,2) AS DAY_OF_MONTH, REQ_QTY FROM TEST
UNPIVOT
(REQ_QTY FOR DAY_OF_MONTH IN ([REQ_QTY1],[REQ_QTY2],[REQ_QTY3],......[REQ_QTY30],[REQ_QTY31])
) B
) A
INNER JOIN (SELECT YEAR, MONTH, ITEM, SUBSTRING(DAY_OF_MONTH,10,2) AS DAY_OF_MONTH, CONVERTED FROM TEST
UNPIVOT
(CONVERTED FOR DAY_OF_MONTH IN ([CONVERTED1],[CONVERTED2],[CONVERTED3],....[CONVERTED30],[CONVERTED31])
) C
) D
ON D.YEAR = A.YEAR AND D.MONTH = A.MONTH AND D.ITEM = A.ITEM AND D.DAY_OF_MONTH = A.DAY_OF_MONTH
Is there a way to use CROSS APPLY and yet get the DAY_OF_MONTH out?
This is not a solution with CROSS APPLY but it will definitely make it a bit faster as it uses a bit simpler approach and simpler execution plan.
SQL Fiddle
MS SQL Server 2008 Schema Setup:
CREATE TABLE Test_Table([YEAR] INT, [MONTH] INT, [ITEM] INT, REQ_QTY1 INT
, REQ_QTY2 INT ,REQ_QTY3 INT , CONVERTED1 INT, CONVERTED2 INT, CONVERTED3 INT)
INSERT INTO Test_Table VALUES
( 2015 , 1 , 1 , 10 , 20 , 30 , 100 , 200 , 300),
( 2015 , 2 , 1 , 10 , 20 , 30 , 100 , 200 , 300),
( 2015 , 3 , 1 , 10 , 20 , 30 , 100 , 200 , 300)
Query 1:
SELECT *
FROM
(
SELECT [YEAR]
,[MONTH]
,ITEM
,Vals
,CASE WHEN LEFT(N,3) = 'REQ' THEN SUBSTRING(N,8 ,2)
WHEN LEFT(N,3) = 'CON' THEN SUBSTRING(N,10,2)
END AS Day_Of_Month
,CASE WHEN LEFT(N,3) = 'REQ' THEN LEFT(N,7)
WHEN LEFT(N,3) = 'CON' THEN LEFT(N,9)
END AS Tran_Type
FROM Test_Table t
UNPIVOT (Vals FOR N IN ([REQ_QTY1],[REQ_QTY2],[REQ_QTY3],
[CONVERTED1],[CONVERTED2],[CONVERTED3]))up
)t2
PIVOT (SUM(Vals)
FOR Tran_Type
IN (REQ_QTY, CONVERTED))p
Results:
| YEAR | MONTH | ITEM | Day_Of_Month | REQ_QTY | CONVERTED |
|------|-------|------|--------------|---------|-----------|
| 2015 | 1 | 1 | 1 | 10 | 100 |
| 2015 | 1 | 1 | 2 | 20 | 200 |
| 2015 | 1 | 1 | 3 | 30 | 300 |
| 2015 | 2 | 1 | 1 | 10 | 100 |
| 2015 | 2 | 1 | 2 | 20 | 200 |
| 2015 | 2 | 1 | 3 | 30 | 300 |
| 2015 | 3 | 1 | 1 | 10 | 100 |
| 2015 | 3 | 1 | 2 | 20 | 200 |
| 2015 | 3 | 1 | 3 | 30 | 300 |
Well, I found a way using CROSS APPLY, but instead of taking a substring, I'm basically hardcoding the days. Works well enough so...
SELECT A.YEAR, A.MONTH, A.ITEM, B.DAY_OF_MONTH, B.REQ_QTY, B.CONVERTED
FROM TEST A
CROSS APPLY
(
VALUES
('01', REQ_QTY1, CONVERTED1),
('02', REQ_QTY2, CONVERTED2),
('03', REQ_QTY3, CONVERTED3),
('04', REQ_QTY4, CONVERTED4),
......
('31', REQ_QTY31, CONVERTED31)
) B (DAY_OF_MONTH, REQ_QTY, CONVERTED)