Alternative for Sys_Calendar of teradata in snowflake - snowflake-cloud-data-platform

Do we have any alternative for teradata Sys_Calendar.CALENDAR function in snowflake? I couldnt find any table or builtin functions to achive this
[The Sys_Calendar.CALENDAR system view helps to extend the properties of a DATE data type column by means of a join. The columns of the view contain data only for the active calendar for the session.
The calendar dates range from 1900 to 2100 and are stored in a table in the Sys_Calendar database.]

BASIC -> BELOW
ADVANCED -> AUTOMATICALLY PULLS IN HOLIDAYS AND PRETTY EMOJI FLAGS FOR YOUR COUNTRY BASED ON CURRENT_IP()
WITH GAPLESS_ROW_NUMBERS AS (
SELECT ROW_NUMBER() OVER (ORDER BY seq4()) - 1 as "ROW_NUMBER"
FROM TABLE(GENERATOR(rowcount => 366 * (2100 - 1970)) )
-- rowcount is 366 days x (2100 - 1970) years to cover leap years. A later filter can remove the spillover days
)
SELECT
DATEADD('DAY', ROW_NUMBER, DATE(0))::DATE as DATE -- Dimension starts on 1970-01-01 but a different value can be entered if desired - replace DATE(0) with '1900-01-01' to start at 1900 for example
, EXTRACT(year FROM DATE) as YEAR
, EXTRACT(month FROM DATE) as MONTH
, EXTRACT(day FROM DATE) as DAY
, EXTRACT(dayofweek FROM DATE) as DAY_OF_WEEK
, EXTRACT(dayofyear FROM DATE) as DAY_OF_YEAR
, EXTRACT(quarter FROM "DATE") as QUARTER
, MIN("DAY_OF_YEAR") OVER (PARTITION BY "YEAR", "QUARTER") as "QUARTER_START_DAY_OF_YEAR"
, "DAY_OF_YEAR" - "QUARTER_START_DAY_OF_YEAR" + 1 as "DAY_OF_QUARTER"
, TO_VARCHAR("DATE", 'MMMM') as "MONTH_NAME"
, TO_VARCHAR("DATE", 'MON') as "MONTH_NAME_SHORT"
, CASE "DAY_OF_WEEK"
WHEN 0 THEN 'Sunday'
WHEN 1 THEN 'Monday'
WHEN 2 THEN 'Tuesday'
WHEN 3 THEN 'Wednesday'
WHEN 4 THEN 'Thursday'
WHEN 5 THEN 'Friday'
WHEN 6 THEN 'Saturday'
END as "DAY_NAME"
, CASE "DAY_OF_WEEK"
WHEN 0 THEN TRUE
WHEN 6 THEN TRUE
ELSE FALSE END as "IS_WEEKEND"
, TO_VARCHAR("DATE", 'DY') as "DAY_NAME_SHORT"
, EXTRACT(yearofweekiso FROM "DATE") as "ISO_YEAR"
, EXTRACT(weekiso FROM "DATE") as "ISO_WEEK"
, CASE
WHEN "ISO_WEEK" <= 13 THEN 1
WHEN "ISO_WEEK" <= 26 THEN 2
WHEN "ISO_WEEK" <= 39 THEN 3
ELSE 4
END as "ISO_QUARTER"
, EXTRACT(dayofweekiso FROM "DATE") as "ISO_DAY_OF_WEEK"
, MAX("DAY_OF_YEAR") OVER (PARTITION BY "YEAR") as "DAYS_IN_YEAR"
, "DAYS_IN_YEAR" - "DAY_OF_YEAR" as "DAYS_REMAINING_IN_YEAR"
FROM
GAPLESS_ROW_NUMBERS
WHERE "YEAR" BETWEEN 1950 AND 2050
GROUP BY DAY_OF_YEAR,YEAR ,QUARTER ,GAPLESS_ROW_NUMBERS.ROW_NUMBER
ORDER BY 1,2,3,4

Related

Add number of working days to Date in snowflake

I'm trying to add no. of business days to a date field.
Below is my logic, this is not working if day falls on Monday and if no. of days are more than 5.
dateadd(DAY,
(iff(dayofweek(to_date(Start_Date_Column) ) = 1, 0 ,
(TRUNCATE(((dayofweek(to_date(Start_Date_Column)) + No_DAYS - 1)/5)) * 2)) + No_DAYS) , to_date(Start_Date_Column));
e.g.. For the below scenario the date is moving to 2021-01-09(which is Saturday) instead of monday(2021-01-11)
select
dateadd(DAY,
(iff(dayofweek(to_date('2021-01-04') ) = 1, 0 ,
(TRUNCATE(((dayofweek(to_date('2021-01-04')) + 5 - 1)/5)) * 2)) + 5) , to_date('2021-01-04'))
Recursive CTE to get next business day, after adding specific number of business days to a date.
WITH RECURSIVE
biz_day (rown, dt) AS
(
SELECT 1,'2022-05-07'::date
union all
SELECT
case when dayname(dateadd(day,1,dt)) not in ('Sat','Sun')
then rown+1
else rown end ,
dateadd(day,1,dt)
from biz_day where rown <= $No_DAYS
)
SELECT min(dt) orig_dt,max(dt) nxt_biz_date FROM biz_day;
ORIG_DT
NXT_BIZ_DATE
2022-05-07
2022-05-20
Original table -
Column next_dy to be modified with next business day, based on specific number of days (e.g. 10)
select * from dates;
DY
NEXT_DY
2022-05-05
NULL
2022-05-09
NULL
2022-05-11
NULL
2022-05-14
NULL
2022-05-15
NULL
2022-05-17
NULL
2022-05-27
NULL
(UDF) Function creation including recursive CTE for getting next biz day -
CREATE OR REPLACE FUNCTION next_b_date(dt date,no_day number)
RETURNS date
LANGUAGE SQL
AS
$$
WITH RECURSIVE
biz_day (rown, cte_dt) AS
(
SELECT 1,dt
union all
SELECT
case when dayname(dateadd(day,1,cte_dt)) not in ('Sat','Sun')
then rown+1
else rown end ,
dateadd(day,1,cte_dt)
from biz_day where rown <= no_day
)
SELECT max(cte_dt) nxt_biz_date FROM biz_day
$$;
Anonymous Block to call function and update source table -
EXECUTE IMMEDIATE $$
DECLARE
p_dt date;
n_dt date;
no_days number;
c1 CURSOR FOR SELECT dy FROM dates;
BEGIN
for record in c1 do
p_dt:=record.dy;
no_days:=10;
n_dt:=(select next_b_date(:p_dt,:no_days));
update dates set next_dy = :n_dt where dy=:p_dt;
end for;
RETURN 0;
END;
$$
;
Table after running the update -
select * from dates;
DY
NEXT_DY
2022-05-05
2022-05-19
2022-05-09
2022-05-23
2022-05-11
2022-05-25
2022-05-14
2022-05-27
2022-05-15
2022-05-27
2022-05-17
2022-05-31
2022-05-27
2022-06-10
Below can also be used, which has little-bit of hard-coding
i.e. multiply number of days with 3 to go bit over extra in days
(to compensate for intermediate sat/sun) for fetching business days.
set no_days=11;
update dates dt1 set next_dy = dt2.n_dy from
(
with cte1 as
(select row_number() over (partition by dy order by seq4()) rn,dy,
dateadd(day,rn,dy) day1 from dates,
table(generator(rowcount=>$no_days*3))), cte2 as
(select row_number() over (partition by dy order by seq4()) rn,dy,day1
from cte1 where dayname(day1)
not in ('Sat','Sun') )
select dy,max(day1) n_dy from cte2 where rn<=$no_days
group by dy order by dy
) dt2
where dt1.dy = dt2.dy;
Table after update -
select * from dates;
DY
NEXT_DY
2022-05-05
2022-05-20
2022-05-09
2022-05-24
2022-05-11
2022-05-26
2022-05-14
2022-05-30
2022-05-15
2022-05-30
2022-05-17
2022-06-01
2022-05-27
2022-06-13
Old - solution (not valid) to add days and move to next Monday -
To get next business day, check if day after adding is sat or sun and if so, get to next Monday using function next_day
Query with some test-data:
with date_cte(bizday) as
(select * from values
('2022-05-05'::date),
('2022-05-11'::date),
('2022-05-14'::date),
('2022-05-15'::date),
('2022-05-21'::date),
('2022-05-27'::date)
)
select bizday orig_date,dayname(bizday) orig_day,
$No_DAYS No_days,
dateadd(day,$No_DAYS,bizday) next_day,
case when dayname(dateadd(day,$No_DAYS,bizday)) = 'Sat'
then next_day(dateadd(day,$No_DAYS,bizday),'Monday')
when dayname(dateadd(day,$No_DAYS,bizday)) = 'Sun'
then next_day(dateadd(day,$No_DAYS,bizday),'Monday')
else dateadd(day,$No_DAYS,bizday) end next_b_day,
dayname(next_b_day) next_b_dayname
from date_cte;
ORIG_DATE
ORIG_DAY
NO_DAYS
NEXT_DAY
NEXT_B_DAY
NEXT_B_DAYNAME
2022-05-05
Thu
10
2022-05-15
2022-05-16
Mon
2022-05-11
Wed
10
2022-05-21
2022-05-23
Mon
2022-05-14
Sat
10
2022-05-24
2022-05-24
Tue
2022-05-15
Sun
10
2022-05-25
2022-05-25
Wed
2022-05-21
Sat
10
2022-05-31
2022-05-31
Tue
2022-05-27
Fri
10
2022-06-06
2022-06-06
Mon
Refer for date-time
Also, you might need to add more here as the definition of a business day can be varied.

How to filter out from count distinct query

I am trying to calculate numbers of customers whom are active in the past 3 and 6 months.
SELECT COUNT (DISTINCT CustomerNo)
FROM SalesDetail
WHERE InvoiceDate > (GETDATE() - 180) AND InvoiceDate < (GETDATE() - 90)
SELECT COUNT (DISTINCT CustomerNo)
FROM SalesDetail
WHERE InvoiceDate > (GETDATE() - 90)
However, based on above query, I'll get count Customers which has been active for both in the last 3 months and the last 6 months, even if there are duplicates like this.
Customer A bought once in past 3 months
Customer A bought once in past 6 months too
How do I filter out the customers, so that if customer A has been active in both past 3 and 6 months, he/she will only be counted in the 'active in past 3 months' query and not in the 'active in past 6 months' too.
I solve this problem this way
Let us consider you have following table. You might have more columns but for the result you want, we only require customer_id and date they bought something on.
CREATE TABLE [dbo].[customer_invoice](
[id] [int] IDENTITY(1,1) NOT NULL,
[customer_id] [int] NULL,
[date] [date] NULL,
CONSTRAINT [PK_customer_invoice] PRIMARY KEY([id]);
I created this sample data on this table
INSERT INTO [dbo].[customer_invoice]
([customer_id]
,[date])
VALUES
(1,convert(date,'2019-12-01')),
(2,convert(date,'2019-11-05')),
(2,convert(date,'2019-8-01')),
(3,convert(date,'2019-7-01')),
(4,convert(date,'2019-4-01'));
Lets not try to jump directly on the final solution directly but take a single leap each time.
SELECT customer_id, MIN(DATEDIFF(DAY,date,GETDATE())) AS lastActiveDays
FROM customer_invoice GROUP BY customer_id;
The above query gives you the number of days before each customer was active
customer_id lastActiveDays
1 15
2 41
3 168
4 259
Now We will use this query as subquery and Add a new column ActiveWithinCategory so that in later step we can group our data by the column.
SELECT customer_id, lastActiveDays,
CASE WHEN lastActiveDays<90 THEN 'active within 3 months'
WHEN lastActiveDays<180 THEN 'active within 6 months'
ELSE 'not active' END AS ActiveWithinCategory
FROM(
SELECT customer_id, MIN(DATEDIFF(DAY,date,GETDATE())) AS lastActiveDays
FROM customer_invoice GROUP BY customer_id
)AS temptable;
This query gives you the the following result
customer_id lastActiveDays ActiveWithinCategory
1 15 active within 3 months
2 41 active within 3 months
3 168 active within 6 months
4 259 not active
Now use the above whole thing as subquery and Group the data using ActiveWithinCategory
SELECT ActiveWithinCategory, COUNT(*) AS NumberofCustomers FROM (
SELECT customer_id, lastActiveDays,
CASE WHEN lastActiveDays<90 THEN 'active within 3 months'
WHEN lastActiveDays<180 THEN 'active within 6 months'
ELSE 'not active' END AS ActiveWithinCategory
FROM(
SELECT customer_id, MIN(DATEDIFF(DAY,date,GETDATE())) AS lastActiveDays
FROM customer_invoice GROUP BY customer_id
)AS temptable
) AS FinalResult GROUP BY ActiveWithinCategory;
And Here is your final result
ActiveWithinCategory NumberofEmployee
active within 3 months 2
active within 6 months 1
not active 1
If you want to achieve same thing is MySQL Database
Here is the final Query
SELECT ActiveWithinCategory, count(*) NumberofCustomers FROM(
SELECT MIN(DATEDIFF(curdate(),date)) AS lastActiveBefore,
IF(MIN(DATEDIFF(curdate(),date))<90,
'active within 3 months',
IF(MIN(DATEDIFF(curdate(),date))<180,'active within 6 months','not active')
) ActiveWithinCategory
FROM customer_invoice GROUP BY customer_id
) AS FinalResult GROUP BY ActiveWithinCategory;
I suspect that you want to do conditional aggregation here:
SELECT
CustomerNo,
COUNT(CASE WHEN InvoiceDate > GETDATE() - 90 THEN 1 END) AS cnt_last_3,
COUNT(CASE WHEN InvoiceDate > GETDATE() - 180 AND InvoiceDate < GETDATE() - 90
THEN 1 END) AS cnt_first_3
FROM yourTable
GROUP BY
CustomerNo;
Here cnt_last_3 is the count over the immediate past 3 months, and cnt_first_3 is the count from the 3 month period starting 6 months ago and ending 3 months ago.
If you want the distinct count you may add distinct like this
Select
count( Case when dt between getdate()- 90 and getdate() then id else null end) cnt_3_months
,count(distinct Case when dt between getdate() - 180 and getdate() - 90 then id else null end) cnt_6_months
from a

SQL - Group By Week to begin on a specific weekday without involving two transactions?

I am writing a query that returns the sum of rows for the last 10 weeks FRI-THURS.
It uses a group by to show the sum of each week:
WITH Vars (Friday) -- get current week Fridays Date
AS (
SELECT CAST(DATEADD(DAY,(13 - (##DATEFIRST + DATEPART(WEEKDAY,GETDATE())))%7,GETDATE()) AS DATE) As 'Friday'
)
SELECT datepart(week, DateField) AS WeekNum, COUNT(*) AS Counts
FROM Table
WHERE DateField >= DATEADD(week,-9, (SELECT Friday from Vars))
GROUP BY datepart(week, DateField)
ORDER BY WeekNum DESC
The problem is every week starts on Monday so the Group By doesn't group the dates on how I want it. I want a week to be defined as FRI-THURS.
One workaround to this is to use DATEFIRST. e.g:
SET DATEFIRST = 5; --set beginning of each week to Friday
WITH Vars (Friday) -- get current week Fridays Date
... rest of query
However due to limitations on the interface I am writing this query I cannot have two separate statements run. It needs to be one query with no semicolons.
How can I achieve this?
This should do it. First pre-compute once the StartingFriday of 9 weeks ago, rather than doing that for each row. Then compute the dfYear and dfWeek giving them alias-es, where their DateField is after the starting friday. Lastly, Count/GroupBy/OrderBy.
Declare #StartingFriday as date =
DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,GETDATE()) +1) % 7) , GETDATE())) ) ;
SELECT dfYear, dfWeek, COUNT(*) AS Counts
FROM
(Select -- compute these here, and use alias in Select, GroupBy, OrderBy
(Datepart(Year,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfYear
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfWeek
From Table
WHERE #StartingFriday <= DateField
) as aa
group by dfYear, dfWeek
order by dfYear desc, dfWeek desc
-- we want the weeknum of the (Friday on or before the DateField)
-- the % (percent sign) is the math MODULO operator.
-- used to get back to the nearest Friday,
-- day= Fri Sat Sun Mon Tue Wed Thu
-- weekday= 6 7 1 2 3 4 5
-- plus 1 = 7 8 2 3 4 5 6
-- Modulo7= 0 1 2 3 4 5 6
-- which are the days to subtract from DateField
-- to get to its Friday start of its week.
I did some testing with this
declare #dt as date = '8/17/18';
select ((DATEPART(WEEKDAY,#dt) +1) % 7) as wd
,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) as Fri
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) )as wk
,DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) as StartingFriday

Conditional scoring

I have a table that shows only the 'captured' data. For example in the below exhibit, the emp_no 17 has 2 records - for November and February (for a specified 6 month period, from July 2017). It does not have data for the other 4 months (within the 6-month date range, from previous 6 months to current date).
How can I populate these missing months (Sept, Oct, Dec) with default values for num_differences of 0 for the missing months? (for example, in this case, I want emp_no 17 to have the below (I can ignore 2018 data - only require data up to Dec 2017):
I have the script below:
declare #YMN date;
set #YMN = '20171201';
DECLARE #Emp TABLE (
[date] date,
[emp_no] int,
[num_differences] int
);
INSERT INTO #Emp VALUES
('2017-09-14', 17, 1), ('2017-12-01', 17, 1),('2017-12-18', 17, 1),('2017-12-21', 17, 1),
('2017-09-27', 17, 1), ('2017-12-04', 17, 1);
-------------------------------------------------------------------------------------------get missing dates---------------------------------------------------------------------------
;WITH cte_Emp_No AS (
SELECT DISTINCT [emp_no]
FROM #Emp
),
cte_dates AS (
SELECT [emp_no], DATEADD(month, -6, DATEADD(dd, -(DAY(dateadd(month, 1, #YMN)) - 1), dateadd(month, 1, #YMN))) AS [date]
FROM cte_Emp_No
UNION ALL
SELECT [emp_no], DATEADD(month, 1, [date]) AS [date]
FROM cte_dates
WHERE [date] < dateadd(month, 0, #YMN)
)
SELECT DISTINCT ISNULL(e.emp_no, c.emp_no) emp_no, ISNULL(e.date, c.date) date, ISNULL(e.num_differences, 0) num_differences
into ##new_table
FROM #Emp AS e
RIGHT JOIN cte_dates AS c ON YEAR(c.date) = YEAR(e.date) AND MONTH(c.date) = MONTH(e.date)
-----------------------------------------------------------------------------------------------MAIN CTE------------------------------------------------------------------------------
;with cte_RawScore as
(
select emp_no
, date YMN
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) = 0 then num_differences else 0 end) as thismonth
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -2 and 0 then num_differences else 0 end) as last3month
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -5 and 0 then num_differences else 0 end) as last6month
from ##new_table d
group by emp_no, date
)
select
emp_no
,YMN
,case when last6month = 0 then 5
when last3month = 0 then 4
when thismonth = 0 then 3
when thismonth <= 3 then 2
else 1 end RawScore
from cte_RawScore
ORDER BY day(YMN) desc
drop table ##new_table
I want this the scoring only to be applicable for 6 months from and after July 2017. i.e. the #YMN is a variable that stores the year month number; and the score, according to the above rule applies to the 6 months from 201707.
So 201707 is 1 month,
201708 is 2 months, etc, up to 201712
I wish to have a list of employees with their associated scores, based on the rules mentioned below .
That’s, :
A score of 5 if 0 differences in 6 consecutive months ( from July to December) ;
A score of 4 if 0 differences in 3 consecutive months (from July to December);
A score of 3 if 0 differences for 1 month ( from July to December);
A score of 2 if 1 to 3 differences for 1 month (from July to December);
A score of 1 if 4 or more differences in 1 month (from July to December).
I get the number of differences from a table, but some employees do not appear for certain months; hence I want to give them a difference of 0 if they do not appear for that particular month.
Please assist.
I think I understand what you're getting at. Let me give you a simplified example. You need a table full of dates to join to. In data warehousing we use a Date dimension which has attributes about every date.
For your example your date dimension table could just have Month names or numbers:
1
2
...
12
Let's call this table Months.
Then you would do something like this, to count a zero for months with no data. Here I'm using what's called a Common Table Expression or CTE (the part with the WITH) in place of a table, since I'm not concerned with creating a permanent table right now.
WITH Months AS (
SELECT 1 AS MonthNumber UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12
)
SELECT M.MonthNumber, COUNT(*)
FROM Months as M
LEFT JOIN MyData as D
ON MONTH(D.SomeDateValue) = M.MonthNumber
GROUP BY M.MonthNumber
This will guarantee every month appears with a count, perhaps of zero.

Determine number of Days in month returns different count for Month of March

Here is a weird one for you all.
I need to determine the number of days in a Month
;WITH cteNetProfit AS
(
---- NET PROFIT
SELECT DT.CreateDate
, SUM(DT.Revenue) as Revenue
, SUM(DT.Cost) as Cost
, SUM(DT.GROSSPROFIT) AS GROSSPROFIT
FROM
(
SELECT CAST([createDTG] AS DATE) as CreateDate
, SUM(Revenue) as Revenue
, SUM(Cost) as Cost
, SUM(REVENUE - COST) AS GROSSPROFIT
FROM [dbo].[CostRevenueSpecific]
WHERE CAST([createDTG] AS DATE) > CAST(GETDATE() - 91 AS DATE)
AND CAST([createDTG] AS DATE) <= CAST(GETDATE() - 1 AS DATE)
GROUP BY createDTG
UNION ALL
SELECT CAST([CallDate] AS DATE) AS CreateDate
, SUM(Revenue) as Revenue
, SUM(Cost) as Cost
, SUM(REVENUE - COST) AS GROSSPROFIT
FROM abc.PublisherCallByDay
WHERE CAST([CallDate] AS DATE) > CAST(GETDATE() - 91 AS DATE)
AND CAST([CallDate] AS DATE) <= CAST(GETDATE() - 1 AS DATE)
GROUP BY CALLDATE
) DT
GROUP BY DT.CreateDate
)
select distinct MONTH(CREATEDATE), DateDiff(Day,CreateDate,DateAdd(month,1,CreateDate))
FROM cteNetProfit
For some reason it is returning two different results for the month of March 2016 one result is 30 and the other 31(which of course is correct) I validate that the underlying data only has 31 days worth of data for the Month of March. Since Feb is a leap year can this affect the DATEDIFF function. The remaining months return the correct #.
2 29
3 31
3 30
4 30
5 31
Thanks for the input, however, I found the solution elsewhere
select Distinct MONTH(CREATEDATE), Day(EOMONTH(CreateDate))
FROM cteNetProfit
The difference comes when you hit the 2016-03-31 date. If you run the query below for 2016-03-30 and 2016-03-31, the results of adding 1 MONTH using DATEADD, in both instances, is 2016-04-30. It returns the last day of the next month.
SELECT DATEADD(MONTH,1,'2016-03-30') , DATEADD(MONTH,1,'2016-03-31')
This syntax seemed to work (courtesy of https://raresql.com/2013/01/06/sql-server-get-number-of-days-in-month/).
SELECT DAY(DATEADD(ms,-2,DATEADD(MONTH, DATEDIFF(MONTH,0,#DATE)+1,0))) AS [Current Month]

Resources