Filling empty dates with stored procedure in sql - sql

I have found similar questions that have been answered but I can't seem to get it working. I have the following SQL query but I want to fill the missing dates with 0-values
SELECT
Lines.Item,
CAST(Lines.Date AS Date) AS SalesDate,
ABS(SUM(Lines.Invoiced)) AS QtySoldOnDate
FROM
Lines
WHERE
Lines.Invoiced < 0
AND Lines.Item = 'a158wa'
AND Lines.Date >= '2014-01-01'
AND Lines.Date <= '2014-12-31'
GROUP BY
Lines.Item, Lines.Date
I have found the following post, but I can't seem to get it working/figure out how to merge the two queries: What is the most straightforward way to pad empty dates in sql results (on either mysql or perl end)?
Any help would be greatly appreciated.

The easiest way is to get a list of 365 values for forming the dates. One way is with master..spt_values, something like this:
with dates as (
select dateadd(day, row_number() over (order by (select null)),
cast('2014-01-01' as date)) as thedate
from master..spt_values
)
SELECT l.Item, d.thedate AS SalesDate,
ABS(SUM(l.Invoiced)) AS QtySoldOnDate
FROM dates d left join
Lines l
ON l.Date = d.thedate and
l.Invoiced < 0 AND
l.Item = 'a158wa'
WHERE d.thedate BETWEEN '2014-01-01' AND Lines.Date <= '2014-12-31'
GROUP BY l.Item, d.theDate;
Note: You can also read the Number column directly from master..spt_values if you use type = 'P'. I'm likely to forget the type part, so I just used row_number(). Perhaps Microsoft could add a view called Numbers that did this for us.

You can Create storedprocedure with 2 input parameters and 1 output parameters
and you can check if row exists then do your job and in the else part you can set output parameter as o-values
CREATE PROCEDURE yourprocedure
#startDate Date,
#endDate Date,
#OutputPara as nvarchar(100) output
AS
BEGIN
IF EXISTS( Select Lines.Item FROM Lines [Lines.Date] Where [Lines.Date] >= #startDate AND [Lines.Date] <= #endDate)
BEGIN
SELECT
Lines.Item,
COALESCE(CAST(Lines.Date AS Date),'0') AS SalesDate,
ABS(SUM(Lines.Invoiced)) AS QtySoldOnDate
FROM
Lines
WHERE
Lines.Invoiced < 0
AND Lines.Item = 'a158wa'
AND [Lines.Date] >= #startDate
AND [Lines.Date] <= #endDate
GROUP BY
Lines.Item, [Lines.Date]
Set #OutputPara =' result exists'
END
ELSE
BEGIN
SET #OutputPara='0-results';
END
END
GO

I'm assuming you want the days where nothing is sold, to have its own row with 0 as it's value. This should do it perhaps with a few tweaks from you since I don't have your table or any data.
DECLARE #startDate DATE,
#endDate DATE;
SET #startDate = '2014-01-01';
SET #endDate = '2014-12-31';
--Generates table of each day in range
WITH cte_dates
AS
(
SELECT #startDate AS startDate
UNION ALL
SELECT DATEADD(DAY,1,startDate)
FROM cte_dates
WHERE startDate <= #endDate
)
SELECT cte_dates.startDate,
Lines.Item,
CAST([Lines.Date] AS Date) AS SalesDate,
ISNULL(ABS(SUM(Lines.Invoiced)),0) AS QtySoldOnDate
FROM cte_dates
--Left join makes it where if there is no date in Lines, then cte_dates will be there with nulls for columns in your table Lines
LEFT JOIN Lines
ON cte_dates.startDate = Lines.[Date]
WHERE
Lines.Invoiced < 0
AND Lines.Item = 'a158wa'
AND Lines.Date BETWEEN #startDate AND #endDate
GROUP BY Lines.Item,Lines.[Date],cte_dates.startDate
--It's a recursive CTE. This allows it recursively iterate enough times to generate the list of dates
OPTION (MAXRECURSION 0)
Theoretical results:
StartDate Item SalesDate QtySOldOnDate
---------------------------------------------------
2014-01-01 Item1 2014-01-01 500
2014-01-02 NULL NULL 0
2014-01-03 Item2 2014-01-03 250

Related

SQL Server : GETDATE not returning today's date

Below is my query. Everything is working except for the GETDATE function in the WHERE clause. It won't return today's date if I put the date in there like this: 7/12/22. It is a DATETIME column in the backend. Thanks in advance.
SELECT
acsMFG.dbo.production_posting_trans.item_no,
SUM(acsMFG.dbo.production_posting_trans.good_quantity) AS [Good Qty],
SUM(acsMFG.dbo.production_posting_trans.scrap_quantity) AS [Scrap Qty],
acsAUTOSYS.dbo.inventory_master.selling_price
FROM
acsAUTOSYS.dbo.inventory_master
FULL OUTER JOIN
acsMFG.dbo.production_posting_trans ON acsMFG.dbo.production_posting_trans.item_no = acsAUTOSYS.dbo.inventory_master.item_no
AND acsAUTOSYS.dbo.inventory_master.company_code = acsMFG.dbo.production_posting_trans.company_code
WHERE
acsMFG.dbo.production_posting_trans.company_code = '10'
AND acsMFG.dbo.production_posting_trans.production_date = GETDATE()
AND acsMFG.dbo.production_posting_trans.posting_type = 'MMQ'
OR acsMFG.dbo.production_posting_trans.posting_type = 'IRS'
OR acsMFG.dbo.production_posting_trans.posting_type = 'PME'
GROUP BY
acsMFG.dbo.production_posting_trans.item_no,
acsAUTOSYS.dbo.inventory_master.selling_price
Well, when you say SELECT GETDATE(); what do you see? There is a time component there too, so if the data in the table is 2022-07-12 15:12 and you run the query at 2022-07-12 15:13, that's not a match.
If you want data from today, you need a range query:
WHERE col >= CONVERT(date, GETDATE())
AND col < DATEADD(DAY, 1, CONVERT(date, GETDATE()));
It is cleaner to use variables, e.g.
DECLARE #today date = GETDATE();
DECLARE #tomorrow date = DATEADD(DAY, 1, #today);
...
WHERE col >= #today
AND col < #tomorrow;
Don't get tempted into doing this:
WHERE CONVERT(date, col) = CONVERT(date, GETDATE());
It will work, but it's not fantastic.
For the actual problem with OR logic, you have:
... date clauses with AND ...
AND acsMFG.dbo.production_posting_trans.posting_type='MMQ'
Or acsMFG.dbo.production_posting_trans.posting_type ='IRS'
Or acsMFG.dbo.production_posting_trans.posting_type ='PME'
I think you want:
AND
(
acsMFG.dbo.production_posting_trans.posting_type='MMQ'
Or acsMFG.dbo.production_posting_trans.posting_type ='IRS'
Or acsMFG.dbo.production_posting_trans.posting_type ='PME'
)
As for aliases:
FROM
acsAUTOSYS.dbo.inventory_master AS im
FULL OUTER JOIN
acsMFG.dbo.production_posting_trans AS ppt
Now all your references can be:
AND
(
ppt.posting_type='MMQ'
ppt.posting_type ='IRS'
Or ppt.posting_type ='PME'
)
GROUP BY
ppt.item_no, im.selling_price;
Or better:
AND
(
ppt.posting_type IN ('MMQ', 'IRS', 'PME')
)
GROUP BY
ppt.item_no, im.selling_price;
...so much more readable.
Since GETDATE() returns the time too, you will never match. You need something like:
CAST(acsMFG.dbo.production_posting_trans.production_date AS Date)
= CAST(GETDATE() AS Date)

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

Loop Through Recordset and count if Within Date Range

Please note - this is my first post, so I apologize for anything I have missed.
I have a large event table that has a record for each time someone moves within a facility. What I would like to do, is say if that person was in the facility over the previous 365 days, count them (for each day). Essentially, I need the Average Daily Population (for everyone in the facility over the previous 365 days).
thank you.
Example Data:
PersonID ArriveDt LeaveDt Location
1111 1/1/2019 1/3/2019 ABC
1122 1/1/2019 1/5/2019 ABC
1123 1/2/2019 1/6/2019 ABC
Date Count
1/1/2019 2
1/2/2019 3
1/3/2019 3
1/4/2019 2
1/5/2019 2
1/6/2019 1
If you don't care about the intermediate values then really you just need to compute the total man-days and divide.
select sum(datediff(day, adjusted_start, adjusted_end) + 1) / 365.0
from T
cross apply (
select
dateadd(day, -365, cast(getdate() as date)),
dateadd(day, -1, cast(getdate() as date))
) d(range_start, range_end)
cross apply (
select
case when ArriveDt < range_start then range_start else ArriveDt end,
case when LeaveDt > range_end then range_end else LeaveDt end
) a(adjusted_start, adjusted_end);
If not, then come up with a table of dates (from any of numerous sources around the internet) and join from that. An outer join allows for dates with zero census.
with dates as (
select dt from calendar -- exercise for the reader
where
dt >= dateadd(day, -365, cast(getdate() as date)),
and dt <= dateadd(day, -1, cast(getdate() as date))
)
select d.dt, count(*)
from dates d left outer join T t
on d.dt between t.ArriveDt and t.LeaveDt
group by d.dt;
Create basic calendar table for the last X days:
IF OBJECT_ID('tempdb..#Calendar') IS NOT NULL
DROP TABLE #Calendar;
GO
DECLARE #StartDate DATE = DATEADD(d, -365, GETDATE())
DECLARE #EndDate DATE = GETDATE()
CREATE TABLE #Calendar
(
[CalendarDate] DATE
)
WHILE #StartDate <= #EndDate
BEGIN
INSERT INTO #Calendar
(
CalendarDate
)
SELECT
#StartDate
SET #StartDate = DATEADD(dd, 1, #StartDate)
END
GO
Now CROSS JOIN with Calendar Table to get rows per Person per day they were at a Location and the GROUP BY
SELECT
c.CalendarDate
,Location
,COUNT(PersonID) AS PersonCount
FROM (
SELECT PersonID ,ArriveDt ,LeaveDt ,Location FROM dbo.Table
) t
CROSS JOIN Calendar c
WHERE c.CalendarDate BETWEEN t.ArriveDt AND t.LeaveDt
GROUP BY
c.CalendarDate
,Location
Edit (2019-10-08):
If you're unable to create tables, you can use temp tables instead and do a DROP IF EXISTS. You can run both of these parts in one go on the fly to generate your final report.

SUM(value) where {date filter) is in an aggregation query

I am doing an aggregation query for a temp table (details here) and need to filter the sum part on a date range.
The query I have is:
insert #temp2(name, cost)
select ename6, sum(dqr.cost)
from dbo.condensed7day_query_result dqr
group by dqr.ename6;
However, I need the sum(dqr.cost) to actually be
sum(dqr.cost) (dbo.condensed7day_query_result.start_date >= #StartDate) and (dbo.condensed7day_query_result.start_date <= #EndDate)
And if the sum() is a null (no rows returned), I'd prefer it be a 0 but it's ok if it's a null. However, I need that row because I need the name (ename6).
This is part of building a temp table in SQL Server if that is relevant.
Easy:
insert #temp2(name, cost)
select ename6,
sum(
case when dqr.start_date >= #StartDate and dqr.start_date <= #EndDate then dqr.cost
else 0 end
)
from dbo.condensed7day_query_result dqr
group by dqr.ename6;
I think you can filter using where condition as below:
insert #temp2(name, cost)
select ename6, sum(dqr.cost)
from dbo.condensed7day_query_result dqr
where dqr.start_date >= #startDate and dqr.start_date <= #EndDate
group by dqr.ename6;
You can use this code
insert #temp2(name, cost)
Select ename6,cost from
( select ename6, IsNull(sum(dqr.cost),0) as cost
from dbo.condensed7day_query_result dqr
where (start_date >= #StartDate) and (start_date <= #EndDate)
group by dqr.ename6
) Temp
union
( Select ename6 , 0 as cost
from dbo.condensed7day_query_result
where ename6 not in (
Select distinct ename6
from dbo.condensed7day_query_result dqr
where (dqr.start_date >= #StartDate) and (dqr.start_date <= #EndDate)
)
)
In Sql you can use Whereclause before GroupByClause and so Aggregatefunction will work on resltset after Whereclause.
Also you can use Isnull function to return 0 value when the result of Sum is null. And the second Select returns the records that not exists in #StartDate and #EndDate range.

CTE and last known date processing

Input
#StartDate = '01/25/2010'
#EndDate = '02/06/2010'
I have 2 CTEs in a stored procedure as follows:
with CTE_A as
(
[gives output A..Shown below]
),
with CTE_B as
(
Here,
I want to check if #StartDate is NOT in output A then replace it with the last known date. In this case, since #startdate is less than any date in output A hence #StartDate will become 02/01/2010.
Also to check if #EndDate is NOT in output A then replace it with the last known date. In this case, since #enddate is 02/06/2010 hence it will be replace with 02/05/2010.
// Here there is a query using #startDate and #EndDate.
)
output A
Name Date
A 02/01/2010
B 02/01/2010
C 02/05/2010
D 02/10/2010
You don't need a 2nd CTE (untested)
...
SELECT
StartDate, EndDate
FROM
(
SELECT TOP 1
A.Date AS StartDate
FROM
CTEA A
WHERE
A.[Date] >= #StartDate
ORDER BY
A.Date
) Bmin
CROSS JOIN
(
SELECT TOP 1
A.Date AS EndDate
FROM
CTEA A
WHERE
A.[Date] <= #EndDate
ORDER BY
A.Date DESC
) Bmax
You could use MAX/MIN too