How to join a table which is derived from a with statement into a larger query - sql

I have a large query which joins many different select queries to create a single table with one of the columns recording current stock. I now need to count the out of stock days in the next 99 days but since inbound stock and demand stock do not have a record for every day I have to create a table which shows the movement of stock for each day in the next 99 days by taking current stock plus the change of stock on each future dates. In order to get to this I have created the below stand alone query which returns the correct results for the change in stock per day, the problem that I now face is that I am unable to join this query to main query.
DECLARE #Date date
SET #Date = '2020-12-15'
;WITH cte AS
(
SELECT
CASE
WHEN DATEPART(Day, #Date) = 1 THEN #Date
ELSE DATEADD(day, DATEDIFF(day, 0, #Date) + 1, 0)
END AS myDate,
SKU
FROM
BS_BufferSetting
UNION ALL
SELECT
DATEADD(day, 1, myDate), SKU
FROM
cte
WHERE
DATEADD(day, 1, myDate) <= DATEADD(day, 99, #Date)
)
SELECT
myDate AS Date,
SKU,
ISNULL(Amount_IOP, 0) - ISNULL(Amount_DP, 0) AS Amount
FROM
CTE
LEFT OUTER JOIN
DP_Demand ON SKU = SKU_DP AND mydate = Date_DP
LEFT OUTER JOIN
IOP_Inbound ON SKU = SKU_IOP AND mydate = Date_IOP
The above query returns a result set like this:
Date SKU Amount
----------------------
2020-12-16 BEX -108
2020-12-17 BEX 0
2020-12-18 BEX -12
2020-12-19 BEX 0
Once I have linked this to the main query I will then need to work out how to add in the opening stock (from the main table) and formulate a running total but this is part two of the problem.

You can chain CTE's like this:
;with firstcte as
(
select 1 as MyColumn
),
AnotherCTEUsingTheFirstOne as
(
select MyColumn from firstcte
)
select * from AnotherCTEUsingTheFirstOne
so you should be able to;
DECLARE #Date date
SET #Date = '2020-12-15'
;WITH cte AS
(
SELECT
CASE
WHEN DATEPART(Day, #Date) = 1 THEN #Date
ELSE DATEADD(day, DATEDIFF(day, 0, #Date) + 1, 0)
END AS myDate,
SKU
FROM
BS_BufferSetting
UNION ALL
SELECT
DATEADD(day, 1, myDate), SKU
FROM
cte
WHERE
DATEADD(day, 1, myDate) <= DATEADD(day, 99, #Date)
),
secondCTE As
(
SELECT
myDate AS Date,
SKU,
ISNULL(Amount_IOP, 0) - ISNULL(Amount_DP, 0) AS Amount
FROM
CTE
LEFT OUTER JOIN
DP_Demand ON SKU = SKU_DP AND mydate = Date_DP
LEFT OUTER JOIN
IOP_Inbound ON SKU = SKU_IOP AND mydate = Date_IOP
)
SELECT * FROM ATable A INNER JOIN secondCTE B ON A.Col=B.Col

Related

Get all data variations from same sql table based on multiple periods

I have two tables.
Table TDatos - contains ending and starting dates of each month
appointment - contains Valid From and Valid To dates
There is no any relation between the two tables, however, I want to select all appointment.[Valid From] date day differences with each month DatosT.[Month Start]
For example if appointment. [Valid From] is '2022-05-01' a selected data should look something like 120, 89, 61, 30, 0, -31 ... (for all 12 months of the year or more...)
I have done that using loop, but is there a way to do that without looping ?
/* declarations ... */
SET #Counter=1
WHILE ( #Counter <= 12)
BEGIN
set #month_start = (select top 1 month_start_date from TDatos where MonthNumber = #Counter)
insert into #AllRecords
select
DATEADD(day, [#month_start, Valid From]) as NewDays
from appointment
END
use a recursive query or a RVC...
WITH
T AS (select top 1 month_start_date, 1 AS N from TDatos where MonthNumber = 1
UNION ALL
select top 1 month_start_date, N + 1 from TDatos where MonthNumber = N + 1)
insert into #AllRecords
select DATEADD(day, month_start, [Valid From])
from appointment CROSS JOIN T;

SQL Need days of selected week as columns

Im in current need of making a query that returns based on a week all the days between the week but I need to use them as columns so that I can use rows per day for that week, I took an example I found here on forums
;WITH TestData(N, Order_Date, Net_Amount) AS (
SELECT 1 N, CAST(GETDATE() AS DATE) Order_Date, RAND() * 100 Net_Amount
UNION ALL
SELECT N+1 N, CAST(GETDATE()-N/5 AS DATE) Order_Date, RAND(CHECKSUM(NEWID())) * 100 Net_Amount FROM TestData
WHERE N < 20
)
SELECT TestData.Order_Date, TestData.Net_Amount INTO #Order FROM TestData
SET DATEFIRST 1
;WITH Days(N,DayOfTheWeek) AS (
SELECT 1 N, DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), CONVERT(DATE,GETDATE())) DayOfTheWeek
UNION ALL
SELECT N+1 N,DATEADD(DAY, 1, DayOfTheWeek) DayOfTheWeek FROM Days
WHERE N < 7
)
SELECT d.DayOfTheWeek, SUM(Net_Amount) TotalAmount
FROM Days d
LEFT JOIN #Order ON d.DayOfTheWeek = Order_Date
GROUP BY d.DayOfTheWeek
This is my end result
But I need to display the days of the week in columns instead of rows
Result expected
thx for your help

Join Generated Date Sequence

Currently I'm trying to join a date table to a ledger table so I can fill the gaps of the ledger table whenever there are no transactions in certain instances (e.g. there are transactions on March 1st and in March 3rd, but no transaction in March 2nd. And by joining both tables March 2nd would appear in the ledger table but with 0 for the variable we're analyzing.)
The challenge is that I can't create a Date object/table/dimension because I don't have permissions to create tables in the database. Therefore I've been generating a date sequence with this code:
DECLARE #startDate date = CAST('2016-01-01' AS date),
#endDate date = CAST(GETDATE() AS date);
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1;
So, is there the possibility to join both tables into the same statement? Let's say the ledger table looks like this:
SELECT
date,cost
FROM ledger
I'd assume it can be done by using a subquery but I don't know how.
Thank you.
There is a very good article by Aaron Bertrand showing several methods for generating a sequence of numbers (or dates) in SQL Server: Generate a set or sequence without loops – part 1.
Try them out and see for yourself which is faster or more convenient to you. (spoiler - Recursive CTE is rather slow)
Once you've picked your preferred method you can wrap it in a CTE (common-table expression).
Here I'll use your method from the question
WITH
CTE_Dates
AS
(
SELECT
DATEADD(day, number - 1, #startDate) AS dt
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT
...
FROM
CTE_Dates
LEFT JOIN Ledger ON Ledger.dt = CTE_Dates.dt
;
You can use your generated date sequence as a CTE and LEFT JOIN that to your ledger table. For example:
DECLARE #startDate date = CAST('2020-02-01' AS date);
DECLARE #endDate date = CAST(GETDATE() AS date);
WITH dates AS (
SELECT DATEADD(day, number - 1, #startDate) AS [Date]
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY n.object_id
)
FROM sys.all_objects n
) S(number)
WHERE number <= DATEDIFF(day, #startDate, #endDate) + 1
)
SELECT dates.Date, COALESCE(ledger.cost, 0)
FROM dates
LEFT JOIN (VALUES ('2020-02-02', 14), ('2020-02-05', 10)) AS ledger([Date], [cost]) ON dates.Date = ledger.Date
Output:
Date cost
2020-02-01 0
2020-02-02 14
2020-02-03 0
2020-02-04 0
2020-02-05 10
2020-02-06 0
Demo on dbfiddle

return a result even if there is no data for that day/ week/ month

I have an MSSQL database and I want to get a value for each day/ week/ month in separate queries.
I got this working just fine except for intervals where there is no data, it wont return anything. And since im putting this in a graph, I want it to display a 0 or a NULL at least instead of jumping days or weeks etc.
I dont know if it will be different for each query but here is my daily query:
select CAST(Placements.CreatedOn AS DATE) AS
date,SUM(Placements.CommissionPerc * (Placements.PlacementFee / 100)) AS value
from [placements]
where [Placements].[CreatedOn] >= '2018-06-07' and [Placements].[CreatedOn] < '2018-06-12'
group by CAST(Placements.CreatedOn AS DATE)
order by CAST(Placements.CreatedOn AS DATE) ASC
This returns a result like:
So it returns 0 for when the data is actually 0 but when its missing, theres nothing like for days 9, 10 and 12
How can i fix this? thanks
Using a recursive CTE you can generate a list of dates.
Which can then be used to LEFT JOIN your table.
Example:
WITH DATES2018 AS
(
SELECT CAST('2018-01-01' AS DATE) AS [date]
UNION ALL
SELECT DATEADD(day, 1, [date])
FROM DATES2018
WHERE [date] < CAST('2018-12-31' AS DATE)
)
SELECT
d.[Date],
SUM(p.CommissionPerc * (p.PlacementFee / 100.0)) AS [value]
FROM DATES2018 AS d
LEFT JOIN [Placements] AS p ON CAST(p.CreatedOn AS DATE) = d.[Date]
WHERE d.[Date] BETWEEN '2018-06-07' AND '2018-06-11'
GROUP BY d.[Date]
ORDER BY d.[Date] ASC
OPTION (MAXRECURSION 366)
But you could also just add a new permanent table with all dates.
And use that table to left join your table.
Btw, if variables are used for the start and end date then that SQL can be optimized.
DECLARE #StartDate DATE = '2018-06-07';
DECLARE #EndDate DATE = '2018-06-11';
WITH DATES AS
(
SELECT #StartDate AS [date]
UNION ALL
SELECT DATEADD(day, 1, [date])
FROM DATES
WHERE [date] < #EndDate
)
SELECT
d.[Date],
SUM(p.CommissionPerc * (p.PlacementFee / 100.0)) AS [value]
FROM DATES AS d
LEFT JOIN [Placements] AS p
ON p.CreatedOn BETWEEN CAST(#StartDate AS DATETIME) AND CAST(DATEADD(day, 1, #EndDate) AS DATETIME) AND
CAST(p.CreatedOn AS DATE) = d.[Date]
GROUP BY d.[Date]
ORDER BY d.[Date] ASC
OPTION (MAXRECURSION 0)
A permanent calendar table would be best but here's and example that uses a CTE to create the dates needed for a LEFT JOIN. This uses a maximum of 1,000 days but can be extended as needed.
DECLARE
#StartDate date = '2018-06-07'
, #EndDate date = '2018-06-12';
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t1k AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b CROSS JOIN t10 AS c)
,calendar AS (SELECT DATEADD(day, num, #StartDate) AS calendar_date
FROM t1k
WHERE num <= DATEDIFF(day, #StartDate, #EndDate)
)
SELECT
calendar.calendar_date AS date
, SUM( COALESCE(Placements.CommissionPerc * (Placements.PlacementFee / 100),0 ) ) AS value
FROM calendar
LEFT JOIN [placements] ON [Placements].[CreatedOn] = calendar.calendar_date
GROUP BY calendar.calendar_date
ORDER BY calendar.calendar_date ASC;

tsql month problem

I have a table like:
id month cost
------------------
1 Jan 200
1 Mar 204
1 May 200
1 Dec 201
I need an output like( order by month including the other months of a year-displaying all 12 months):
to month cost
------------------
1 Jan 200
NULL Feb NULL
1 Mar 204
....
....
....
1 Dec 201
any idea or solution how to do this in TSQL?
Thanks!
edit:: month is extracted from a datetime value.
in real world i'll have to show previous 12 months from last month in a DESC order! any suggestion for that?
Try building a reference table of months, and JOINing on it. It's the quickest way to do this with months in varchar datatype.
declare #foo table (id int, [mon] varchar(100), cost int)
declare #mon table (mon varchar(100), orderWeight int)
INSERT INTO #mon (mon, orderWeight)
VALUES ('Jan',1), ('Feb',2),('Mar',3),('Apr',4),('May',5),('Jun',6),('Jul',7),
('Aug',8),('Sep',9),('Oct',10),('Nov',11),('Dec',12)
INSERT INTO #foo(id, [mon], cost)
VALUES ( 1 ,'Jan' , 200),
( 1 ,'Mar', 204),
( 1 ,'May' , 200),
( 1 ,'Dec' , 201)
select f.id,
m.[mon] ,
f.cost
from #mon as m
left join #foo as f on m.mon = f.mon
order by m.orderWeight
Results:
Your ordering will now be guaranteed with the order by orderWeight.
Sample table
create table mytable(id int, dt datetime, cost money)
insert mytable values
(1,GETDATE()-10,200),
(1,GETDATE()-40,204),
(1,GETDATE()-100,200),
(1,GETDATE()-200,201);
The query, using SQL Server 2008 specific syntax, and sorted properly
select
t.id [to],
CONVERT(char(3),dateadd(month,-M.N,L.PVT),7) [Month],
sum(t.cost) totalCost
from (select PVT=dateadd(month,datediff(month,0,getdate())-1,0)) L
cross join (values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) M(N)
left join mytable t
on t.dt >= dateadd(month,-M.N,L.PVT)
and t.dt < dateadd(month,-M.N+1,L.PVT)
group by t.id, right(CONVERT(char(9),dt,6),6), M.N, L.PVT
order by M.N
What it does:
right(CONVERT(char(9),dt,6),6) converts a date into the format 'DD MMM YY', we only need the MMM YY part
In the SELECT, we further extract only the 3-char month from it, using LEFT( , 3)
The subquery L has a single record and column, PVT, which is the first date of the last month
The number series 0-11 is used to create the month values for the last 12 months, using the formula dateadd(month,-M.N,L.PVT)
The range t.dt >= .. and t.dt < .. finds data for a single month
How about this?
The result contains month and year, but you can strip it as you want.
;with months
as
(
select dateadd(month, -1, dateadd(day, datediff(day, 0, getdate()), 0)) as m
union all
select dateadd(month, -1, m)
from months
where m > dateadd(month, -12, getdate())
)
-- Testdata
,yourTable(id,somedate,cost)
as
(
select 1, '2011-01-03', 200
union all
select 1, '2011-03-06', 204
union all
select 1, '2010-05-09', 200
union all
select 1, '2010-05-19', 201
union all
select 1, '2010-12-02', 201
)
-- end testdata
select yt.id
,datename(month,coalesce(yt.somedate, m.m)) as [month]
,datename(year,coalesce(yt.somedate, m.m)) as [year]
--,yt.cost
,sum(yt.cost) as cost
from months m
left join yourTable yt
on datepart(year, yt.someDate) = DATEPART(year, m.m)
and datepart(month, yt.someDate) = DATEPART(month, m.m)
group by
yt.id
,datename(month,coalesce(yt.somedate, m.m))
,datename(year,coalesce(yt.somedate, m.m))
,m.m
order by m.m desc
Edit: Altered solution to support sum.
Remove the group by-section and alter the comment of cost, to get the old solution.