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

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;

Related

Finding Active Clients By Date

I'm having trouble writing a recursive function that would count the number of active clients on any given day.
Say I have a table like this:
Client
Start Date
End Date
1
1-Jan-22
2
1-Jan-22
3-Jan-22
3
3-Jan-22
4
4-Jan-22
5-Jan-22
5
4-Jan-22
6-Jan-22
6
7-Jan-22
9-Jan-22
I want to return a table that would look like this:
Date
NumActive
1-Jan-22
2
2-Jan-22
2
3-Jan-22
3
4-Jan-22
4
5-Jan-22
4
6-Jan-22
3
7-Jan-22
3
8-Jan-22
3
9-Jan-22
4
Is there a way to do this? Ideally, I'd have a fixed start date and go to today's date.
Some pieces I have tried:
Creating a recursive date table
Truncated to Feb 1, 2022 for simplicity:
WITH DateDiffs AS (
SELECT DATEDIFF(DAY, '2022-02-02', GETDATE()) AS NumDays
)
, Numbers(Numbers) AS (
SELECT MAX(NumDays) FROM DateDiffs
UNION ALL
SELECT Numbers-1 FROM Numbers WHERE Numbers > 0
)
, Dates AS (
SELECT
Numbers
, DATEADD(DAY, -Numbers, CAST(GETDATE() -1 AS DATE)) AS [Date]
FROM Numbers
)
I would like to be able to loop over the dates in that table, such as by modifying the query below for each date, such as by #loopdate. Then UNION ALL it to a larger final query.
I'm now stuck as to how I can run the query to count the number of active users:
SELECT
COUNT(Client)
FROM clients
WHERE [Start Date] >= #loopdate AND ([End Date] <= #loopdate OR [End Date] IS NULL)
Thank you!
You don't need anything recursive in this particular case, you need as a minimum a list of dates in the range you want to report on, ideally a permanent calendar table.
for purposes of demonstration you can create something on the fly, and use it like so, with the list of dates something you outer join to:
with dates as (
select top(9)
Convert(date,DateAdd(day, -1 + Row_Number() over(order by (select null)), '20220101')) dt
from master.dbo.spt_values
)
select d.dt [Date], c.NumActive
from dates d
outer apply (
select Count(*) NumActive
from t
where d.dt >= t.StartDate and (d.dt <= t.EndDate or t.EndDate is null)
)c
See this Demo Fiddle

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

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

Query to check number of records created in a month.

My table creates a new record with timestamp daily when an integration is successful. I am trying to create a query that would check (preferably automated) the number of days in a month vs number of records in the table within a time frame.
For example, January has 31 days, so i would like to know how many days in january my process was not successful. If the number of records is less than 31, than i know the job failed 31 - x times.
I tried the following but was not getting very far:
SELECT COUNT (DISTINCT CompleteDate)
FROM table
WHERE CompleteDate BETWEEN '01/01/2015' AND '01/31/2015'
Every 7 days the system executes the job twice, so i get two records on the same day, but i am trying to determine the number of days that nothing happened (failures), so i assume some truncation of the date field is needed?!
One way to do this is to use a calendar/date table as the main source of dates in the range and left join with that and count the number of null values.
In absence of a proper date table you can generate a range of dates using a number sequence like the one found in the master..spt_values table:
select count(*) failed
from (
select dateadd(day, number, '2015-01-01') date
from master..spt_values where type='P' and number < 365
) a
left join your_table b on a.date = b.CompleteDate
where b.CompleteDate is null
and a.date BETWEEN '01/01/2015' AND '01/31/2015'
Sample SQL Fiddle (with count grouped by month)
Assuming you have an Integers table*. This query will pull all dates where no record is found in the target table:
declare #StartDate datetime = '01/01/2013',
#EndDate datetime = '12/31/2013'
;with d as (
select *, date = dateadd(d, i - 1 , #StartDate)
from dbo.Integers
where i <= datediff(d, #StartDate, #EndDate) + 1
)
select d.date
from d
where not exists (
select 1 from <target> t
where DATEADD(dd, DATEDIFF(dd, 0, t.<timestamp>), 0) = DATEADD(dd, DATEDIFF(dd, 0, d.date), 0)
)
Between is not safe here
SELECT 31 - count(distinct(convert(date, CompleteDate)))
FROM table
WHERE CompleteDate >= '01/01/2015' AND CompleteDate < '02/01/2015'
You can use the following query:
SELECT DATEDIFF(day, t.d, dateadd(month, 1, t.d)) - COUNT(DISTINCT CompleteDate)
FROM mytable
CROSS APPLY (SELECT CAST(YEAR(CompleteDate) AS VARCHAR(4)) +
RIGHT('0' + CAST(MONTH(CompleteDate) AS VARCHAR(2)), 2) +
'01') t(d)
GROUP BY t.d
SQL Fiddle Demo
Explanation:
The value CROSS APPLY-ied, i.e. t.d, is the ANSI string of the first day of the month of CompleteDate, e.g. '20150101' for 12/01/2015, or 18/01/2015.
DATEDIFF uses the above mentioned value, i.e. t.d, in order to calculate the number of days of the month that CompleteDate belongs to.
GROUP BY essentially groups by (Year, Month), hence COUNT(DISTINCT CompleteDate) returns the number of distinct records per month.
The values returned by the query are the differences of [2] - 1, i.e. the number of failures per month, for each (Year, Month) of your initial data.
If you want to query a specific Year, Month then just simply add a WHERE clause to the above:
WHERE YEAR(CompleteDate) = 2015 AND MONTH(CompleteDate) = 1

Pad out an SQL table with data for Graphing Purposes

SQL Server 2005
I have an SQL Function (ftn_GetExampleTable) which returns a table with multiple result rows
EXAMPLE
ID MemberID MemberGroupID Result1 Result2 Result3 Year Week
1 1 1 High Risk 2 xx 2011 22
2 11 4 Low Risk 1 yy 2011 21
3 12 5 Med Risk 3 zz 2011 25
etc.
Now I do a count and group by on a table above this for Result 2 for instance so I get
SELECT MemberGroupID, Result2, Count(*) AS ExampleCount, Year, Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
MemberGroupID Result2 ExampleCount Year Week
1 2 4 2011 22
4 1 2 2011 21
5 3 1 2011 25
Now imagine when I go to graph this new table between Weeks 20 and 23 of Year 2011, you'll see that it won't graph 20 or 23 or certain groups or even certain results in this example as they are not in the included data, so I need "false data" inserted into this table which has all the possibilities so they at least show on a graph even if the count is 0, does this make sense?
I am wondering on the easiest and kind of most dynamic way as it could be Result1 or Result3 I want to Graph on (different column types).
Thanks in advance
It looks like your dimensions are: MemberGroupID,Result2, and week (Year,Week).
One approach to solving this is to generate a list of all values you want for all the dimensions, and produce a cartesian product of them. As an example,
SELECT m.MemberGroupID, n.Result2, w.Year, w.Week
FROM (SELECT MemberGroupID FROM ftn_GetExampleTable GROUP BY MemberGroupID) m
CROSS
JOIN (SELECT Result2 FROM ftn_GetExampleTable GROUP BY Result2 ) n
CROSS
JOIN (SELECT Year, Week FROM myCalendar WHERE ... ) w
You don't necessarily need a table named myCalendar. (That approach does seem to be the popular one.) You just need a row source from which you can derive a list of (Year, Week) tuples. (There are answers to the question elsewhere in Stackoverflow, how to generate a list of dates.)
And the list of MemberGroupID and Result2 values doesn't have to come from the ftn_GetExampleTable rowsource, you could substitute another query.
With a cartesian product of those dimensions, you've got a complete "grid". Now you can LEFT JOIN your original result set to that.
Any place you don't have a matching row from the "gappy" result query, you'll get a NULL returned. You can leave the NULL, or replace it with a 0, which is probably what you want if it's a "count" you are returning.
SELECT d.MemberGroupID
, d.Result2
, d.Year
, d.Week
, IFNULL(r.ExampleCount,0) as ExampleCount
FROM ( <dimension query from above> ) d
LEFT
JOIN ( <original ExampleCount query> ) r
ON r.MemberGroupID = d.MemberGroupID
AND r.Result2 = d.Result2
AND r.Year = d.Year
AND r.Week = d.Week
That query can be refactored to make use of Common Table Expressions, which makes the query a little easier to read, especially if you are including multiple measures.
; WITH d AS ( /* <dimension query with no gaps (example above)> */
)
, r AS ( /* <original query with gaps> */
SELECT MemberGroupID, Result2, Count(*) AS ExampleCount, Year, Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
)
SELECT d.*
, IFNULL(r.ExampleCount,0)
FROM d
LEFT
JOIN r
ON r.Year=d.Year AND r.Week=d.Week AND r.MemberGroupID = d.MemberGroupID
AND r.Result2 = d.Result2
This isn't a complete working solution to your problem, but it outlines an approach you can use.
Whenever I need to generate a sequence within SQL-Server I use the sys.all_objects table along with the ROW_NUMBER function, then maninpulate it as required:
SELECT ROW_NUMBER() OVER(ORDER BY Object_ID) AS Sequence
FROM Sys.All_Objects
So for the list of year and week numbers I would use:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SET #StartDate = '20110101'
SET #EndDate = '20120601'
SELECT DATEPART(YEAR, Date) AS YEAR,
DATEPART(WEEK, Date) AS WeekNum
FROM ( SELECT DATEADD(WEEK, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #StartDate) AS Date
FROM Sys.All_Objects
) Dates
WHERE Date < #endDate
Where the dates subquery provides a list of dates at one week intervals between your start and end dates.
So in your example the end result would be something like:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SET #StartDate = '20110101'
SET #EndDate = '20120601'
;WITH Data AS
( SELECT MemberGroupID,
Result2,
Count(*) AS ExampleCount,
Year,
Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
), Dates AS
( SELECT DATEPART(YEAR, Date) AS YEAR,
DATEPART(WEEK, Date) AS WeekNum
FROM ( SELECT DATEADD(WEEK, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #StartDate) AS Date
FROM Sys.All_Objects
) Dates
WHERE Date < #endDate
)
SELECT YearNum,
WeeNum,
MemberID,
Result2,
COALESCE(ExampleCount, 0) AS ExampleCount
FROM Dates
LEFT JOIN Data
ON YearNum = Data.Year
AND WeekNum = Data.Week

Sql to select row from each day of a month

I have a table which store records of all dates of a month. I want to retrieve some data from it. The table is so large that I should only selecting a fews of them. If the records have a column "ric_date" which is a date, how can I select records from each of the dates in a month, while selecting only a fews from each date?
The table is so large that the records for 1 date can have 100000 records.
WITH T AS (
SELECT ric_date
FROM yourTable
WHERE rice_date BETWEEN #start_date AND #end_date -- thanks Aaron Bertrand
GROUP BY ric_date
)
SELECT CA.*
FROM T
CROSS APPLY (
SELECT TOP 500 * -- 'a fews'
FROM yourTable AS YT
WHERE YT.ric_date = T.ric_date
ORDER BY someAttribute -- not required, but useful
) AS CA
Rough idea. This will get the first three rows per day for the current month (or as many that exist for any given day - there may be days with no rows represented).
DECLARE
#manys INT = 3,
#month DATE = DATEADD(DAY, 1-DAY(GETDATE()), DATEDIFF(DAY, 0, GETDATE()));
;WITH x AS
(
SELECT some_column, ric_date, rn = ROW_NUMBER() OVER
(PARTITION BY ric_date ORDER BY ric_date)
FROM dbo.data
WHERE ric_date >= #month
AND ric_date < DATEADD(MONTH, 1, #month)
)
SELECT some_column, ric_date FROM x
WHERE rn <= #manys;
If you don't have supporting indexes (most importantly on ric_date), this won't necessarily scale well at the high end.