Nested Hierarchy to Calculated Date - sql-server

I am trying to convert an nested hierarchy from a currency holiday table to select the specific date occurrence for 2022.
Sample source table for explanation:
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| hol_ccy | holiday | date_type | hol_dt | hol_day_no | calloc_id | base_hol_id |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| CHF | Good Friday | Date | 2022-04-15 00:00:00.000 | 0 | 9169 | NULL |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| CHF | Easter Monday | Ordinal Based | 1899-12-30 00:00:00.000 | 3 | 9188 | 9169 |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| CHF | Easter | Ordinal Based | 1899-12-30 00:00:00.000 | 2 | 9189 | 9169 |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| CHF | Ascension | Ordinal Based | 1899-12-30 00:00:00.000 | 39 | 9190 | 9189 |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
| CHF | Whit Monday | Ordinal Based | 1899-12-30 00:00:00.000 | 50 | 9191 | 9189 |
+---------+---------------+---------------+-------------------------+------------+-----------+-------------+
Desired Output:
CCY HOLIDAY DATE
CHF Good Friday 2022-04-15 00:00:00.000
CHF Easter Monday 2022-04-18 00:00:00.000
CHF Easter 2022-04-17 00:00:00.000
CHF Ascension 2022-05-26 00:00:00.000
CHF Whit Monday 2022-06-06 00:00:00.000
Row 1 is a given fact entered into the database for each year. Given as date_type: date
Rows 2 & 3 are based on Row 1. Each adding the value of hol_day_no to row 1's hol_dt (date). This relationship is described in calloc_id and base_hol_id columns
Rows 4 & 5 are based on Row 3.
I cant figure out how to treat the nesting of the ordinal based date types in SQL.
Any pointers would be appreciated.

It looks like a pretty simple recursive CTE is needed. You just need to add the child row's day number to the parent row's date.
WITH cte AS (
SELECT
t.hol_ccy,
t.holiday,
t.hol_dt,
t.calloc_id
FROM YourTable t
WHERE t.base_hol_id IS NULL
UNION ALL
SELECT
t.hol_ccy,
t.holiday,
DATEADD(day, t.hol_day_no, cte.hol_dt),
t.calloc_id
FROM YourTable t
JOIN cte ON cte.calloc_id = t.base_hol_id
)
SELECT
t.hol_ccy,
t.holiday,
t.hol_dt
FROM cte t;
db<>fiddle

Related

Create ToDate from previous record

I have a dataset with a column FromDate and I am trying to create another column ToDate based on the FromDate from the previous record.
The corresponding ToDate for the latest FromDate will default to '9999-12-31'. Is there anyway I can do this using SQL?
Current Sample Dataset
ID| FromDate
1 | 2022-02-08
1 | 2022-01-05
1 | 2022-01-02
2 | 2022-04-03
2 | 2022-01-07
2 | 2022-12-04
Expected Output
ID| FromDate | ToDate
1 | 2022-02-08 | 9999-12-31
1 | 2022-01-05 | 2022-02-07
1 | 2022-01-02 | 2022-01-04
2 | 2022-04-03 | 9999-12-31
2 | 2022-01-07 | 2022-04-02
2 | 2021-12-04 | 2022-01-06
Using Lead/Lag you could do something like:
SELECT ID, FromDate,
COALESCE(LEAD(DATEADD(DAY, -1, FromDate)) OVER (PARTITION BY ID ORDER BY FromDate), '99991231') as ToDate
FROM yourtable;

Selecting unique records based on date of effect, ending on date of discontinue

I have an interesting conundrum and I am using SQL Server 2012 or SQL Server 2016 (T-SQL obviously). I have a list of products, each with their own UPC code. These products have a discontinue date and the UPC code gets recycled to a new product after the discontinue date. So let's say I have the following in the Item_UPCs table:
Item Key | Item Desc | UPC | UPC Discontinue Date
123456 | Shovel | 0009595959 | 2018-04-01
123456 | Shovel | 0007878787 | NULL
234567 | Rake | 0009595959 | NULL
As you can see, I have a UPC that gets recycled to a new product. Unfortunately, I don't have an effective date for the item UPC table, but I do in an items table for when an item was added to the system. But let's ignore that.
Here's what I want to do:
For every inventory record up to the discontinue date, show the unique UPC associated with that date. An inventory record consists of the "Inventory Date", the "Purchase Cost", the "Purchase Quantity", the "Item Description", and the "Item UPC".
Once the discontinue date is over with (e.g.: it's the next day), start showing only the UPC that is in effect.
Make sure that no duplicate data exists and the UPCs are truly being "attached" to each row per whatever the date is in the query.
Here is an example of the inventory details table:
Inv_Key | Trans_Date | Item_Key | Purch_Qty | Purch_Cost
123 | 2018-05-12 | 123456 | 12.00 | 24.00
108 | 2018-03-22 | 123456 | 8.00 | 16.00
167 | 2018-07-03 | 234567 | 12.00 | 12.00
An example query:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Item_UPCs iu ON iu.Item_Key = id.Item_Key
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
The real query I have is far too long to post here. It has three CTEs and the resultant query. This is simply a mockup. Here is an example result set:
Site_ID | Item_Key | Item_Desc | Item_Department | Item_Category | UPC | UPC_Discontinue Date | Trans_Date | Purch_Cost | Purch_Qty
2457 | 123456 | Shovel | Digging Tools | Shovels | 0009595959 | 2018-04-01 | 2018-03-22 | 16.00 | 8.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-03-22 | 16.00 | 8.00
2457 | 234567 | Rakes | Garden Tools | Rakes | 0009595959 | NULL | 2018-07-03 | 12.00 | 12.00
2457 | 123456 | Shovel | Digging Tools | Shovels | 0007878787 | NULL | 2018-05-12 | 24.00 | 12.00
Do any of you know how I can "assign" a UPC to a specific range of dates in my query and then "assign" an updated UPC to the item for every effective date thereafter?
Many thanks!
Given your current Item_UPC table, you can generate effective start dates from the Discontinue Date using the LAG analytic function:
With Effective_UPCs as (
select [Item_Key]
, [Item_Desc]
, [UPC]
, coalesce(lag([UPC_Discontinue_Date])
over (partition by [Item_Key]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
),
lag([UPC_Discontinue_Date])
over (partition by [UPC]
order by coalesce( [UPC_Discontinue_Date]
, datefromparts(9999,12,31))
)) [UPC_Start_Date]
, [UPC_Discontinue_Date]
from Item_UPCs i
)
select * from Effective_UPCs;
Which yields the following Results:
| Item_Key | Item_Desc | UPC | UPC_Start_Date | UPC_Discontinue_Date |
|----------|-----------|------------|----------------|----------------------|
| 123456 | Shovel | 0007878787 | 2018-04-01 | (null) |
| 123456 | Shovel | 0009595959 | (null) | 2018-04-01 |
| 234567 | Rake | 0009595959 | 2018-04-01 | (null) |
This function produces a fully open ended interval where both the start and discontinue dates could be null indicating that it's effective for all time. To use this in your query simply reference the Effective_UPCs CTE in place of the Item_UPCs table and add a couple additional predicates to take the effective dates into consideration:
SELECT DISTINCT
s.SiteID
,id.Item_Key
,iu.Item_Desc
,iu.Item_Department
,iu.Item_Category
,iu.Item_Subcategory
,iu.UPC
,iu.UPC_Discontinue_Date
,id.Trans_Date
,id.Purch_Cost
,id.Purch_Qty
FROM Inventory_Details id
INNER JOIN Effective_UPCs iu
ON iu.Item_Key = id.Item_Key
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)
INNER JOIN Sites s ON s.Site_Key = id.Site_Key
Note that the above query uses a partially open range (UPC_Start_Date < trans_date <= UPC_Discontinue_Date instead of <= for both inequalities) this prevents transactions occurring exactly on the discontinue date from matching both the prior and next Item_Key record. If transactions that occur exactly on the discontinue date should match the new record and not the old simply swap the two inequalities:
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date <= id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date < iu.UPC_Discontinue_Date)
instead of
and (iu.UPC_Start_Date is null or iu.UPC_Start_Date < id.Trans_Date)
and (iu.UPC_Discontinue_Date is null or id.Trans_Date <= iu.UPC_Discontinue_Date)

SQL Get 12 weeks as columns

i need help on the following.
I have the basic query below:
select count(transactions)
from tx
where customer = 'AA'
This gives me a count of all transactions for the relevant client.
What I want is a query that gives me the same output but broken down into the LATEST last 12 weeks (Monday-Sunday is one full week). These values should be presented as 12 columns with the header of each column presented as the last date of the week (ie Sunday's date).
Furthermore the total transactions are split into status- failed and success. I would like the rows of the transactions to be failed and success so the final table would look like this:
25/03/2018 (week 1)| 01/04/2018| ........ |17/06/2018 << (week 12)
Success 100 | 200 | ........ | 150
Failed 3 | 4 | ........ | 6
Any ideas how this can be done?
Thanks you in advance
Returning pivoted data is usually a lot more hassle than it is worth and you should just leave this up to your presentation layer, which will handle your dynamic columns with much more grace. Regardless of the presentation layer you are using (SSRS, Excel, Power BI, etc), you will get the most flexibility by providing it a standard set of unpivoted data:
declare #t table (id int, TransactionDate date, Outcome varchar(8));
insert into #t values
(1,getdate()-1,'Success')
,(2,getdate()-2,'Success')
,(3,getdate()-2,'Success')
,(4,getdate()-3,'Success')
,(5,getdate()-6,'Failed')
,(6,getdate()-6,'Success')
,(7,getdate()-7,'Success')
,(8,getdate()-8,'Success')
,(9,getdate()-8,'Success')
,(10,getdate()-10,'Success')
,(11,getdate()-10,'Failed')
,(12,getdate()-11,'Success')
,(13,getdate()-13,'Success')
;
with w(ws) as(select dateadd(week, datediff(week,0,getdate())-w, 0) -- Monday at the start of the week, minus w.w weeks for all 12
from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) as w(w)
)
,d(ws,d) as(select w.ws
,dateadd(day,d.d,w.ws) as d -- Each day that makes up each week for equijoin to Transactions table
from w
cross join (values(0),(1),(2),(3),(4),(5),(6)) as d(d)
)
select d.ws as WeekStart
,t.Outcome
,count(t.TransactionDate) as Transactions
from d
left join #t as t
on d.d = t.TransactionDate
group by d.ws
,t.Outcome
order by d.ws
,t.Outcome;
Output:
+-------------------------+---------+--------------+
| WeekStart | Outcome | Transactions |
+-------------------------+---------+--------------+
| 2018-04-09 00:00:00.000 | NULL | 0 |
| 2018-04-16 00:00:00.000 | NULL | 0 |
| 2018-04-23 00:00:00.000 | NULL | 0 |
| 2018-04-30 00:00:00.000 | NULL | 0 |
| 2018-05-07 00:00:00.000 | NULL | 0 |
| 2018-05-14 00:00:00.000 | NULL | 0 |
| 2018-05-21 00:00:00.000 | NULL | 0 |
| 2018-05-28 00:00:00.000 | NULL | 0 |
| 2018-06-04 00:00:00.000 | NULL | 0 |
| 2018-06-11 00:00:00.000 | NULL | 0 |
| 2018-06-11 00:00:00.000 | Success | 2 |
| 2018-06-18 00:00:00.000 | NULL | 0 |
| 2018-06-18 00:00:00.000 | Failed | 2 |
| 2018-06-18 00:00:00.000 | Success | 5 |
| 2018-06-25 00:00:00.000 | NULL | 0 |
| 2018-06-25 00:00:00.000 | Success | 4 |
+-------------------------+---------+--------------+

How to sum up values in a column for each week number?

I need to sum up values from Money column for each WeekNumber.
Now I have view:
WeekNumber | DayTime | Money
---------------------------------------
1 | 2012-01-01 | 20.4
1 | 2012-01-02 | 30.5
1 | 2012-01-03 | 55.1
2 | 2012-02-01 | 67.3
2 | 2012-02-02 | 33.4
3 | 2012-03-01 | 11.8
3 | 2012-03-04 | 23.9
3 | 2012-03-05 | 34.3
4 | 2012-04-01 | 76.6
4 | 2012-04-02 | 90.3
Tsql:
SELECT datepart(week,DayTime) AS WeekNumber, DayTime, Money FROM dbo.Transactions
In conclusion, I would like to get something like this:
WeekNumber | DayTime | Sum
---------------------------------------
1 | 2012-01-01 | 106
2 | 2012-02-02 | 100.7
3 | 2012-03-03 | 470
4 | 2012-04-01 | 166.9
DayTime should be random for each Week Number but exists in column DayTime from view above.
Please, be free to write your ideas. Thanks.
SELECT datepart(week,DayTime) AS WeekNumber
, MIN(DayTime) DayTime --<-- Instead of random get first date from your data in that week
, SUM(Money) AS [Sum]
FROM dbo.Transactions
GROUP BY datepart(week,DayTime)
Try this
SELECT datepart(week,DayTime) AS WeekNumber, SUM(Money) FROM dbo.Transactions GROUP BY WeekNumber
As you will have number of rows for each week you cannot get DayTime with the same table. There are other ways to add that too like JOIN
Change your SQL to sum the money column. Like this
SELECT
datepart(week,DayTime) AS WeekNumber,
DayTime, Money = SUM(Money)
FROM dbo.Transactions
GROUP BY datepart(week,DayTime),DayTime
SELECT datepart(week, DayTime) AS WeekNumber
,MIN(DayTime)
,SUM(MONEY)
FROM dbo.Transactions
GROUP BY datepart(week, DayTime)

SSRS : How to show all month of a year as X axis of a graph

I'm working on a graph that shows data for a whole year, but as I'm a newbie, I can't figure out how to show all month on the X axis (including those without data)
Here is the SELECT part of my SQL request :
SELECT DISTINCT
AB.Id AS ID,
MONTH(AP.Date) AS Date
Resulting in the following data:
| ID | Date |
|--------|--------|
| 1 | 2 |
| 2 | 3 |
| 3 | 2 |
| 4 | 2 |
| 5 | 3 |
| 6 | 3 |
| 7 | 3 |
|--------|--------|
Currently I'm using =MonthName(Fields!Date.Value)) as category group, resulting in the following graph :
4| X
3| X
2|
1|
0|_________________
February March
What I want to have is something like this :
4| X
3| X
2|
1|
0|______________________________________
January February March April ...
I've found some workaround on the net using a custom table in the database, but I can't in this case, as it is a MS Dynamics project.
Thank you for your time.
You need to return the right data to your chart so that there is something to include on the axis. To do this, you need to generate every single x-axis value and then find the total that relates to it.
In the script below, I am creating a table of dates that represent the start of the month for each month of the current year. It then goes and finds all the relevant values in your table for that month and does a count. This can then be used as your chart dataset with minimal processing on the client side, which will help with report performance.
declare #t table(ID int,DateValue Date)
insert into #t values (1,'20170204'),(2,'20170307'),(3,'20170203'),(4,'20170207'),(5,'20170304'),(6,'20170302'),(7,'20170309');
with d(d) as(select cast(dateadd(mm,n,dateadd(yyyy,datediff(yyyy,0,getdate()),0)) as date) from(values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) n(n))
select d.d
,count(t.ID) as c
from d
left join #t t
on(d.d = dateadd(mm,datediff(mm,0,t.DateValue),0))
group by d.d
order by d.d;
Output:
+------------+---+
| d | c |
+------------+---+
| 2017-01-01 | 0 |
| 2017-02-01 | 3 |
| 2017-03-01 | 4 |
| 2017-04-01 | 0 |
| 2017-05-01 | 0 |
| 2017-06-01 | 0 |
| 2017-07-01 | 0 |
| 2017-08-01 | 0 |
| 2017-09-01 | 0 |
| 2017-10-01 | 0 |
| 2017-11-01 | 0 |
| 2017-12-01 | 0 |
+------------+---+
You can add a right join to your query with all the month and return its column to the output:
SELECT DISTINCT
AB.Id AS ID,
m.n AS Date
right join (select * from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) m(n) ) m
on MONTH(AP.Date)=m.n

Resources