Using PIVOT over a group by query - sql-server

I've been trying to create a pivot for following query :
select mainstate, customertypeid, count(1) as [counter] from customers group by customertypeid, mainstate
This query should display as many customers types per state, it looks like this (order by doesn't matter) :
State|customertypeid|counter
UT 3 200
CA 3 500
NY 3 300
UT 2 100
CA 2 200
NY 2 120
UT 1 20
CA 1 50
NY 1 30
I've tried to use PIVOT as follow (I'm sure I'm wrong) :
SELECT *
FROM ( select mainstate, customertypeid, count(1) as [counter] from customers where customertypeid in (1,2,3) and mainstate != '' group by customertypeid, mainstate) as NonPivotedDataForReport2
PIVOT
(
COUNT([counter])
FOR mainstate IN ([# of Amb],[# Whole Sale Customers],[# Retail Customers])
) AS PivotedDataForReport2
I'm getting this :
customertypeid|type1|type2|type3
1 0 0 0
2 0 0 0
3 0 0 0
and the report should look like this :
State|type1|type2|type3
UT 20 100 200
CA 50 200 500
NY 30 120 300
*Ps : I don't really want to go back to CASE + SUM Statement,
Thanks a lot!

This will do:
SELECT mainstate [State],
[1] type1,
[2] type2,
[3] type3
FROM ( SELECT mainstate, customertypeid, COUNT(1) [counter]
FROM customers
WHERE customertypeid in (1,2,3)
AND mainstate != ''
GROUP BY customertypeid, mainstate) as NonPivotedDataForReport2
PIVOT(SUM([counter]) FOR customertypeid IN ([1],[2],[3])) AS PivotedDataReport2

This (perhaps slightly edited) should do the job for you without case/sum/pivot. Create a temp table, insert starting data and then dynamically add columns depending on how many customer type ids there is.
declare #s varchar(10), #xx1 varchar(500)
select distinct state into #temp from customers
DECLARE myCursor CURSOR FOR SELECT distinct customertypeid from customers
open MyCursor
FETCH NEXT FROM myCursor into #S
WHILE ##FETCH_STATUS = 0
begin
set #xx1 = 'alter table #temp add ['+#s+'] varchar(5)'
execute sp_executesql #xx1
set #xx1 = 'update a set a.['+#s+'] = coalesce(b.counter,0) from #temp a, customers b where b.customertypeid = '+#s+' and a.state = b.state'
execute sp_executesql #xx1
FETCH NEXT FROM myCursor into #S
End
Close myCursor
DEALLOCATE myCursor
select * from #temp

Related

SQL How to join rows as column headers in a Pivot?

I have the following 2 tables:
TagNames:
TagName
TagIndex
Name1
0
Name2
1
Name3
2
TagValues:
DateAndTime
TagIndex
Val
2023-02-08 09:31:51.000
0
0
2023-02-08 09:31:51.000
1
10
2023-02-08 09:31:51.000
2
20
2023-02-08 09:32:01.000
0
1
2023-02-08 09:32:01.000
1
11
2023-02-08 09:32:01.000
2
21
Using this query I managed to fetch the rows as cols
WITH Tags AS (
SELECT
T.[TagIndex],
T.[DateAndTime],
T.[Val]
FROM
[dbo].[TagValues] T
INNER JOIN [dbo].[TagNames] N
ON T.TagIndex = N.TagIndex
)
SELECT *
FROM
Tags
PIVOT (MAX([Val]) FOR [TagIndex] IN ([0], [1], [2])) P
ORDER BY DateAndTime
;
Obtaining as result something like this:
DateAndTime
0
1
2
2023-02-08 09:31:51.000
0
10
20
2023-02-08 09:32:01.000
1
11
21
What I want to do is substitute the column headers with TagName in the first table
You can do this with dynamic SQL, as I describe in this article:
DECLARE #TagNameCols nvarchar(max), #sql nvarchar(max);
SELECT #TagNameCols = STRING_AGG(QUOTENAME(tn.TagName),',')
FROM dbo.TagNames AS tn
WHERE TagIndex IN (SELECT TagIndex FROM dbo.TagValues);
SELECT #sql = N'WITH Tags AS (
SELECT
N.[TagName],
T.[DateAndTime],
T.[Val]
FROM
[dbo].[TagValues] T
INNER JOIN [dbo].[TagNames] N
ON T.TagIndex = N.TagIndex
)
SELECT *
FROM
Tags
PIVOT (MAX([Val]) FOR [TagName] IN (' + #TagNameCols + N')) P
ORDER BY DateAndTime
;';
EXEC sys.sp_executesql #sql;
Working db<>fiddle example

Transact-SQL - number rows until condition met

I'm trying to generate the numbers in the "x" column considering the values in field "eq", in a way that it should assign a number for every record until it meets the value "1", and the next row should reset and start counting again. I've tried with row_number, but the problem is that I only have ones and zeros in the column I need to evaluate, and the cases I've seen using row_number were using growing values in a column. Also tried with rank, but I haven't managed to make it work.
nInd Fecha Tipo #Inicio #contador_I #Final #contador_F eq x
1 18/03/2002 I 18/03/2002 1 null null 0 1
2 20/07/2002 F 18/03/2002 1 20/07/2002 1 1 2
3 19/08/2002 I 19/08/2002 2 20/07/2002 1 0 1
4 21/12/2002 F 19/08/2002 2 21/12/2002 2 1 2
5 17/03/2003 I 17/03/2003 3 21/12/2002 2 0 1
6 01/04/2003 I 17/03/2003 4 21/12/2002 2 0 2
7 07/04/2003 I 17/03/2003 5 21/12/2002 2 0 3
8 02/06/2003 F 17/03/2003 5 02/06/2003 3 0 4
9 31/07/2003 F 17/03/2003 5 31/07/2003 4 0 5
10 31/08/2003 F 17/03/2003 5 31/08/2003 5 1 6
11 01/09/2005 I 01/09/2005 6 31/08/2003 5 0 1
12 05/09/2005 I 01/09/2005 7 31/08/2003 5 0 2
13 31/12/2005 F 01/09/2005 7 31/12/2005 6 0 3
14 14/01/2006 F 01/09/2005 7 14/01/2006 7 1 4
There is another solution available:
select
nind, eq, row_number() over (partition by s order by s)
from (
select
nind, eq, coalesce((
select sum(eq) +1 from mytable pre where pre.nInd < mytable.nInd)
,1) s --this is the sum of eq!
from mytable) g
The inner subquery creates groups sequentially for each occurrence of 1 in eq. Then we can use row_number() over partition to get our counter.
Here is an example using Sql Server
I have two answers here. One is based off of the ROW_NUMBER() and the other is based off of what appears to be your index (nInd). I wasn't sure if there would be a gap in your index so I made the ROW_NUMBER() as well.
My table format was as follows -
myIndex int identity(1,1) NOT NULL
number int NOT NULL
First one is ROW_NUMBER()...
WITH rn AS (SELECT *, ROW_NUMBER() OVER (ORDER BY myIndex) AS rn, COUNT(*) AS max
FROM counting c GROUP BY c.myIndex, c.number)
,cte (myIndex, number, level, row) AS (
SELECT r.myIndex, r.number, 1, r.rn + 1 FROM rn r WHERE r.rn = 1
UNION ALL
SELECT r1.myIndex, r1.number,
CASE WHEN r1.number = 0 AND r2.number = 1 THEN 1
ELSE c.level + 1
END,
row + 1
FROM cte c
JOIN rn r1
ON c.row = r1.rn
JOIN rn r2
ON c.row - 1 = r2.rn
)
SELECT c.myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
Now the index...
WITH cte (myIndex, number, level) AS (
SELECT c.myIndex + 1, c.number, 1 FROM counting c WHERE c.myIndex = 1
UNION ALL
SELECT c1.myIndex + 1, c1.number,
CASE WHEN c1.number = 0 AND c2.number = 1 THEN 1
ELSE c.level + 1
END
FROM cte c
JOIN counting c1
ON c.myIndex = c1.myIndex
JOIN counting c2
ON c.myIndex - 1 = c2.myIndex
)
SELECT c.myIndex - 1 AS myIndex, c.number, c.level FROM cte c OPTION (MAXRECURSION 0);
The answer that I have now is via using
Cursor
I know if there is another solution without cursor it will be better for performance aspects
here is a quick demo of my solution:
-- Create DBTest
use master
Go
Create Database DBTest
Go
use DBTest
GO
-- Create table
Create table Tabletest
(nInd int , eq int)
Go
-- insert dummy data
insert into Tabletest (nInd,eq)
values (1,0),
(2,1),
(3,0),
(4,1),
(5,0),
(6,0),
(7,0),
(8,0),
(9,1),
(8,0),
(9,1)
Create table #Tabletest (nInd int ,eq int ,x int )
go
DECLARE #nInd int , #eq int , #x int
set #x = 1
DECLARE db_cursor CURSOR FOR
SELECT nInd , eq
FROM Tabletest
order by nInd
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #nInd , #eq
WHILE ##FETCH_STATUS = 0
BEGIN
if (#eq = 0)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = #x +1
end
else if (#eq = 1)
begin
insert into #Tabletest (nInd ,eq ,x) values (#nInd , #eq , #x)
set #x = 1
end
FETCH NEXT FROM db_cursor INTO #nInd , #eq
END
CLOSE db_cursor
DEALLOCATE db_cursor
select * from #Tabletest
The end result set will be as following:
Hope it helps.
Looking at this a slightly different way (which might not be true, but eliminates the need for cursors of recursive CTEs), it looks like you building ordered groups within your dataset. So, start by finding those groups, then determining the ordering of each of them.
The real key is to determine the rules to find the correcting grouping. Based on your description and comments, I'm guessing the grouping is from the start (ordered by the nInd column) ending at each row with and eq value of 1, so you can do something like:
;with ends(nInd, ord) as (
--Find the ending row for each set
SELECT nInd, row_number() over(order by nInd)
FROM mytable
WHERE eq=1
), ranges(sInd, eInd) as (
--Find the previous ending row for each ending row, forming a range for the group
SELECT coalesce(s.nInd,0), e.nInd
FROM ends s
right join ends e on s.ord=e.ord-1
)
Then, using these group ranges, you can find the final ordering of each:
select t.nInd, t.Fecha, t.eq
,[x] = row_number() over(partition by sInd order by nInd)
from ranges r
join mytable t on r.sInd < t.nInd
and t.nInd <= r.eInd
order by t.nInd

Modifying the current row based on the previous row in sql server

I have a result set like this:
YearMonth Sales
201411 100
201412 100
201501 100
201502 100
201503 100
201504 100
201505 100
201506 100
201507 100
201508 100
Need to add another row with 4% more sales than the previous month. For example my Result should be
YearMonth Sales New Sales
201411 100 100.00
201412 100 104.00
201501 100 108.16
201502 100 112.49
201503 100 116.99
201504 100 121.67
201505 100 126.53
201506 100 131.59
201507 100 136.86
201508 100 142.33
Please help me to get the best way for it.
Got perfect answer for your requirement. It took long time to figure out. Just change the #Temp table name with your table name and verify the column names also.
DECLARE #nCurrentSale FLOAT
DECLARE #nYeatDate INT
DECLARE #nSale FLOAT
CREATE TABLE #TempNEW(YearMonth VARCHAR(10), Sales FLOAT, NewSale FLOAT)
SELECT TOP 1 #nCurrentSale = Sales FROM #Temp
ORDER BY (CAST('01/' + SUBSTRING (CAST(YearMonth AS VARCHAR), 5 , 2) + '/' + SUBSTRING (CAST(YearMonth AS
VARCHAR), 0 , 5) AS DATETIME)) ASC
DECLARE Cursor1 CURSOR FOR
SELECT YearMonth, Sales FROM #Temp
ORDER BY (CAST('01/' + SUBSTRING (CAST(YearMonth AS VARCHAR), 5 , 2) + '/' + SUBSTRING (CAST(YearMonth AS
VARCHAR), 0 , 5) AS DATETIME)) ASC
OPEN Cursor1
FETCH NEXT FROM Cursor1 INTO #nYeatDate, #nSale
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #TempNEW(YearMonth, Sales, NewSale) VALUES(#nYeatDate, #nSale, CAST(#nCurrentSale AS DECIMAL(12,2)))
SET #nCurrentSale = #nCurrentSale + ((#nCurrentSale/100) * 4)
FETCH NEXT FROM Cursor1 INTO #nYeatDate, #nSale
END
CLOSE Cursor1
DEALLOCATE Cursor1
SELECT * FROM #TempNEW
Notify me with your status.
Yes it possible. But first you have to alter the table and add the extra column NewSales then try with this link
https://dba.stackexchange.com/questions/34243/update-row-based-on-match-to-previous-row
i think you can done it through this link
Also sql server support some "Computed Columns in SQL Server with Persisted Values"
using that you can specify the formula what you want, then the new column value will automatically created according to your formula
Here's two thoughts... Not super clear if I understood the use case... Also this solution will only work for SQL 2012 and up
So given the table
CREATE TABLE [dbo].[LagExample](
[YearMonth] [nvarchar](100) NOT NULL,
[Sales] [money] NOT NULL
)
First one is fairly simple and just assumes you are wanting to base the magnitude of your percentage increase on how many days came before it...
;WITH cte
as
(
SELECT YearMonth,
ROW_NUMBER() OVER (ORDER BY YearMonth) - 1 AS SalesEntry,
cast(LAG(Sales, 1,Sales) OVER (ORDER BY YearMonth) as float) as Sales
FROM LagExample
)
SELECT YearMonth,
Sales,
cast(Sales * POWER(cast(1.04 as float), SalesEntry) AS decimal(10,2)) as NewSales
FROM cte
The Second one uses a recursive CTE to calculate the value as you move along the months..
Here's a good link about recursive CTEs
http://www.codeproject.com/Articles/683011/How-to-use-recursive-CTE-calls-in-T-SQL
;with data
as
(
SELECT Lead(le.YearMonth, 1, null) OVER (ORDER BY le.YearMonth) as NextYearMonth,
cast(le.Sales as Decimal(10,4)) as Sales,
le.YearMonth
FROM LagExample le
)
,cte
as
(
SELECT *
FROM data
Where YearMonth = '201411'
UNION ALL
SELECT
data.NextYearMonth,
cast(cte.Sales * 1.04 as Decimal(10,4)) as Sales,
data.YearMonth
From cte join
data on data.YearMonth = cte.NextYearMonth
)
SELECT YearMonth, cast(Sales as Decimal(10,2))
FROM cte
order by YearMonth

About sql server2008 running total

I have a table , the table structure is:
TransactionRecordID State TransactionMoney CreateTime
1 1 200 2014/6/19
2 0 100 2014/6/12
3 1 50 2014/3/17
4 1 400 2014/2/23
......
I want to get the result:
TransactionRecordID State TransactionMoney CreateTime TotalMoney
1 1 200 2014/6/19 650
2 0 100 2014/6/12 450
3 1 50 2014/3/17 450
4 1 400 2014/2/23 400
If current record field state=1,I would like to get sum TransactionMoney after current
record add current TransactionMoney , else get sum TransactionMoney after current record
Sorry for my english!
SQL Server 2012 makes this a lot simpler using SUM() OVER (ORDER BY ...), but in SQL Server 2008 you can use a LEFT JOIN for the same effect;
SELECT a.TransactionRecordID, a.State, a.TransactionMoney, a.CreateTime,
COALESCE(SUM(b.TransactionMoney), 0) TotalMoney
FROM transactions a
LEFT JOIN transactions b ON a.CreateTime >= b.CreateTime AND b.state = 1
GROUP BY a.TransactionRecordID, a.State, a.TransactionMoney, a.CreateTime
ORDER BY a.TransactionRecordID;
An SQLfiddle to test with.
Here is a CTE Example for Northwind. it might give you some alternate ideas.
Use Northwind
GO
declare #CustomerID varchar(6)
declare #BeginDate datetime
declare #EndDate datetime
select #CustomerID = (select top 1 CustomerID from dbo.Orders )
select #BeginDate = '01/01/1900'
select #EndDate = '12/31/2010'
;
WITH
MyCTE /* http://technet.microsoft.com/en-us/library/ms175972.aspx */
( ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,ROWID) AS
(
SELECT
ShipName ,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address]
,City ,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName
,ProductID ,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight
, ROW_NUMBER() OVER ( ORDER BY OrderDate , ProductName ASC ) as ROWID
FROM
dbo.Invoices inv /* “Invoices” is a VIEW, FYI */
where
inv.CustomerID = #CustomerID and (inv.OrderDate between #BeginDate and #EndDate)
)
SELECT
/*
ShipName,ShipAddress,ShipCity,ShipRegion,ShipPostalCode,ShipCountry,CustomerID,CustomerName,[Address],
City,Region,PostalCode,Country,Salesperson,OrderID,OrderDate,RequiredDate,ShippedDate,ShipperName,
ProductID,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,
*/
/*trim the list down a little for the final output */
CustomerID ,Salesperson,OrderID,OrderDate,ProductName,UnitPrice,Quantity,Discount,ExtendedPrice,Freight,(ExtendedPrice + Freight) as ComputedTotal
/*The below line is the “trick”. I reference the above CTE, but only get data that is less than or equal to the row that I am on (outerAlias.ROWID)*/
, (Select SUM (ExtendedPrice + Freight) from MyCTE innerAlias where innerAlias.ROWID <= outerAlias.ROWID ) as RunningTotal
, ROWID as ROWID_SHOWN_FOR_KICKS , OrderDate as OrderDateASecondTimeForConvenience
FROM
MyCTE outerAlias
/*Two Order By Options*/
ORDER BY outerAlias.OrderDate , ProductName
/* << Whatever the ORDER BY is here, should match the “ROW_NUMBER() OVER ( ORDER BY ________ ASC )” statement inside the CTE */
/*ORDER BY outerAlias.ROWID */ /* << Or, to keep is more “trim”, ORDER BY the ROWID, which will of course be the same as the “ROW_NUMBER() OVER ( ORDER BY” inside the CTE */

How do I calculate on-time delivery when SQL tables aren't one to one

I have the following two tables:
DueDates:
DECLARE #DueDates TABLE (
uniqueln varchar(10),
ship_dts smalldatetime,
qty decimal(18,4))
INSERT INTO #DueDates
SELECT '51351621AS','1/1/2013',7
UNION ALL
SELECT '51351621AS','1/7/2013',7
UNION ALL
SELECT '51351621AS','1/14/2013',7
UNION ALL
SELECT '51351621AS','1/21/2013',7
UNION ALL
SELECT '51351621AS','1/28/2013',7
UNION ALL
SELECT 'V4351621AS','1/5/2013',10
UNION ALL
SELECT 'V4351621AS','1/10/2013',10
UNION ALL
SELECT 'V4351621AS','1/15/2013',10
UNION ALL
SELECT 'V4351621AS','1/20/2013',10
UNION ALL
SELECT 'V4351621AS','1/25/2013',10
PlDetail
DECLARE #PlDetail TABLE (
uniqueln varchar(10),
shipdate smalldatetime,
shippedqty decimal(18,4))
INSERT INTO #PlDetail
SELECT '51351621AS','1/1/2013',10
UNION ALL
SELECT '51351621AS','1/7/2013',10
UNION ALL
SELECT '51351621AS','1/14/2013',10
UNION ALL
SELECT '51351621AS','1/21/2013',5
UNION ALL
SELECT 'V4351621AS','1/5/2013',13
UNION ALL
SELECT 'V4351621AS','1/15/2013',9
UNION ALL
SELECT 'V4351621AS','1/25/2013',12
UNION ALL
SELECT 'V4351621AS','1/30/2013',10
UNION ALL
SELECT 'V4351621AS','2/5/2013',6
The shipments in PlDetail can be one to one with the orders in the DueDates table, but often they are not.
I am trying to calculate an on-time delivery for each uniqueln schedule using a FIFO method (I cannot change how the data is stored in the tables). Basically, I want to apply the earliest shipments to the earliest deliveries.
If a shipment qty exceeds the balance in a DueDates record, it should have the balance applied to the next scheduled delivery.
The end result should look something like this:
uniqueln ship_dts qty shipdate shippedqty daysLate
51351621AS 1/1/2013 7 1/1/2013 7 0
51351621AS 1/7/2013 7 1/1/2013 3 -6
51351621AS 1/7/2013 7 1/7/2013 4 0
51351621AS 1/14/2013 7 1/7/2013 6 -7
51351621AS 1/14/2013 7 1/14/2013 1 0
51351621AS 1/21/2013 7 1/14/2013 7 -7
51351621AS 1/28/2013 7 1/14/2013 2 -14
51351621AS 1/28/2013 7 1/21/2013 5 -7
V4351621AS 1/5/2013 10 1/5/2013 10 0
V4351621AS 1/10/2013 10 1/5/2013 3 -5
V4351621AS 1/10/2013 10 1/15/2013 7 5
V4351621AS 1/15/2013 10 1/15/2013 2 0
V4351621AS 1/15/2013 10 1/25/2013 8 10
V4351621AS 1/20/2013 10 1/25/2013 4 5
V4351621AS 1/20/2013 10 1/30/2013 6 10
V4351621AS 1/25/2013 10 1/30/2013 4 5
V4351621AS 1/25/2013 10 2/5/2013 6 11
I know how to group the PlDetail shipments strictly by date so any shipment that falls on or before the next due date is grouped together, but it HAS to factor in the scheduled qty vs the shipped qty.
I don't want to create a cursor and cycle through the shipment records, but I can if this type of join won't work. However, I believe it is possible, but I am not sure how to group or join the tables.
It sounds like SQL Server 2012 has some new methods that will make this easier, but right now I am using SQL SERVER 2008 R2 and have to keep it that way for the near future.
What is the best way to approach this? Is a cursor really the only way?
UPDATE:
This is what I have added so far. The end result is a table showing the from and to qty and ship_dts for each uniqueln
WITH DSeq AS (
SELECT TOP 100 PERCENT
Seq = Row_Number() OVER (partition by uniqueln ORDER BY ship_dts),
D.UNIQUELN,
D.SHIP_DTS,
SchBal = (SELECT TOP 100 PERCENT SUM(B.Qty) FROM #DueDates B WHERE b.SHIP_DTS<= D.SHIP_DTS AND b.UNIQUELN=d.UNIQUELN ORDER BY d.SHIP_DTS)
FROM #DueDates D
GROUP BY UNIQUELN,D.QTY,D.UNIQUELN,D.SHIP_DTS
ORDER BY D.UNIQUELN, D.SHIP_DTS
)
--SELECT * FROM DSeq
, Slices AS (
SELECT
LN = D.UNIQUELN,
FromQty = COALESCE(N.SchBal,0),
ToQty = D.SchBal,
D.SHIP_DTS
FROM
DSeq D
LEFT OUTER JOIN DSeq N
ON D.Seq -1 = N.Seq AND D.UNIQUELN=N.UNIQUELN
)
SELECT * FROM Slices
CURSOR APPROACH:
As has been stated, the best approach might be a cursor. Hopefully someone has a join method that will meet all the needs, but until then we are using a cursor.
In case someone is looking for a solution to this with a cursor approach, the code below is how we have done it. The user selects a date range and it produces the results. Please note that you HAVE to run the cursor for ALL records of the uniqueln even if they shipped prior to the selected date range, otherwise the FIFO application of shipments will be wrong.
DECLARE #startdate smalldatetime, #endDate smalldatetime
DECLARE #OnTime TABLE (Uniqueln varchar(10),DueQty int,dueDate smalldatetime,shipDate smalldatetime,shipQty int DEFAULT 0,daysLate int,balQty int)
DECLARE #uniqln1 varchar(10),#toQty int, #dueDate smalldatetime,#bQty int
DECLARE #uniqln2 varchar(10),#shipQty int, #shipDate smalldatetime
DECLARE ot_cursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT uniqueln,qty,ship_dts,qty
FROM #DueDates
ORDER BY uniqueln,ship_dts
OPEN ot_cursor;
FETCH NEXT FROM ot_cursor INTO #uniqln1,#toQty,#dueDate,#bQty
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE s_cursor CURSOR LOCAL FAST_FORWARD
FOR
SELECT Uniqueln,shippedqty,shipdate
FROM #PlDetail p
WHERE uniqueln = #uniqln1
ORDER BY 3
OPEN s_cursor ;
FETCH NEXT FROM s_cursor INTO #uniqln2,#shipQty,#shipDate
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #OnTime(Uniqueln,DueQty,dueDate,shipDate,shipQty,daysLate,balQty)
SELECT #uniqln1,#toQty,#dueDate,#shipDate,CASE WHEN #bQty>#shipQty THEN #shipQty ELSE #bQty END,DATEDIFF(d,#dueDate,#shipDate),CASE WHEN #bQty>#shipQty THEN #bQty-#shipQty ELSE 0 END
SET #bQty=#bQty-#shipQty
IF #bQty < 0
BEGIN
SET #shipQty = -#bQty
FETCH NEXT FROM ot_cursor INTO #uniqln1,#toQty,#dueDate,#bQty
END
ELSE
IF #bQty =0
BEGIN
BREAK
END
ELSE
BEGIN
SET #shipQty = #bQty
FETCH NEXT FROM s_cursor INTO #uniqln2,#shipQty,#shipDate
END
END
CLOSE s_cursor
DEALLOCATE s_cursor
FETCH NEXT FROM ot_cursor INTO #uniqln1,#toQty,#dueDate,#bQty
END
CLOSE ot_cursor
DEALLOCATE ot_cursor
SELECT * FROM #OnTime
WHERE shipDate BETWEEN #startdate AND #endDate
ORDER BY Uniqueln,dueDate
Tried putting something together using CTEs and a pidgin cross join between the tables. (don't ask, it was ugly)
Anywho, after a bit of reading, and knowing how large the tables are, I feel pretty safe answering this with... drumroll... use a cursor. It'll be ugly and slow, but the logic will make much more sense on paper. There's a lot to be said about maintainable code...
Update: Going on vacation. Here's what I was playing with.
DECLARE #DueDates TABLE (
uniqueln varchar(10),
ship_dts smalldatetime,
qty decimal(18,4))
INSERT INTO #DueDates
SELECT '51351621AS','1/1/2013',7
UNION ALL
SELECT '51351621AS','1/7/2013',7
UNION ALL
SELECT '51351621AS','1/14/2013',7
UNION ALL
SELECT '51351621AS','1/21/2013',7
UNION ALL
SELECT '51351621AS','1/28/2013',7
UNION ALL
SELECT 'V4351621AS','1/5/2013',10
UNION ALL
SELECT 'V4351621AS','1/10/2013',10
UNION ALL
SELECT 'V4351621AS','1/15/2013',10
UNION ALL
SELECT 'V4351621AS','1/20/2013',10
UNION ALL
SELECT 'V4351621AS','1/25/2013',10
DECLARE #PlDetail TABLE (
uniqueln varchar(10),
shipdate smalldatetime,
shippedqty decimal(18,4))
INSERT INTO #PlDetail
SELECT '51351621AS','1/1/2013',10
UNION ALL
SELECT '51351621AS','1/7/2013',10
UNION ALL
SELECT '51351621AS','1/14/2013',10
UNION ALL
SELECT '51351621AS','1/21/2013',5
UNION ALL
SELECT 'V4351621AS','1/5/2013',13
UNION ALL
SELECT 'V4351621AS','1/15/2013',9
UNION ALL
SELECT 'V4351621AS','1/25/2013',12
UNION ALL
SELECT 'V4351621AS','1/30/2013',10
UNION ALL
SELECT 'V4351621AS','2/5/2013',6
; WITH DueDates AS
(
SELECT b.*
FROM #DueDates a
JOIN #DueDates b
ON a.uniqueln = b.uniqueln
AND b.ship_dts >= a.ship_dts
)
, PlDetail AS
(
SELECT b.*
FROM #PlDetail a
JOIN #PlDetail b
ON a.uniqueln = b.uniqueln
AND b.shipdate >= a.shipdate
)
SELECT a.uniqueln
, SUM(a.qty) AS ordered_running_total
, SUM(b.shippedqty) AS shipped_running_total
, a.ship_dts
, b.shipdate
, SUM(b.shippedqty) - SUM(a.qty) AS leftover_running_total
FROM DueDates a
JOIN PlDetail b
ON a.uniqueln = b.uniqueln
AND a.ship_dts >= b.shipdate
GROUP BY a.uniqueln, a.ship_dts, b.shipdate
HAVING SUM(a.qty) <= SUM(b.shippedqty)
ORDER BY a.uniqueln, a.ship_dts, b.shipdate

Resources