CTE - recursively update quantity until total consumed - sql-server

I've been researching CTEs trying to determine if it's possible to recursively update inventory quantity records with an order quantity until the order quantity is consumed.
Here are the tables and records:
CREATE TABLE [dbo].[myOrder](
[Account] [float] NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL
) ON [PRIMARY]
insert into dbo.myOrder values (12345, 1, 50)
CREATE TABLE [dbo].[myInventory](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Account] [float] NOT NULL,
[InvDate] [numeric](18, 0) NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL,
[QuantitySold] [float] NOT NULL
) ON [PRIMARY]
insert into dbo.myInventory values (12345, 111287, 1, 45, 40)
insert into dbo.myInventory values (12345, 111290, 1, 40, 0)
insert into dbo.myInventory values (12345, 111290, 1, 12, 0)
insert into dbo.myInventory values (12345, 111291, 1, 25, 0)
The record in the myOrder table indicates that an order is to be created for account 12345 for item #1, quantity 50:
Account Item Quantity
------- ---- --------
12345 1 50
The inventory table shows that we have plenty of item #1 on hand for account 12345:
ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1 12345 111287 1 45 40
2 12345 111290 1 40 0
3 12345 111290 1 12 0
4 12345 111291 1 25 0
The goal is to start plugging in the order quantity of 50 into the inventory records until all 50 are consumed. Inventory records are ordered by the value in the InvDate column. Record 1 has 5 remaining quantity (45 - 40 = 5), which would leave us with 45 more to consume for the order. Record 2 can consume 40. Record 3 can consume the last 5. When the query completes the inventory records would look like this:
ID Account InvDate Item Quantity QuantitySold
-- ------- ------- ---- -------- ------------
1 12345 111287 1 45 45
2 12345 111290 1 40 40
3 12345 111290 1 12 5
4 12345 111291 1 25 0
Note: The inventory table stores QuantitySold, not QuantityRemaining, so you have to do the math (Quantity - QuantitySold) to determine how much quantity remains per inventory record.
I've gotten almost nowhere with the CTE. I've found plenty of examples for doing selects where you have 2 parts to your CTE - an initialization part and the recursive part UNIONed together. I could write this with a cursor, but I think it's possible to do with a CTE and I'd like to learn how.
If anyone can confirm this is possible with a CTE or explain how to set up the CTE, I'd appreciate it. Thanks!

--#inserted table mimics inserted virtual table from AFTER INSERT triggers on [dbo].[myOrder] table
DECLARE #inserted TABLE
(
[Account] [float] NOT NULL,
[Item] [float] NOT NULL,
[Quantity] [float] NOT NULL
);
INSERT #inserted
VALUES (12345, 1, 50);
WITH CteRowNumber
AS
(
SELECT inv.ID
,inv.Account
,inv.Item
,inv.Quantity
,inv.QuantitySold
,i.Quantity QuantityOrdered
,ROW_NUMBER() OVER(PARTITION BY inv.Account,inv.Item ORDER BY inv.ID ASC) RowNumber
FROM myInventory inv
INNER JOIN #inserted i ON inv.Account = i.Account
AND inv.Item = i.Item
WHERE inv.Quantity > inv.QuantitySold
), CteRecursive
AS
(
SELECT a.ID
,a.Account
,a.Item
,a.RowNumber
,CASE
WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold
ELSE a.QuantityOrdered
END QuantitySoldNew
,CASE
WHEN a.Quantity - a.QuantitySold < a.QuantityOrdered THEN a.Quantity - a.QuantitySold
ELSE a.QuantityOrdered
END RunningTotal
FROM CteRowNumber a
WHERE a.RowNumber = 1
UNION ALL
SELECT crt.ID
,crt.Account
,crt.Item
,crt.RowNumber
,CASE
WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN crt.Quantity - crt.QuantitySold
ELSE crt.QuantityOrdered - prev.RunningTotal
END QuantitySoldNew
,CASE
WHEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold) < crt.QuantityOrdered THEN prev.RunningTotal + (crt.Quantity - crt.QuantitySold)
ELSE crt.QuantityOrdered
END RunningTotal
FROM CteRecursive prev
INNER JOIN CteRowNumber crt ON prev.Account = crt.Account
AND prev.Item = crt.Item
AND prev.RowNumber + 1 = crt.RowNumber
WHERE prev.RunningTotal < crt.QuantityOrdered
)
SELECT cte.ID
,cte.Account
,cte.Item
,cte.QuantitySoldNew
FROM CteRecursive cte;
--or CteRecursive can be used to update QuantitySold column from [dbo].[myInventory] table
--UPDATE myInventory
--SET QuantitySold = inv.QuantitySold + cte.QuantitySoldNew
--FROM myInventory inv
--INNER JOIN CteRecursive cte ON inv.ID = cte.ID;

Related

How to get the year wise employee as per the promotion

How to get the year wise employee as per the promotion.
Example:
Employee Number 'A-001' Join on '01-07-2013' then the O/P will be showing in image.
As Employee Number 'A-001' there is no promotion in 2014 then in Last_Designation,Promoted_Designation, Last_Gross and Promoted_Gross
need to be same as 2013 or previous one.
Below is my SQL Query with Data.
CREATE TABLE [dbo].[Employee](
[Emp_No] [numeric](18, 0) NULL,
[Emp_Number] [nvarchar](50) NULL,
[Emp_Name] [nvarchar](50) NULL,
[Emp_JoiningDate] [date] NULL,
[Emp_ResignDate] [date] NULL,
[Emp_Status] [nvarchar](50) NULL,
[Emp_CurrentDesignation] [nvarchar](50) NULL,
[Emp_CurrentGross] [numeric](18, 0) NULL
) ON [PRIMARY]
GO
INSERT INTO [UserDB].[dbo].[Employee]
([Emp_No]
,[Emp_Number]
,[Emp_Name]
,[Emp_JoiningDate]
,[Emp_ResignDate]
,[Emp_Status]
,[Emp_CurrentDesignation]
,[Emp_CurrentGross])
VALUES
(1,'A-001','Alex','2012-07-01',null,'On Board','Trainee3',2000)
GO
INSERT INTO [UserDB].[dbo].[Employee]
([Emp_No]
,[Emp_Number]
,[Emp_Name]
,[Emp_JoiningDate]
,[Emp_ResignDate]
,[Emp_Status]
,[Emp_CurrentDesignation]
,[Emp_CurrentGross])
VALUES
(2,'A-002','Smith','2014-07-01','2015-07-01','Resigned','HR1',1500)
GO
CREATE TABLE [dbo].[Promotion](
[Prom_No] [numeric](18, 0) NULL,
[Prom_EmpNo] [numeric](18, 0) NULL,
[Last_Designation] [nvarchar](500) NULL,
[Promoted_Designation] [nvarchar](500) NULL,
[WEF_Date] [date] NULL,
[Promoted_Gross] [numeric](18, 0) NULL,
[Last_Gross] [numeric](18, 0) NULL
) ON [PRIMARY]
GO
INSERT INTO [UserDB].[dbo].[Promotion]
([Prom_No]
,[Prom_EmpNo]
,[Last_Designation]
,[Promoted_Designation]
,[WEF_Date]
,[Promoted_Gross]
,[Last_Gross])
VALUES
(1,1,'Trainee1','Trainee2','2013-11-01',1000,500)
GO
INSERT INTO [UserDB].[dbo].[Promotion]
([Prom_No]
,[Prom_EmpNo]
,[Last_Designation]
,[Promoted_Designation]
,[WEF_Date]
,[Promoted_Gross]
,[Last_Gross])
VALUES
(2,1,'Trainee2','Trainee3','2015-03-01',2000,1000)
GO
Try my script with various sample data and let me know if it not working any particular sample data.
First you can create and populate one year table which can be
permanent .In my example it is temporary.
create table #Year(yr int primary key)
insert into #Year
select (ROW_NUMBER()over(order by number)+1900) from master..spt_values
--select * from #Year
Main query start from here.
;WITH CTE
AS (
SELECT Emp_No NewEmpNo
,min(year(emp_joiningdate)) minYear
,max(year(case when Emp_ResignDate is not null then Emp_ResignDate else getdate() END)) maxYear
FROM [dbo].[Employee]
GROUP BY Emp_No
)
,CTE1
AS (
SELECT c.yr
,p.NewEmpNo
,e.*
FROM cte p
CROSS APPLY (
SELECT yr
FROM #Year c
WHERE c.yr >= minYear
AND c.yr <= maxYear
) c
LEFT JOIN (
SELECT e.*
,p.*
,year(isnull(p.WEF_Date,e.Emp_JoiningDate)) PYr
FROM dbo.employee E
LEFT JOIN [dbo].[Promotion] P ON e.Emp_No = p.Prom_EmpNo
) e ON p.NewEmpNo = e.Emp_No
AND c.yr = e.pyr
)
--select * from CTE1
,CTE2 as
(
SELECT yr
,NewEmpNo
,isnull(c.Emp_Number, prv.Emp_Number) Emp_Number
,isnull(c.Emp_Name, prv.Emp_Name) Emp_Name
,isnull(c.Emp_JoiningDate, prv.Emp_JoiningDate) Emp_JoiningDate
,isnull(c.Emp_ResignDate, prv.Emp_ResignDate) Emp_ResignDate
,isnull(c.Emp_Status, prv.Emp_Status) Emp_Status
,isnull(c.Emp_CurrentDesignation, prv.Emp_CurrentDesignation) Emp_CurrentDesignation
,isnull(c.Emp_CurrentGross, prv.Emp_CurrentGross) Emp_CurrentGross
,COALESCE(c.Last_Designation, prv.Last_Designation
,c.Emp_CurrentDesignation,prv.Emp_CurrentDesignation) Last_Designation
,COALESCE(c.Promoted_Designation, prv.Promoted_Designation
,c.Emp_CurrentDesignation,prv.Emp_CurrentDesignation) Promoted_Designation
,COALESCE(c.Last_Gross, prv.Last_Gross
,c.Emp_CurrentGross, prv.Emp_CurrentGross) Last_Gross
,COALESCE(c.Promoted_Gross, prv.Promoted_Gross
,c.Emp_CurrentGross, prv.Emp_CurrentGross) Promoted_Gross
,prv. PYr
--,prv1.*
FROM cte1 c
outer APPLY (
SELECT TOP 1 prve.*
,prvp.*
,year(isnull(WEF_Date,Emp_JoiningDate))Pyr
FROM dbo.employee prve
LEFT JOIN [dbo].[Promotion] prvp ON prve.Emp_No = prvp.Prom_EmpNo
WHERE (c.yr >= year(isnull(WEF_Date,Emp_JoiningDate))
)
AND c.NewEmpNo = prve.Emp_No
ORDER BY isnull(WEF_Date,Emp_JoiningDate) DESC
) prv
)
select
yr
,NewEmpNo
,isnull(c.Emp_Number, prv.Emp_Number) Emp_Number
,isnull(c.Emp_Name, prv.Emp_Name) Emp_Name
,isnull(c.Emp_JoiningDate, prv.Emp_JoiningDate) Emp_JoiningDate
,isnull(c.Emp_ResignDate, prv.Emp_ResignDate) Emp_ResignDate
,isnull(c.Emp_Status, prv.Emp_Status) Emp_Status
,isnull(c.Emp_CurrentDesignation, prv.Emp_CurrentDesignation) Emp_CurrentDesignation
,isnull(c.Emp_CurrentGross, prv.Emp_CurrentGross) Emp_CurrentGross
,COALESCE(c.Last_Designation, prv.Last_Designation
,c.Emp_CurrentDesignation,prv.Emp_CurrentDesignation) Last_Designation
,COALESCE(c.Promoted_Designation, prv.Promoted_Designation
,c.Emp_CurrentDesignation,prv.Emp_CurrentDesignation) Promoted_Designation
,COALESCE(c.Last_Gross, prv.Last_Gross
,c.Emp_CurrentGross, prv.Emp_CurrentGross) Last_Gross
,COALESCE(c.Promoted_Gross, prv.Promoted_Gross
,c.Emp_CurrentGross, prv.Emp_CurrentGross) Promoted_Gross
,prv. PYr
from cte2 c
outer apply(
SELECT TOP 1 prve.*
,prvp.*
,year(isnull(WEF_Date,Emp_JoiningDate))Pyr
FROM dbo.employee prve
LEFT JOIN [dbo].[Promotion] prvp ON prve.Emp_No = prvp.Prom_EmpNo
WHERE (c.yr <= year(isnull(WEF_Date,Emp_JoiningDate))
)
AND c.NewEmpNo = prve.Emp_No
ORDER BY isnull(WEF_Date,Emp_JoiningDate)
)prv
ORDER BY yr
DROP TABLE #Year
The CTE [Year] is to get the list of years from the Emp_JoiningDate and ResignDate. After that just CROSS join to the Employee table
For the promotion information (prev and promoted), use OUTER APPLY to get the last row by WEF_Date
; with
[Year] as
(
select [Year] = min(datepart(year, e.Emp_JoiningDate))
from [Employee] e
union all
select [Year] = y.[Year] + 1
from [Year] y
where [Year] < datepart(year, getdate())
)
select y.[Year], e.Emp_No, e.Emp_Number, e.Emp_Name, e.Emp_JoiningDate,
e.Emp_ResignDate, e.Emp_Status,
e.Emp_CurrentDesignation, e.Emp_CurrentGross,
Last_Designation = coalesce(p.Last_Designation, e.Emp_CurrentDesignation),
Promoted_Designation= coalesce(p.Promoted_Designation, e.Emp_CurrentDesignation),
Last_Gross = coalesce(p.Last_Gross, e.Emp_CurrentGross),
Promoted_Gross = coalesce(p.Promoted_Gross, e.Emp_CurrentGross)
from [Employee] e
cross join [Year] y
outer apply
(
select top 1 *
from [Promotion] x
where x.Prom_EmpNo = e.Emp_No
and datepart(year, x.[WEF_Date]) <= y.[Year]
order by x.WEF_Date desc
) p
where y.[Year] >= datepart(year, e.Emp_JoiningDate)
and y.[Year] <= datepart(year, isnull(e.Emp_ResignDate, getdate()))
order by y.[Year], e.Emp_No
RESULT
2013 1 A-001 Alex 2013-07-01 NULL On Board Trainee3 2000 Trainee1 Trainee2 500 1000
2014 1 A-001 Alex 2013-07-01 NULL On Board Trainee3 2000 Trainee1 Trainee2 500 1000
2014 2 A-002 Smith 2014-07-01 2015-07-01 Resigned HR1 1500 HR1 HR1 1500 1500
2015 1 A-001 Alex 2013-07-01 NULL On Board Trainee3 2000 Trainee2 Trainee3 1000 2000
2015 2 A-002 Smith 2014-07-01 2015-07-01 Resigned HR1 1500 HR1 HR1 1500 1500
2016 1 A-001 Alex 2013-07-01 NULL On Board Trainee3 2000 Trainee2 Trainee3 1000 2000
2017 1 A-001 Alex 2013-07-01 NULL On Board Trainee3 2000 Trainee2 Trainee3 1000 2000

Selecting records for deletions based on relationship with previous and next records

I have a SQL Server 2014 table with millions of gps coordinates, each at a particular time. However the interval between the registrations is not fixed and varies from 1 second to a couple of hours. I only want to keep one measurement every 4 minutes, so the other records have to be deleted.
I tried a WHILE loop in T-SQL that traverses every record, with inside the loop a select statement with a double CROSS APPLY to only return a record if it sits in beween 2 other records which are not more than 4 minutes apart. However this strategy turns out to be too slow.
Can this be done with a set-based solution ? Or is there a way to speed-up this query ? (the test query below is just printing, not yet deleting)
SELECT * INTO #myTemp FROM gps ORDER BY TimePoint asc
declare #Id Uniqueidentifier
declare #d1 varchar(19)
declare #d2 varchar(19)
declare #d3 varchar(19)
While EXISTS (select * from #myTemp )
BEGIN
select top 1 #Id = ID FROM #myTemp order by TimePoint asc
SELECT
#d1 = convert(varchar(19), a.justbefore, 121),
#d2 = convert(varchar(19), b.tijdstip, 121),
#d3 = convert(varchar(19), c.justafter, 121)
FROM Gps B CROSS APPLY
(
SELECT top 1 TimePoint as justbefore
FROM Gps
WHERE (B.TimePoint > TimePoint ) AND (B.Id = #Id )
ORDER by TimePoint desc
) A
CROSS APPLY (
SELECT top 1 TimePoint as justafter
FROM Gps
WHERE (Datediff(n,A.justbefore,TimePoint ) between -4 AND 0)
AND (B.TimePoint < TimePoint )
ORDER by TimePoint asc
) C
print 'ID=' + Cast(#id as varchar(50))
+ ' / d1=' + #d1 + ' / d2=' + #d2 + ' / d3=' + #d3
DELETE #myTemp where Id = #id
END
--
Sample data:
Id TimePoint Lat Lon
1 20170725 13:05:27 12,256 24,123
2 20170725 13:10:27 12,254 24,120
3 20170725 13:10:29 12,253 24,125
4 20170725 13:11:55 12,259 24,127
5 20170725 13:11:59 12,255 24,123
6 20170725 13:14:28 12,254 24,126
7 20170725 13:16:52 12,259 24,121
8 20170725 13:20:53 12,257 24,125
In this case records 3,4,5 should be deleted.
Record 7 should stay as the gap between 7 and 8 is longer than 4 minutes.
Looking at the numbers... It looks like 1 & 2 stay (5 mins apart)...3, 4, & 5 should go... 6 stays (4 mins from 2)... 7 should go (only 2 mins from 6) and 8 stays (6 mins from 6)...
If this is correct, the following will do what you're looking for...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Id INT NOT NULL PRIMARY KEY CLUSTERED,
TimePoint DATETIME2(0) NOT NULL,
Lat DECIMAL(9,3),
Lon DECIMAL(9,3)
);
INSERT #TestData (Id, TimePoint, Lat, Lon) VALUES
(1, '20170725 13:05:27', 12.256, 24.123),
(2, '20170725 13:10:27', 12.254, 24.120),
(3, '20170725 13:10:29', 12.253, 24.125),
(4, '20170725 13:11:55', 12.259, 24.127),
(5, '20170725 13:11:59', 12.255, 24.123),
(6, '20170725 13:14:28', 12.254, 24.126),
(7, '20170725 13:16:52', 12.259, 24.121),
(8, '20170725 13:20:53', 12.257, 24.125);
-- SELECT * FROM #TestData td;
--================================================================================
WITH
cte_AddLag AS (
SELECT
td.Id, td.TimePoint, td.Lat, td.Lon,
MinFromPrev = DATEDIFF(mi, LAG(td.TimePoint, 1) OVER (ORDER BY td.TimePoint), td.TimePoint)
FROM
#TestData td
),
cte_TimeGroup AS (
SELECT
*,
TimeGroup = ISNULL(SUM(al.MinFromPrev) OVER (ORDER BY al.TimePoint ROWS UNBOUNDED PRECEDING) / 4, 0)
FROM
cte_AddLag al
)
SELECT TOP 1 WITH TIES
tg.Id,
tg.TimePoint,
tg.Lat,
tg.Lon
FROM
cte_TimeGroup tg
ORDER BY
ROW_NUMBER() OVER (PARTITION BY tg.TimeGroup ORDER BY tg.TimePoint);
Results...
Id TimePoint Lat Lon
----------- --------------------------- --------------------------------------- ---------------------------------------
1 2017-07-25 13:05:27 12.256 24.123
2 2017-07-25 13:10:27 12.254 24.120
6 2017-07-25 13:14:28 12.254 24.126
8 2017-07-25 13:20:53 12.257 24.125
HTH, Jason

sql server 2014 group by date extract several fields

I have a table that looks like this:
CREATE TABLE dbo.Mails (
ID int IDENTITY(1, 1) NOT NULL,
Reference nvarchar(20) COLLATE Latin1_General_CI_AS NULL,
Email nvarchar(70) NOT NULL,
ETS datetime NULL, --Estimated Time of Shipping
ATS datetime NULL, --Actual Time of Shipping
ReadOn datetime NULL,
Unsubscribed datetime NULL,
Bounced datetime NULL,
BouncedReason nvarchar(30) COLLATE Latin1_General_CI_AS NULL,
Active bit DEFAULT 1 NULL
)
I need to show info on a chart, and I need to group by Date.
therefore if I want to group details by ReadOn field for a certain campaign I build the following query
Select
CAST(readOn as date) [date],
COUNT(*) [read]
FROM Mails m
WHERE m.Reference=#Reference
GROUP BY CAST(readOn as date)
ORDER BY CAST(readOn as date) ASC
and I get something like this:
sDate read
NULL 360
2016-05-05 67
2016-05-06 123
2016-05-07 84
2016-05-08 62
2016-05-09 89
2016-05-10 17
2016-05-11 12
2016-05-12 8
2016-05-13 4
2016-05-14 4
But I would like to extract, in the same query, not only ReadOn field, but also other fields like ETS, ATS, Unsubscribed/Read & Unread and Bounced
and get something like this
sDate read ETS ATS Bounced Unsub./Read Unsub/Unread
NULL 360
2016-05-05 67 830 570 27 7 3
2016-05-06 123 0 260 4 9 5
2016-05-07 84 0 0 0 2 2
2016-05-08 62 0 0 0 2 4
2016-05-09 89 0 0 0 7 1
2016-05-10 17 0 0 0 5 6
2016-05-11 12 0 0 0 8 2
2016-05-12 8 0 0 0 1 3
2016-05-13 4 0 0 0 0 2
2016-05-14 4 0 0 0 0 2
Is there an easier way than building 6 different queries?
can at least indicate the path to follow?
Thanks
Joe
You can do it with some pre-processing and a PIVOT. In this example, I've put the query into a stored procedure, so that it is contained and easy to test. I'm doing the pre-processing in a CTE, to keep the main query tidy.
First, create the table and populate it.
CREATE TABLE dbo.Mails
(
ID int IDENTITY(1, 1) NOT NULL,
Reference nvarchar(20) COLLATE Latin1_General_CI_AS NULL,
ETS datetime NULL, --Estimated Time of Shipping
ATS datetime NULL, --Actual Time of Shipping
ReadOn datetime NULL,
Unsubscribed datetime NULL,
Bounced bit DEFAULT 0 NULL,
BouncedReason nvarchar(30) COLLATE Latin1_General_CI_AS NULL,
Active bit DEFAULT 1 NULL
);
GO
INSERT INTO dbo.Mails (Reference, ETS, ATS, ReadOn, Unsubscribed, Bounced)
VALUES
(N'ABC', '2015-05-05', '2015-05-05', '2015-05-05', NULL, 0),
(N'ABC', '2015-05-06', '2015-05-07', '2015-05-08', NULL, 0),
(N'ABC', '2015-05-05', '2015-05-05', '2015-05-07', NULL, 0),
(N'ABC', '2015-05-07', '2015-05-08', '2015-05-09', NULL, 0),
(N'ABC', '2015-05-06', '2015-05-07', '2015-05-09', '2015-05-09', 0),
(N'ABC', '2015-05-06', '2015-05-07', NULL, '2015-05-08', 0);
Then create a stored procedure with a parameter #Reference. I'm using a CTE to create a two column row set, with Date and Type as the columns. Then, in the main SELECT statement it's being pivoted to give the result you want.
The row set produced by the CTE looks like this.
Note: I haven't included the Bounced column, because I'm not clear what the requirement is for that; it's not a date column. However you should be able to extend this example quite easily.
CREATE PROCEDURE dbo.up_ReportMails
(
#Reference nvarchar(20)
)
AS
WITH cte AS
(
SELECT CAST(ReadOn AS date) AS 'Date', 'R' AS 'Type'
FROM dbo.Mails
WHERE Reference = #Reference AND ReadOn IS NOT NULL
UNION ALL
SELECT CAST(ETS AS date), 'E'
FROM dbo.Mails
WHERE Reference = #Reference AND ETS IS NOT NULL
UNION ALL
SELECT CAST(ATS AS date), 'A'
FROM dbo.Mails
WHERE Reference = #Reference AND ATS IS NOT NULL
UNION ALL
SELECT CAST(Unsubscribed AS date), 'U'
FROM dbo.Mails
WHERE Reference = #Reference AND UNSUBSCRIBED IS NOT NULL AND ReadOn IS NOT NULL
UNION ALL
SELECT CAST(Unsubscribed AS date), 'V'
FROM dbo.Mails
WHERE Reference = #Reference AND UNSUBSCRIBED IS NOT NULL AND ReadOn IS NULL
)
SELECT [Date], [R] AS 'Read', [E] AS 'ETS', [A] AS 'ATS', [U] AS 'Unsub/Read', [V] As 'Unsub/Unread'
FROM
(SELECT [Date], [Type]
FROM cte) AS C
PIVOT
(
COUNT([Type])
FOR [Type] IN ([R], [E], [A], [U], [V])
) AS PivotTable
ORDER BY [Date];
Then we can test it.
EXEC dbo.up_ReportMails #Reference=N'ABC';
I've tested this code and it works. Assuming you have a calendar table (if you don't have one, you can Google it and make one in about 10 minutes - they're very simple and will save you loads of time):
DECLARE #StartDate date = '01/01/2016'
DECLARE #EndDate date = '05/06/2016'
SELECT C.BaseDate,
ISNULL(SUM(CASE WHEN C.BaseDate = CAST(M.ETS AS DATE) THEN 1 END), 0) AS [ETS],
ISNULL(SUM(CASE WHEN C.BaseDate = CAST(M.ATS AS DATE) THEN 1 END), 0) AS [ATS],
ISNULL(SUM(CASE WHEN C.BaseDate = CAST(M.ReadOn AS DATE) THEN 1 END), 0) AS [Read On],
ISNULL(SUM(CASE WHEN C.BaseDate = CAST(M.Unsubscribed AS DATE) THEN 1 END), 0) AS [Unsubscribed],
ISNULL(SUM(CASE WHEN C.BaseDate = CAST(M.Bounced AS DATE) THEN 1 END), 0) AS [Bounced]
FROM Calendar C
LEFT OUTER JOIN Mails M
ON C.BaseDate = CAST(M.ETS AS DATE)
OR C.BaseDate = CAST(M.ATS AS DATE)
OR C.BaseDate = CAST(M.ReadOn AS DATE)
OR C.BaseDate = CAST(M.Unsubscribed AS DATE)
OR C.BaseDate = CAST(M.Bounced AS DATE)
WHERE C.BaseDate BETWEEN #StartDate AND #EndDate
GROUP BY C.BaseDate
Basically what you're doing is selecting every date from the calendar table within your date range, and then joining it to your mails table if ANY of the datetimes match that date. The purpose of the left join is so that dates on which nothing occurs are still returned in your result set. They will all be zeros, but it's better for consistency and in case someone wants to calculate averages from your report.
Once you have all the dates - and all of the records that have a matching datetime, you just need to count how many, for each date, have a matching ETS, how many have a matching ATS, so on and so forth. Last, you group by the calendar date and you're all done.

how to join 2 tables but have the same result count as table a

here is the dilemma i am having...
i have 2 tables
create table #orders
(orderNumber int,qty int,sku varchar(250),barcode varchar(250))
create table #allItemsInBox
([id] int,[date] date,[localsku] varchar(250),[box] varchar(250),barcode varchar(250))
i need to join the 2 tables on [barcode] and only have 1 result in the final table for every row in #allItemsInBox
please note [#allItemsInBox].[id] is unique the other fields in [#allItemsInBox] may not be
how would i go about doing something like this?
sample data:
[#orders]
(1,0,'10','10')
(1,0,'20','20')
(3,0,'20','20')
(4,0,'30','30')
(5,0,'40','40')
(6,0,'50','50')
#allItemsInBox
(1,'12/3/2014',10,'Box1',10)
(2,'12/2/2014',20,'Box2',20)
(3,'12/1/2014',20,'Box3',20)
(4,'11/30/2014',20,'Box4',20)
(5,'11/29/2014',30,'Box5',30)
(6,'11/28/2014',40,'Box6',40)
(7,'11/27/2014',60,'Box8',60)
(8,'11/27/2014',50,'Box10',50)
#output
(ordernumber int,uniqueitemID int,localsku varchar(250),box varchar(250))
(1,1,10,'Box1')
(1,2,20,'Box2')
(3,3,10,'Box3')
(4,5,30,'Box5')
(5,6,40,'Box6')
(6,8,50,'Box10')
This is quick but works. Depending on the size of your data this might be not the best way performance wise. But this will give you a start
DECLARE #orders TABLE (
orderNumber int,
qty int,
sku varchar(250),
barcode varchar(250)
)
DECLARE #allItemsInBox TABLE (
[id] int,
[date] date,
[localsku] varchar(250),
[box] varchar(250),
barcode varchar(250)
)
INSERT INTO #orders VALUES
(1,0,'10','10'),
(1,0,'20','20'),
(3,0,'20','20'),
(4,0,'30','30'),
(5,0,'40','40'),
(6,0,'50','50')
INSERT INTO #allItemsInBox VALUES
(1,'2014-12-03',10,'Box1',10),
(2,'2014-12-02',20,'Box2',20),
(3,'2014-12-01',20,'Box3',20),
(4,'2014-11-30',20,'Box4',20),
(5,'2014-11-29',30,'Box5',30),
(6,'2014-11-28',40,'Box6',40),
(7,'2014-11-27',60,'Box8',60),
(8,'2014-11-27',50,'Box10',50)
SELECT
orders.orderNumber AS ordernumber
,(SELECT TOP 1 allItems.id FROM #allItemsInBox allItems WHERE allItems.barcode = orders.barcode AND allItems.id >= orders.orderNumber ORDER BY allItems.id) AS uniqueitemID
,(SELECT TOP 1 allItems.localsku FROM #allItemsInBox allItems WHERE allItems.barcode = orders.barcode AND allItems.id >= orders.orderNumber ORDER BY allItems.id) AS localsku
,(SELECT TOP 1 allItems.box FROM #allItemsInBox allItems WHERE allItems.barcode = orders.barcode AND allItems.id >= orders.orderNumber ORDER BY allItems.id) AS box
FROM
#orders orders
Results in:
ordernumber uniqueitemID localsku box
1 1 10 Box1
1 2 20 Box2
3 3 20 Box3
4 5 30 Box5
5 6 40 Box6
6 8 50 Box10
edit: I updated the answer. You now have the same output as you specified in your example/question

Trouble creating SQL Query to update line item amounts

I'm going to preface this question with the disclaimer that creating what I call "complex" queries isn't in my forte. Most of the time, there is a much simpler way to accomplish what I'm trying to accomplish so if the below query isn't up to par, I apologize.
With that said, I have a table that keeps track of Vendor Invoices and Vendor Invoice Items (along with a Vendor Invoice Type and Vendor Invoice Item Type). Our bookkeeper wants a report that simply shows: Vendor | Location | Inv Number | Inv Type | Item Type | Inv Date | Rental Fee | Restock Fee | Shipping Fee | Line Item Cost | Total (Line Item + Fees)
Most of the time, one vendor invoice is one line. However, there are exceptions where a vendor invoice can have many item types, thus creating two rows. Not a big deal EXCEPT the fees (Rental, Restock, Shipping) are attached to the Vendor Invoice table. So, I first created a query that checks the temp table for Invoices that have multiple rows, takes the last row, and zero's out the fees. So that only one line item would have the fee. However, our bookkeeper doesn't like that. Instead, she'd like the fees to be "distributed" among the line items.
So, if a vendor invoice has a $25 shipping charge, has two line items, then each line item would be $12.50.
After working with the query, I got it to update the Last Row to be the adjusted amount but row 1+ would have the original amount.
I'm going to post my entire query here (again - I'm sorry that this may not be the best looking query; however, suggestions are always welcome)
DROP TABLE #tVendorInvoiceReport
DROP TABLE #tSummary
SELECT v.Name AS Vendor ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr AS InvoiceType ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120) VendorInvDate ,
vi.RentalFee ,
vi.RestockFee ,
vi.ShippingFee ,
SUM(vii.TotalUnitCost) TotalItemCost ,
CONVERT(MONEY, 0) TotalInvoice ,
RowID = IDENTITY( INT,1,1)
INTO #tVendorInvoiceReport
FROM dbo.vVendorInvoiceItems AS vii
JOIN dbo.VendorInvoices AS vi ON vii.VendorInvID = vi.VendorInvID
JOIN dbo.Vendors AS v ON vi.VendorID = v.VendorID
JOIN dbo.VendorInvoiceTypes AS vit ON vi.VendorInvTypeID = vit.VendorInvTypeID
WHERE vi.VendorInvDate >= '2012-01-01'
AND vi.VendorInvDate <= '2012-01-31'
GROUP BY v.Name ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120) ,
vi.RentalFee ,
vi.RestockFee ,
vi.ShippingFee
ORDER BY v.Name ,
vii.Location ,
vi.VendorInvNumber ,
vit.Descr ,
vii.VendorInvoiceItemType ,
CONVERT(VARCHAR(10), vi.VendorInvDate, 120)
SELECT VendorInvNumber ,
COUNT(RowID) TotalLines ,
MAX(RowID) LastLine
INTO #tSummary
FROM #tVendorInvoiceReport
GROUP BY VendorInvNumber
WHILE ( SELECT COUNT(LastLine)
FROM #tSummary AS ts
WHERE TotalLines > 1
) > 0
BEGIN
DECLARE #LastLine INT
DECLARE #NumItems INT
SET #LastLine = ( SELECT MAX(LastLine)
FROM #tSummary AS ts
WHERE TotalLines > 1
)
SET #NumItems = ( SELECT COUNT(VendorInvNumber)
FROM #tVendorInvoiceReport
WHERE VendorInvNumber IN (
SELECT VendorInvNumber
FROM #tSummary
WHERE LastLine = #LastLine )
)
UPDATE #tVendorInvoiceReport
SET RentalFee = ( RentalFee / #NumItems ) ,
RestockFee = ( RestockFee / #NumItems ) ,
ShippingFee = ( ShippingFee / #NumItems )
WHERE RowID = #LastLine
DELETE FROM #tSummary
WHERE LastLine = #LastLine
--PRINT #NumItems
END
UPDATE #tVendorInvoiceReport
SET TotalInvoice = ( TotalItemCost + RentalFee + RestockFee + ShippingFee )
SELECT Vendor ,
Location ,
VendorInvNumber ,
InvoiceType ,
VendorInvoiceItemType ,
VendorInvDate ,
RentalFee ,
RestockFee ,
ShippingFee ,
TotalItemCost ,
TotalInvoice
FROM #tVendorInvoiceReport AS tvir
I sincerely appreciate anyone who took the time to read this and attempt to point me in the right direction.
Thank you,
Andrew
PS - I did try and remove "WHERE RowID = #LastLine" from the first Update, but that changed the Shipping Fees for the first line with two items to "0.0868" instead of 12.50 ($25/2)
If I understand correctly, you're looking for a way to split something like an invoice shipping fee over one or more invoice items.
I created some sample invoice and invoice item tables shown below and used the
over(partition) clause to split out the shipping per item.
-- sample tables
declare #Invoice table (InvoiceID int, customerID int, Date datetime, ShippingFee float)
declare #InvoiceItem table (InvoiceItemID int identity, InvoiceID int, ItemDesc varchar(50), Quantity float, ItemPrice float)
-- Example 1
insert #Invoice values(1, 800, getdate(), 20);
insert #InvoiceItem values(1, 'Widget', 1, 10.00)
insert #InvoiceItem values(1, 'Wing Nut', 5, 2.00)
insert #InvoiceItem values(1, 'Doodad', 8, 0.50)
insert #InvoiceItem values(1, 'Thingy', 3, 1.00)
-- Example 2
insert #Invoice values(2, 815, getdate(), 15);
insert #InvoiceItem values(2, 'Green Stuff', 10, 1.00)
insert #InvoiceItem values(2, 'Blue Stuff', 10, 1.60)
-- Example 3
insert #Invoice values(3, 789, getdate(), 15);
insert #InvoiceItem values(3, 'Widget', 10, 1.60)
-- query
select
n.InvoiceID,
n.InvoiceItemID,
n.ItemDesc,
n.Quantity,
n.ItemPrice,
ExtendedPrice = n.Quantity * n.ItemPrice,
Shipping = i.ShippingFee / count(n.InvoiceItemID) over(partition by n.InvoiceID)
from #InvoiceItem n
join #Invoice i on i.InvoiceID = n.InvoiceID
Output:
InvoiceID InvoiceItemID ItemDesc Quantity ItemPrice ExtendedPrice Shipping
1 1 Widget 1 10 10 5
1 2 Wing Nut 5 2 10 5
1 3 Doodad 8 0.5 4 5
1 4 Thingy 3 1 3 5
2 5 Green Stuff 10 1 10 7.5
2 6 Blue Stuff 10 1.6 16 7.5
3 7 Widget 10 1.6 16 15

Resources