How to generate dynamic table having begin month date and end month date Teradata or SAS SQL - sql

I’d like to generate a dynamic Table with the start date of a month as a column and the end date of the month as another column.
Ideally, I’d like to provide two years, f.e. 2016 and 2021. The output I’d like to have when providing these two years is the following:
Begin_of_Month End_of_Month
2016-01-01 2016-01-31
2016-02-01 2016-02-29
.
.
.
2021-12-01 2021-12-31
Kindly note that I require the output for all years from 2016 to 2021. In my example above this would mean that 2017 2018 2019 2020 should be included.
I have tried to play with a time series function of Teradata but failed to get results.
The solution I tried to recreate in Teradata is this one: How to generate calendar table having begin month date and end month Date
Furthermore, I have tried the EXPAND ON PERIOD time series function of Teradata.

I'm sure there are some fancy ways of doing this, but I think just hitting up the built-in calendar table is probably the easiest:
SELECT DISTINCT
min(calendar_date) OVER (PARTITION BY year_of_calendar, month_of_calendar) as start_of_month,
max(calendar_date) OVER (PARTITION BY year_of_calendar, month_of_calendar) as end_of_month
FROM sys_calendar.calendar
WHERE year_of_calendar BETWEEN 2016 and 2021
To do this without a table reference, it gets a little ugly. EXPAND ON seems like an obvious route, but it errors if there is no table reference in the FROM clause. UNION suffers from the same issue, but we can cheat with UNION by using a cte. EXPAND ON is more picky and to trick it we can hijack Teradata's JSON_TABLE feature:
SELECT BEGIN(dt), PRIOR(END(dt))
FROM JSON_TABLE
(
ON (SELECT 1 as id, NEW JSON('{"startdate":"2016-01-01","enddate":"2021-12-31"}') jd)
USING
rowexpr('$')
colexpr('[{"jsonpath" : "$.startdate", "type" : "DATE"},
{"jsonpath" : "$.enddate", "type" : "DATE"}]')
) as jt(id, startdate, enddate)
EXPAND ON PERIOD(startdate, enddate) as dt BY ANCHOR MONTH_BEGIN
You could also go with a recursive CTE to build out the months, which feels less hacky, but takes longer to generate.
WITH startend AS
(
SELECT
DATE '2016-01-01' periodstartdate,
DATE '2021-12-31' AS periodenddate
)
,RECURSIVE months AS
(
SELECT periodstartdate,
periodenddate,
periodstartdate as monthstartdate,
1 as monthoffset
FROM startend
UNION ALL
SELECT periodstartdate,
periodenddate,
ADD_MONTHS(periodstartdate, monthoffset),
monthoffset + 1
FROM
months
WHERE monthoffset < months_between(periodenddate, periodstartdate)
)
SELECT monthstartdate, monthstartdate + INTERVAL '1' MONTH - INTERVAL '1' DAY as monthenddate from months;
I'd be very interested if there is a more elegant way to pull this off. Without dual or sequence generation like are present in other RDBMS, the options to build data sets with no table reference are pretty limited.

Normally EXPAND ON only works when a table is accessed in FROM, but applying some function like TRUNC or TO_DATE fools the optimizer:
WITH dummy AS
(
SELECT
2016 AS yr_start
,2021 as yr_end
,TO_DATE(TRIM(yr_start) || '-01-01') AS pd_start
,TO_DATE(TRIM(yr_end+1) || '-01-01') AS pd_end
)
SELECT
BEGIN(pd) AS Begin_of_Month
,LAST(pd) AS End_of_Month
FROM dummy
EXPAND ON PERIOD(pd_start, pd_end) AS pd
BY INTERVAL '1' MONTH

If you are going to do this in SAS then there is no need for SQL.
data want;
do year=2016 to 2021;
do month=1 to 12;
start_of_month=mdy(month,1,year);
end_of_month=intnx('month',start_of_month,0,'e');
output;
end;
end;
format start_of_month end_of_month yymmdd10.;
drop year month;
run;

Related

Snowflake SQL: Is there a way to select records between 2 years back and the current date?

I have seen a lot of questions similar to this but I have yet to see one that goes into detail of how to get records from two years back to today but include the start of the year two years back. Meaning I would like to create a function that will always give me results from January 1st two years back. For this year the results would come from 01-01-2020 to today’s date.
This is what I have so far, but in reality I am using it for a temporary table in my query.
SELECT *
FROM final
WHERE order_date BETWEEN DATEADD(‘year’, -2, current_date) AND current_date
You may use a combination of DATE_TRUNC and DATEADD to find January 1 of two years ago.
SELECT *
FROM final
WHERE order_date BETWEEN DATE_TRUNC('year', DATEADD('year', -2, current_date)) AND current_date;
What you had is close. Just truncate the the year.
You can see what happens in isolation:
select trunc('2021-03-14 08:24:12'::timestamp, 'YEAR');
-- Output: 2021-01-01 00:00:00.000
Adding to your SQL:
SELECT *
FROM final
WHERE order_date
BETWEEN trunc(DATEADD(‘year’, -2, current_date), 'YEAR') AND current_date
It is possible to construct arbitrary date using DATE_FROM_PARTS:
Creates a date from individual numeric components that represent the year, month, and day of the month.
DATE_FROM_PARTS( <year>, <month>, <day> )
For current_date:
SELECT DATE_FROM_PARTS(YEAR(current_date)-2,1,1);
-- 2020-01-01
Full query:
SELECT *
FROM final
WHERE order_date BETWEEN DATE_FROM_PARTS(YEAR(current_date)-2,1,1) AND current_date
This should be enough
where year(current_date)-year(order_date)<=2
But in case you have an order_date from the future
where year(current_date)-year(order_date)<=2 and order_date<=current_date

How do I select only the month end date from a table

I am new to SQL, here is my problem.
I have a table with daily dates:
Date:
20190101
20190102
20190103
.
**20190131**
20190201
20190202
20190203
.
**20190228**
20190301
20190302
20190303
.
**20190331**
I want to select only the month-end dates, what would be the code to do that?
thanks
I am using MS SQL Studio.
One method in standard SQL would be:
select t.*
from t
where extract(month from date + interval '1' day) <> extract(month from date);
Date/time functions vary significantly by database, so the exact functions might not match your database. However, the idea is simple: add one day and see if the month changes.
In standard SQL, you could do:
select date
from mytable
where date = date_trunc('month', date) + interval '1' month - interval '1' day
Edit
In SQL Server, you can just use eomonth(). Given a date, this functions returns the corresponding end of month, which you can compare against the date. So:
select date
from mytable
where date = eomonth(date)

Presto SQL get yyyymm minus 2 months

I am using Presto. I have an integer column (let's call the column 'mnth_nbr') showing year and month as: yyyymm. For instance, 201901. I want to have records showing all dates AFTER 201901 as well as 2 months before the given date. In this example, it would return 201811, 201812, 201901, 201902, 201903, etc. Keep in mind that my data type here is integer.
This is what I have so far (I do a self join):
select ...
from table 1 as first_table
left join table 1 as second_table
on first_table.mnth_nbr = second_table.mnth_nbr
where first_table.mnth_nbr <= second_table.mnth_nbr
I know this gives me all dates AFTER 201901, including 201901. But, I don't know how to add the 2 previous months (201811 and 201812)as explained above.
As far as the documentation, Presto DB date_parse function expects a MySQL-like date format specifier.
So the proper condition for your use case should be :
SELECT ...
FROM mytable t
WHERE
date_parse(cast(t.mnth_nbr as varchar), '%Y%m') >= date '2019-01-01' - interval '2' month
Edit
As commented by Piotr, a more optimized expression (index-friendly) would be :
WHERE
mnth_nbr >= date_format(date '2019-01-01' - interval '2', '%Y%m')
Something like this would help. first parse your int to date
date_parse(cast(first_table.mnth_nbr as varchar), 'yyyymm') > date '2019-01-01' - interval '2' month
please keep in mind that you may encounter with indexing issues with this approach.

SQL computing and reusing fiscal year calculation in sql query

I have a condition in my SQL query, using Oracle 11g database, that depends on a plan starting or ending with in a fiscal year:
(BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) OR
(BUSPLAN.END_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31'))
For now, I am passing in YEAR as a parameter. It can be computed as (pseudocode):
IF CURRENT MONTH IN (JAN, FEB, MAR):
USE CURRENT YEAR // e.g. 2015
ELSE:
USE CURRENT YEAR + 1 // e.g. 2016
Is there a way I could computer the :YEAR parameter within in an SQL query and reuse it for the :YEAR parameter?
CTEs are easy, you can make little tables on the fly. With a 1 row table you just cross join it and then you have that value available every row:
WITH getyear as
(
SELECT
CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1
END as ynum from dual
), mydates as
(
SELECT getyear.ynum || '-04-01' as startdate,
getyear.ynum+1 || '-03-31' as enddate
from getyear
)
select
-- your code here
from BUSPLAN, mydates -- this is a cross join
where
(BUSPLAN.START_DATE BETWEEN mydates.startdate AND mydates.enddate) OR
(BUSPLAN.END_DATE BETWEEN mydates.startdate AND mydates.enddate)
note, values statement is probably better if Oracle has values then the first CTE would look like this:
VALUES(CASE WHEN to_char(sysdate,'mm') in ('01','02','03') THEN
EXTRACT(YEAR FROM sysdate)
ELSE
EXTRACT(YEAR FROM sysdate) + 1)
I don't have access to Oracle so I might have bugs typos etc since I didn't test.
In the code you shared there is a problem and a potential problem.
Problem, implicit conversion to date without format string.
In (BUSPLAN.START_DATE BETWEEN (:YEAR || '-04-01') AND (:YEAR+1 || '-03-31')) two strings are being formed and then converted to dates. The conversion to date is going to change depending on the value of NLS_DATE_FORMAT. To insure that the string is converted correctly to_date(:YEAR || '-04-01', 'YYYY-MM-DD').
Potential problem, boundary at the end of the year when time <> midnight.
Oracle's date type holds both date and time. A test like someDate between startDate and endDate will miss all records that happened after midnight on endDate. One simple fix that precludes use of indexes on someDate is trunc(someDate) between startDate and endDate.
A more general approach is to define date ranges and closed open intervals. lowerBound <= aDate < upperBound where lowerBound is the same asstartDateabove andupperBoundisendDate` plus one day.
Note: Some applications used Oracle date columns as dates and always store midnight, if your application is of that sort, then this is not a problem. And check constraints like check (trunc(dateColumn) = dateColumn) would make sure it stays that way.
And now, to answer the question actually asked.
Using subquery factoring (Oracle's terminology) / common table expression (SQL Server's terminology) one can avoid repetition within a query.
Instead of figuring out the proper year, and then using strings to put together dates, the code below starts by getting January 1 at Midnight of the current calendar year, trunc(sysdate, 'YEAR')). Then it adds an offset in months. When the months are Jan, Feb, Mar, the current fiscal year started last year on 4/1, or nine months before the start of this year. The offset is -9. Else the current fiscal year started 4/1 of this calendar year, start of this year plus three months.
Instead of end date, an upper bound is calculated, similar to lower bound, but with the offsets being 12 greater than lower bound to get 4/1 the following year.
with current_fiscal_year as (select add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then -9 else 3 end) as LowerBound
, add_months(trunc(sysdate, 'YEAR')
, case when extract(month from sysdate) <= 3 then 3 else 15 end) as UpperBound
from dual)
select *
from busplan
cross join current_fiscal_year CFY
where (CFY.LowerBound <= busplan.start_date and busplan.start_date < CFY.UpperBound)
or (CFY.LowerBound <= busplan.end_date and busplan.end_date < CFY.UpperBound)
And yet more unsolicited advise.
The times I've had to deal with fiscal year stuff, avoiding repetition within a query was low hanging fruit. Having the fiscal year calculations consistent and correct among many queries, that was the essence of the work. So I'd recommend a developing PL/SQL package that centralizes fiscal calculations. It might include a function like:
create or replace function GetFiscalYearStart(v_Date in date default sysdate)
return date
as begin
return add_months(trunc(v_Date, 'YEAR')
, case when extract(month from v_Date) <= 3 then -9 else 3 end);
end GetFiscalYearStart;
Then the query above becomes:
select *
from busplan
where (GetFiscalYearStart() <= busplan.start_date
and busplan.start_date < add_months(GetFiscalYearStart(), 12))
or (GetFiscalYearStart() <= busplan.end_date
and busplan.end_date < add_months(GetFiscalYearStart(), 12))

select record for recent n years

How to select the records for the 3 recent years, with JUST considering the year instead of the whole date dd-mm-yy.
let's say this year is 2013, the query should display all records from 2013,2012 and 2011.
if next year is 2014, the records shall be in 2014, 2013, 2012
and so on....
table foo
ID DATE
----- ---------
a0001 20-MAY-10
a0002 20-MAY-10
a0003 11-MAY-11
a0004 11-MAY-11
a0005 12-MAY-11
a0006 12-JUN-12
a0007 12-JUN-12
a0008 12-JUN-12
a0009 12-JUN-13
a0010 12-JUN-13
I think the clearest way is to use the year() function:
select *
from t
where year("date") >= 2011;
However, the use of a function in the where clause often prevents an index from being used. So, if you have a large amount of data and an index on the date column, then something that uses the current date is better. The following takes this approach:
select *
from t
where "date" >= trunc(add_months(sysdate, -3*12), 'yyyy')
SELECT ID, DATE FROM FOO
WHERE DATE >= TRUNC(ADD_MONTHS(SYSDATE, -3*12), 'YYYY');
Here we are getting last 36 months data by using ADD_MONTHS. We are adding 12*3 i.e. last 3 years data froom SYSDATE
That is pretty easy so I suspect that your question is not clear.
SELECT *
FROM foo
WHERE [date] >= '2011-01-01 00:00:00'
Does that work for you?
UPDATE
If you wish to do this dynamically you can use variables.
Below is how you would do that in SQL Server (I do not know the Oracle syntax for this)
DECLARE #startYear int
SET #startYear = YEAR(GETDATE()) - 2
SELECT *
FROM foo
WHERE [date] >= #startYear + '-01-01 00:00:00'