T-SQL Use CTE to initialize variables inside a view - tsql

I need to create a view - it is composed by five UNION ALL statements. The difference between each statement is that the data is filter by different period:
For example:
SELECT RecordName
,CASE
WHEN RecordDate > DATEADD(WEEK,-1,GETUTCDATE()) THEN 'This week'
END
UNION ALL
SELECT RecordName
,CASE
WHEN RecordDate > DATEADD(WEEK,-2,GETUTCDATE()) THEN 'Previos week'
END
SELECT RecordName
,CASE
WHEN RecordDate > DATEADD(Year,-1,GETUTCDATE()) THEN 'Year ago'
END
Then I am creating a pivot using the view.
Anyway, the "date" condition is calculate in more complex way. I am using GETUTCDATE() function too and this will return different value any millisecond.
That's why I want to use a CTE expression in order to initialize all date condition variables or to do the calculations one time only in the CTE, and then to use this date conditions in the SELECT-UNION clause.
The problem is, I am not able to join the information from the CTE with the SELECT statement below and when I try to use the date conditions directly (without join) it is not working ("Error - Invalid Column name").
This is a example of what I am trying to do:
WITH DatePeriods(ThisWeek,LastWeek,MonthToDate,QuarterToDate,YearToDate) AS
(
SELECT DATEADD(WEEK,-1,GETUTCDATE()) AS ThisWeek
,... AS LastWeek
,... AS MonthToDate
,... AS QuarterToDate
,DATEADD(YEAR,-1,GETUTCDATE()) AS YearToDate
)
SELECT RecordName
,CASE
WHEN RecordDate > ThisWeek THEN 'This week'
END
UNION ALL
SELECT RecordName
,CASE
WHEN RecordDate > LastWeek THEN 'Previos week'
END
SELECT RecordName
,CASE
WHEN RecordDate >YearToDate THEN 'Year ago'
END

You have to use the CTE in the from clause of each query. You can do it with a cross apply.
WITH DatePeriods(ThisWeek,LastWeek,MonthToDate,QuarterToDate,YearToDate) AS
(
SELECT DATEADD(WEEK,-1,GETUTCDATE()) AS ThisWeek
,... AS LastWeek
,... AS MonthToDate
,... AS QuarterToDate
,DATEADD(YEAR,-1,GETUTCDATE()) AS YearToDate
)
SELECT RecordName
,CASE
WHEN RecordDate > ThisWeek THEN 'This week'
END
FROM YourTable
CROSS APPLY DatePeriods
UNION ALL
SELECT RecordName
,CASE
WHEN RecordDate > LastWeek THEN 'Previos week'
END
FROM YourTable
CROSS APPLY DatePeriods
SELECT RecordName
,CASE
WHEN RecordDate >YearToDate THEN 'Year ago'
END
FROM YourTable
CROSS APPLY DatePeriods

Related

Create additional columns based on other column values in PostgreSQL

I have following data in a PostgreSQL table:
trial start_date end_date
1 20_12_2001 20_01_2005
The expected output is below:
trial start_date end_date Date[(start_end_date)] marker_start_end
1 20_12_2001 20_01_2005 20_12_2001 start
1 20_12_2001 20_01_2005 20_01_2005 end
Is there a way to calculate the additional two columns (Date[(start_end_date)], marker_start_end) without join, but a CASE expression
You can use a lateral join to turn two columns into two rows:
select *
from the_table t
cross join lateral (
values (t.start_date, 'start'), (t.end_date, 'end')
) as x(start_end_date, marker);
The UNION ALL solution might be faster though.
UNION ALL
select trial, start_date, end_date, start_date as date, 'start' marker_start_end from table1
union all
select trial, start_date, end_date, end_date as date, 'end' marker_start_end from table1
UNNEST with CASE
select trial, start_date, end_date,
case when a.num = 1 then start_date else end_date end date,
case when a.num = 1 then 'start' else 'end' end marker_start_end from
(
select trial, start_date, end_date,
unnest(array[1,2]) num from table1
) a
Hidden JOIN (but still join)
select
trial,
start_date,
end_date,
case when a.num = 1 then start_date else end_date end date,
marker_start_end
from table1, (values(1,'start'),(2, 'end')) a(num,marker_start_end)
Db fiddle

PostgreSQL Time Dimension (By Hours and Days) Error

I am am building a Time Dimension table in PostgreSQL with DATE_ID and DATE_DESC.
My T-SQL (works perfectly) script is:
set DATEFIRST 1
;WITH DATES AS (
SELECT CAST('2019-01-01 00:00:00.000' AS datetime) AS [DATE]
UNION ALL
SELECT DATEADD(HH,1,[DATE])
FROM DATES
WHERE DATEADD(HH,1,[DATE]) <= CAST('2019-12-31' AS datetime)
)
SELECT
DATE_ID, DATE_DESC
from
(
SELECT
CONVERT(int, CONVERT(char(8), DATE, 112)) AS DATE_ID,
DATE AS DATE_DESC
FROM
DATES)a
order by 1
OPTION (MAXRECURSION 0)
At the moment Im trying to convert this code to PostgreSQL readable one and it does not work..
Here is mine at the moment:
set EXTRACT(DOW FROM TIMESTAMP '2019-01-01 00:00:00.000')+1
;WITH DATES AS (
SELECT CAST('2019-01-01 00:00:00.000' AS timestamp) AS DATE
UNION ALL
SELECT CURRENT_DATE + INTERVAL '1 hour'
FROM DATES
WHERE CURRENT_DATE + INTERVAL '1 hour' <= CAST('2019-12-31' AS timestamp)
)
SELECT DATE_ID, DATE_DESC from
(SELECT cast(to_char((DATE)::TIMESTAMP,'yyyymmddhhmiss') as BIGInt) AS DATE_ID,
DATE AS DATE_DESC
FROM
DATES)a
order by 1
OPTION (MAXRECURSION 0)
I need all the hours (24h) between 2019-01-01 and 2019-12-31 . At the moment I think OPTION (MAXRECURSION 0) and set EXTRACT(DOW FROM TIMESTAMP '2019-01-01 00:00:00.000')+1 is not working properly.
Its a problem of Recursive CTE, In Postgresql, your desired query will be like below
WITH recursive DATES AS (
SELECT CAST('2019-01-01 00:00:00.000' AS timestamp) AS date_
UNION ALL
SELECT date_ + INTERVAL '1 hour'
FROM DATES
WHERE date_ + INTERVAL '1 hour' <= CAST('2019-12-31' AS timestamp)
)
SELECT DATE_ID, DATE_DESC from
(SELECT cast(to_char((date_)::TIMESTAMP,'yyyymmddhhmiss') as BIGInt) AS DATE_ID,
date_ AS DATE_DESC
FROM
DATES)a
order by 1
DEMO

Create New Rows based on valid to and valid from dates

I have a table that has account number, end of month valid from and end of month valid to columns.
What I need is a table that has account number and a column that has all the end of month dates of when the account was live, inclusive of end of month valid to. The Current Table looks like this
New table will need to be like this
I have tried using a calendar table and an CTE table type query but have had no success.
Any help would be great.
This can be achieved using Using multiple comma separated CTEs in a statement
Query
with t0 (i) AS (select 0 union all select 0 union all select 0 union all select 0 union all select 0 union all select 0),
t1 (i) AS (select 0 from t0 a inner join t0 b on a.i = b.i),
n (i) AS (select row_number()over(order by i) from t1),
Account_details (Account_number,valid_from,valid_to,mth,Live_date)As(
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, valid_from"Live_date"
from tbl1
union all
select Account_number,valid_from,valid_to, datediff(month,valid_from,valid_to ) mth, EOMONTH (dateadd(month,n.i,valid_from)) "Live_date"
from tbl1
inner join n on 1=1 and n.i between 1 and datediff(month,valid_from,valid_to )
)
select *
from Account_details
where Account_details.Account_number =1
order by Account_details.Account_number
Output
CTE Table t0, t1 and n will generate numbers. This is a best way to generate rows without any data.
Then the CTE table Account_details is used to pull data from the table.
Based on sql on the msdn thread how to get month end date between two dates.
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130'
SELECT TOP 100 * FROM #old
DECLARE #AccountNumber INT, #ValidFrom DATE, #ValidTo DATE
DECLARE #Cursor CURSOR
SET #Cursor = CURSOR FOR
SELECT AccountNumber, ValidFrom, ValidTo
FROM #old
OPEN #Cursor
FETCH NEXT INTO #Cursor FROM #AccountNumber, #ValidFrom, #ValidTo
WHILE ##FETCH_STATUS = 0
BEGIN
;WITH cteEndMonthDates (MonthEndDate)
AS
(
SELECT eomonth(#ValidFrom) AS MonthEndDate
UNION ALL
SELECT eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates
WHERE MonthEndDate < eomonth(#ValidTo)
)
INSERT INTO #new (AccountNumber, LiveDate)
SELECT #AccountNumber, MonthEndDate
FROM cteEndMonthDates
FETCH NEXT FROM #Cursor INTO #AccountNumber, #ValidFrom, #ValidTo
END
CLOSE #Cursor
DEALLOCATE #Cursor
SELECT * FROM #New
Edit: Or without the cursor
DECLARE #Old AS Table (AccountNumber INT, ValidFrom DATE, ValidTo DATE)
DECLARE #New AS Table (AccountNumber INT, LiveDate DATE)
INSERT INTO #old
SELECT 1, '20130630', '20131130' UNION ALL
SELECT 2, '20130630', '20131231' UNION ALL
SELECT 3, '20120430', '20120531' UNION ALL
SELECT 4, '20170331', '20171130' UNION ALL
SELECT 5, '20180430', '20190131' UNION ALL
SELECT 6, '20160430', '20180531'
SELECT TOP 100 * FROM #old
;WITH cteEndMonthDates (AccountNumber, MonthEndDate)
AS
(
SELECT AccountNumber, eomonth(ValidFrom) AS MonthEndDate
FROM #Old
UNION ALL
SELECT x.AccountNumber, eomonth( dateadd(day, 1, MonthEndDate)) AS MonthEndDate
FROM cteEndMonthDates x
JOIN #Old o ON o.AccountNumber = x.AccountNumber
WHERE MonthEndDate < eomonth(ValidTo)
)
SELECT AccountNumber, MonthEndDate
FROM cteEndMonthDates
order by AccountNumber, MonthEndDate
This should work.
;WITH Span AS (
SELECT
AccountNumber,
ValidFrom AS Valid
FROM dbo.Input
UNION ALL
SELECT
AccountNumber,
DATEADD(DAY, 1, Span.Valid) AS Valid
FROM Span
WHERE DATEADD(DAY, 1, Span.Valid) <= (SELECT ValidTo FROM dbo.Input WHERE AccountNumber = Span.AccountNumber)
)
SELECT * FROM Span
ORDER BY Span.AccountNumber, Span.Valid
OPTION (MAXRECURSION 0);

PGSQL selecting columns on certain conditions

In a table I have 5 columns day1, day2... day5. Records in the table can have all days set to TRUE or few days set to TRUE.
Is there any way in PGSQL to select only those columns of a record which have boolean value as TRUE
Example:
My table is: Course, with columns as Course Name, Day1, Day2, Day3, Day4,Day5 with record set as
English,True,False,True,False,True
German,False,False,True,True,True
French,False,True,False,True,True
What I need to display as result set is:
English,Mon,Wed,Fri
German,Wed,Thu,Fri
French,Tue,Thu,Fri
I believe something like the following should do the job. It's a bit ugly because your schema isn't the most awesome. This should work on Postgres 9+
SELECT course, string_agg(day, ',') as days_of_week
FROM
(
SELECT course, 'Mon' as day FROM yourtable WHERE day1 = 'True'
UNION ALL
SELECT course, 'Tue' as day FROM yourtable WHERE day2 = 'True'
UNION ALL
SELECT course, 'Wed' as day FROM yourtable WHERE day3 = 'True'
UNION ALL
SELECT course, 'Thu' as day FROM yourtable WHERE day4 = 'True'
UNION ALL
SELECT course, 'Fri' as day FROM yourtable WHERE day5 = 'True'
) sub
Function like iif missed suddenly but you could to create it simply:
create or replace function iif(boolean, anyelement, anyelement = null) returns anyelement
language sql
immutable
as $$
select case when $1 is null then null when $1 then $2 else $3 end
$$;
then:
select
course_name,
concat_ws(',', iif(day1,'Mon'), iif(day2,'Tue'), iif(day3,'Wed'), iif(day4,'Thu'), iif(day5, 'Fri'))
from course;

casting text to date in redshift

I have saved date as text type. There are a few invalid dates those are preventing me from running any date related operation. For e.g.
select case when deliver_date = '0000-00-00 00:00:00' then '2014-01-01' else deliver_date::date end as new_date, count(*) as cnt from some_table group by new_date
Error in query: ERROR: Error converting text to date
I am using the following work-around that seems to be working.
select left(deliver_date,10) as new_date, count(*) as cnt from sms_dlr group by new_date
But I will like to know if it is possible to convert this column to date.
You need to separate the valid and invalid date values.
One solution is to use regular expressions- I'll let you decide how thorough you want to be, but this will broadly cover date and datetime values:
SELECT
CASE
WHEN
deliver_date SIMILAR TO '[0-9]{4}-[0-9]{2}-[0-9]{2}'
OR deliver_date SIMILAR TO '[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}'
THEN TO_DATE(deliver_date, 'YYYY-MM-DD')
ELSE TO_DATE('2014-01-01', 'YYYY-MM-DD')
END as new_date,
COUNT(*) as cnt
FROM some_table
GROUP BY new_date
Try dropping the ::date part:
select
cast(
case
when deliver_date = '0000-00-00 00:00:00' then '2014-01-01'
else deliver_date
end
as date
) as new_date,
count(*) as cnt
from some_table
group by new_date