How to get the year wise employee as per the promotion - sql-server

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

Related

SQL query with function

I have a table of data which i am using a count statement to get the amount of records for the submission date
example
AuditId Date Crew Shift Cast ObservedBy 2ndObserver AuditType Product
16 2017-06-27 3 Day B1974, B1975 Glen Mason NULL Identification Billet
20 2017-06-29 1 Day 9879 Corey Lundy NULL Identification Billet
21 2017-06-29 4 Day T9627, T9625 Joshua Dwyer NULL ShippingPad Tee
22 2017-06-29 4 Day NULL Joshua Dwyer NULL Identification Billet
23 2017-06-29 4 Day S9874 Joshua Dwyer NULL ShippingPad Slab
24 2017-06-29 4 Day Bay 40 Joshua Dwyer NULL Identification Billet
Basically I am using the following code to get my results
SELECT YEAR([Date]) as YEAR, CAST([Date] as nvarchar(25)) AS [Date], COUNT(*) as "Audit Count"
FROM AuditResults
where AuditType = 'Identification' AND Product = 'Billet'
group by Date
this returns example
YEAR Date Audit Count
2017 2017-06-27 1
2017 2017-06-29 3
Now I want to be able to retrieve all dates even if blank
so I would like the return to be
YEAR Date Audit Count
2017 2017-06-27 1
2017 2017-06-28 0
2017 2017-06-29 3
I have the following function I am trying to use:
ALTER FUNCTION [dbo].[fnGetDatesInRange]
(
#FromDate datetime,
#ToDate datetime
)
RETURNS #DateList TABLE (Dt date)
AS
BEGIN
DECLARE #TotalDays int, #DaysCount int
SET #TotalDays = DATEDIFF(dd,#FromDate,#ToDate)
SET #DaysCount = 0
WHILE #TotalDays >= #DaysCount
BEGIN
INSERT INTO #DateList
SELECT (#ToDate - #DaysCount) AS DAT
SET #DaysCount = #DaysCount + 1
END
RETURN
END
How do I use my select statement with this function? or is there a better way?
cheers
Try this;
ALTER FUNCTION [dbo].[fnGetDatesInRange]
(
#FromDate datetime,
#ToDate datetime
)
RETURNS #YourData TABLE ([Year] int, DateText nvarchar(25),[Audit Count] int)
AS
begin
insert into #YourData
SELECT
YEAR(allDates.[Date]) as YEAR,
CAST(allDates.[Date] as nvarchar(25)) AS [Date],
COUNT(r.Product) as "Audit Count"
from
(
SELECT
[date]=convert(datetime, CONVERT(float,d.Seq))
FROM
(
select top 100000 row_number() over(partition by 1 order by A.name) as Seq
from syscolumns A, syscolumns B
)d
)allDates
left join
AuditResults r on r.[Date]=allDates.[date] and r.AuditType = 'Identification' AND r.Product = 'Billet'
where
allDates.[Date]>=#FromDate and allDates.[Date]<=#ToDate
group by
allDates.[Date]
return
end
The key is the 'allDates' section ;
SELECT
[date]=convert(datetime, CONVERT(float,d.Seq))
FROM
(
select top 100000 row_number() over(partition by 1 order by A.name) as Seq
from syscolumns A, syscolumns B
)d
This will return all dates between 1900 and 2173 (in this example). Limit that as you need but a nice option. A ton of different ways to approach this clearly
you have to create another table calendar as (Mysql)- idea is the same on all RDBMS-
CREATE TABLE `calendar` (
`dt` DATE NOT NULL,
UNIQUE INDEX `calendar_dt_unique` (`dt`)
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
and fill with date data.
more details

SQL Query time spent between certain value

I have a database for all temperatures the last 10 years.
Now I want to find all periods where the temperature was above ex. 15 degree.
Simplified example:
...
2015-05-10 12
2015-05-11 15 |
2015-05-12 16 |
2015-05-13 17 |
2015-05-14 16 |
2015-05-15 15 |
2015-05-16 12
2015-05-17 11
2015-05-18 15 |
2015-05-19 12
2015-05-20 18 |
...
Så now I want get all time periods like this:
Min Max
2015-05-11 2015-05-15
2015-05-18 2015-05-18
2015-05-20 2015-05-20
Any suggestion of how this query will look like ?
You could use CTE
CREATE TABLE #Date (DateT datetime, Value int )
INSERT INTO #Date
VALUES ('2015-05-10',12),
('2015-05-11',15),
('2015-05-12',16),
('2015-05-13',17),
('2015-05-14',16),
('2015-05-15',15),
('2015-05-16',12),
('2015-05-17',11),
('2015-05-18',15),
('2015-05-19',12),
('2015-05-20',18)
WITH t AS (
SELECT DateT d,ROW_NUMBER() OVER(ORDER BY DateT) i
FROM #Date
WHERE Value >= 15
GROUP BY DateT
)
SELECT MIN(d) as DataStart,MAX(d) as DataFinal, ROW_NUMBER() OVER(ORDER BY DATEDIFF(day,i,d)) as RN
FROM t
GROUP BY DATEDIFF(day,i,d)
RN column is optional you could use
SELECT MIN(d) as DataStart,MAX(d) as DataFinal
FROM t
GROUP BY DATEDIFF(day,i,d)
Here is a solution using a gaps and islands algorithm. It looks kind of bulky but it runs fast and scales great. It is also modular if you want to add a gap-allowed parameter and you can rewrite it to partition by some other columns and it still performs nicely.
Inspired by Peter Larssons post here: http://www.sqltopia.com/?page_id=83
WITH [theSource](Col1,Col2)
AS
(
SELECT Col1,Col2 FROM (VALUES
('2015-05-10',12),
('2015-05-11',15),
('2015-05-12',16),
('2015-05-13',17),
('2015-05-14',16),
('2015-05-15',15),
('2015-05-16',12),
('2015-05-17',11),
('2015-05-18',15),
('2015-05-19',12),
('2015-05-20',18)
) as x(Col1,Col2)
)
,filteredSource([Value])
AS
(
SELECT Col1 as [Value]
FROM theSource WHERE Col2 >= 15
)
,cteSource(RangeStart, RangeEnd)
AS (
SELECT RangeStart,
CASE WHEN [RangeStart] = [RangeEnd] THEN [RangeEnd] ELSE LEAD([RangeEnd]) OVER (ORDER BY Value) END AS [RangeEnd]
FROM (
SELECT [Value],
CASE
WHEN DATEADD(DAY,1,LAG([Value]) OVER (ORDER BY [Value])) >= [Value] THEN NULL
ELSE [Value]
END AS RangeStart,
CASE
WHEN DATEADD(DAY,-1,LEAD([Value]) OVER (ORDER BY [Value])) <= [Value] THEN NULL
ELSE [Value]
END AS RangeEnd
FROM filteredSource
) AS d
WHERE RangeStart IS NOT NULL
OR RangeEnd IS NOT NULL
)
SELECT RangeStart AS [Min],
RangeEnd AS [Max]
FROM cteSource
WHERE RangeStart IS NOT NULL;

Date issues SQL first day of month crossover

Here is my issue week of year 27 shows 2 dates as the crossover for the month happens it generates another 1st day of month date,
i only want to show to first date in every instance that this happens
Datetime----------------|WeekofYear ------|FirstDayOfMonth
2015-06-29 00:00:00.000 |27 --------------|2015-06-01 00:00:00.000
2015-07-01 00:00:00.000 |27 --------------|2015-07-01 00:00:00.000
CREATE TABLE #weekonweek
(
[Datetime] datetime,
[WeekofYear] int,
[FirstDayOfMonth] datetime
)
INSERT INTO #weekonweek
SELECT
min(c.Datetime),
c.WeekOfYear
,c.FirstDayOfMonth
FROM Calendar c
WHERE YEAR = 2015
and WeekOfYear = 27
GROUP BY WeekOfYear ,c.FirstDayOfMonth
SELECT * FROM #weekonweek
CREATE TABLE #subtable
(
[Datetime] datetime,
[WeekofYear] int,
[FirstDayOfMonth] datetime,
)
INSERT INTO #subtable
SELECT
c.Datetime,
w.WeekOfYear,
min(w.FirstdayofMonth)
FROM Calendar c
LEFT JOIN #weekonweek w on c.WeekOfYear = w.WeekofYear
WHERE YEAR = 2015
and c.WeekOfYear = 27
and w.FirstDayOfMonth <w.FirstDayOfMonth
group by c.Datetime,
w.WeekOfYear
SELECT * FROM #subtable
DROP TABLE #subtable
DROP TABLE #weekonweek
So if you wanna to achieve this ouput:
Datetime----------------|WeekofYear------|FirstDayOfMonth
2015-06-29 00:00:00.000 |27--------------|2015-06-01 00:00:00.000
You simple need to use TOP 1 and add ORDER BY clause.
SELECT TOP 1 * -- like this
FROM #subtable
ORDER BY DateTime -- here you need to pass ORDER BY
SELECT DR.DivNo
,p.[ProductCode]
,p.ProductClass
,p.EmpNo
,[Description]
,[CGNo]
,[SCGNo]
,dr.Retail
,bd.[Buying Director]
INTO #Product
FROM [PRODUCT] p
LEFT JOIN [DIVRETAIL] DR ON p.ProductCode = DR.ProductCode
WHERE dr.ValidTo IS NULL
AND dr.DivNo NOT LIKE '8__'
GROUP BY DR.DivNo
,p.[ProductCode]
,p.ProductClass
,p.EmpNo
,bd.[Buying Director]
,[Description]
,[CGNo]
,[SCGNo]
,dr.Retail
SELECT TOP 1000 [Datetime]
,[FirstDayOfWeek]
,[FirstDayOfMonth]
,s.NoOfStores
,s.DivNo
INTO #stores
FROM [Calendar] c
LEFT JOIN Stores s ON c.FirstDayOfMonth = s.Validfrom
WHERE DATETIME = firstdayofweek

MSSQL - Create records for entries that don't exist in the last 7 days

I have an ASP.NET website with a C# back-end using MSSQL SQL Server 2008 for its content.
I have written the following stored procedure which checks for any records within the last 7 days and then returns what it finds.
ALTER PROCEDURE [dbuser].[GetResponses]
(
#QUEST_ID int
)
AS
SELECT DateAdded, SUM(Responses) AS responseCount
FROM ActiveResponses
WHERE #QUEST_ID = QuestionnaireID AND DateAdded >= dateadd(day,datediff(day,0,GetDate())- 6,0)
GROUP BY DateAdded
RETURN
My problem here is that if no record exists for any of those last 7 days then my method over on the website back-end side will fail as it required 7 records. For example:
Lets say I have the following records in my table
-DateAdded--------Responses
2012-02-12 4
2012-02-11 5
2012-02-10 8
2012-02-08 7
2012-02-07 3
Notice that there are no records for both 2012-02-13(today) and 2012-02-09
How can I create an SQL statement that checks the last 7 days for the number of responses and if no records are found for any one of those days it creates a record with a response of 0 in the correct position?
This is a good application of a numbers table (ex: http://www.projectdmx.com/tsql/tblnumbers.aspx)
Assuming you have a numbers table dbo.Nums that has at least 6 numbers in it, you can try the following:
CREATE TABLE #Dates
(
[Date] DATETIME
)
INSERT INTO #Dates
(
[Date]
)
SELECT
DATEADD(DD, DATEDIFF(DD, 0, GETDATE()) - ([n] - 1), 0)
FROM
[dbo].[Nums] WITH (NOLOCK)
WHERE
[n] < 7
SELECT
[Date],
ISNULL(SUM([Responses]), 0) AS [responseCount]
FROM
#Dates AS d
LEFT OUTER JOIN
ActiveResponses AS a
ON
a.[DateAdded] = d.[Date]
WHERE
#QUEST_ID = QuestionnaireID
ORDER BY
[Date] ASC
This demonstrates getting summary data for each day in a week, even if some of the days have no data:
declare #Data as table ( DateAdded date, Responses int )
insert into #Data ( DateAdded, Responses ) values ( '2/10/2012', 5 ), ( '2/13/2012', 9 )
; with James as (
select cast( SysDateTime() as date ) as StartOfDay, 7 as DaysLeft
union all
select DateAdd( d, -1, StartOfDay ), DaysLeft - 1
from James
where DaysLeft > 1
)
select J.StartOfDay, DateAdd( ms, -3, cast( DateAdd( day, 1, J.StartOfDay ) as DateTime ) ) as EndOfDay, Coalesce( D.Responses, 0 ) as Responses
from James as J left outer join
#Data as D on D.DateAdded = J.StartOfDay
order by J.StartOfDay desc
Left as an exercise is mating this with your questionnaire data.
Note that the time closest to midnight represented by DateTime values is 3ms before midnight. You can use the StartOfDay and EndOfDay values to drop any DateAdded into the correct date.
Declare a table variable with the last seven dates and include it with your query:
ALTER PROCEDURE [dbuser].[GetResponses]
(
#QUEST_ID int
)
AS
DECLARE #i INT=0;
DECLARE #today DATE=getdate();
DECLARE #last7 TABLE(DateAdded DATE);
WHILE #i>-7 BEGIN
INSERT INTO #last7 VALUES (DATEADD(DAY,#i,#today));
SET #i -= 1;
END
;WITH a AS (
SELECT ar.DateAdded, count(ar.Responses) as responseCount
FROM ActiveResponses ar
INNER JOIN #last7 z ON z.DateAdded=ar.DateAdded
WHERE #QUEST_ID = ar.QuestionnaireID
GROUP BY ar.DateAdded
)
SELECT DateAdded=ISNULL(a.DateAdded,z.DateAdded)
, responseCount=ISNULL(a.responseCount,0)
FROM #last7 z
LEFT JOIN a ON a.DateAdded=z.DateAdded;
RETURN;
GO
Results:
DateAdded responseCount
---------- -------------
2012-02-13 0
2012-02-12 4
2012-02-11 5
2012-02-10 8
2012-02-09 0
2012-02-08 7
2012-02-07 3

CTE - recursively update quantity until total consumed

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;

Resources