Converting Recursive Query from Week over Week to Month over Month - sql

I have a recursive query that provides the number of orders placed week over week (week_no, week_start, and week_end). I'd like to create a similar breakdown for a month over month analysis.
WITH recursive weeks (week_start, week_end, time_end, weekno) AS (
VALUES ('2015-12-27'::date, '2016-01-02'::date, '2016-04-02'::date, 1)
UNION ALL
SELECT (week_end + interval '1 day')::date,
(CASE
WHEN (week_end + interval '7 days')::date > time_end THEN time_end
ELSE (week_end + interval '7 days')::date
END)::date,
time_end,
weekno+1
FROM weeks
WHERE time_end > week_end)
Any help would be greatly appreciated.

Why would you use a recursive query for this? Use generate_series():
select g.week_start, g.week_start + interval '6 day' as week_end,
row_number() over (order by g.week_start) as weeknum
from generate_series('2015-12-27'::timestamp,
'2016-01-02'::timestamp,
interval '1 week'
) g(week_start);
The equivalent for months would be like:
select g.month_start, g.month_start + interval '1 month' - interval '1 day' as month_end,
row_number() over (order by g.month_start) as monthnum
from generate_series('2015-12-01'::timestamp,
'2016-01-01'::timestamp,
interval '1 month'
) g(month_start);

Related

Get columns of data with two different date range

I would like to get the average rating for last 7 days and last 14 days.
I tried using WITH AS to get the data but it's taking way too long to load. Any other way that is better and could reduce the run time?
syntax:
WITH last_7_days AS (
SELECT item, rating
FROM sales
WHERE (
rating IS NOT NULL
AND (entry_date >= CAST((CAST(now() AS timestamp) + (INTERVAL '-7 day')) AS date) AND entry_date < CAST((CAST(now() AS timestamp) + (INTERVAL '1 day')) AS date))
)
),
last_14_days AS (
SELECT item, rating
FROM sales
WHERE (
rating IS NOT NULL
AND (entry_date >= CAST((CAST(now() AS timestamp) + (INTERVAL '-14 day')) AS date) AND entry_date < CAST((CAST(now() AS timestamp) + (INTERVAL '1 day')) AS date))
)
)
SELECT last_7_days.item, avg(last_7_days.score) as "avg_last_7_days", avg(last_14_days.rating) as "avg_last_14_days", count(*) AS "count"
FROM last_7_days, last_14_days
WHERE last_7_days.item = last_14_days.item
GROUP BY last_7_days.item
ORDER BY "avg_last_7_days" DESC, last_7_days.item ASC
Result should be something like this:
item|avg_last_7_days|avg_last_14_days|count|
thank you
Use conditional aggregation:
SELECT item,
AVG(rating) FILTER (WHERE entry_date >= NOW() + interval '-7 day' AND entry_date < NOW() + interval '1 day') AS avg_rating_last_seven_days,
AVG(rating) FILTER (WHERE entry_date >= NOW() + interval '-14 day' AND entry_date < NOW() + interval '1 day') AS avg_rating_last_fourteen_days
FROM sales
WHERE rating IS NOT NULL AND
(entry_date >= NOW() + interval '-14 day' AND entry_date < NOW() + interval '1 day')
GROUP BY item;
Note: If you only care about the date, then perhaps you should use CURRENT_DATE or even NOW()::date.
Getting rid of all the casts and aggregating directly on the CTEs should help, try with the following:
WITH last_7_days AS (
SELECT
item,
AVG(rating) AS avg_rating_last_seven_days
FROM
sales
WHERE
rating IS NOT NULL AND
(entry_date >= NOW() + interval '-7 day' AND entry_date < NOW() + interval '1 day')
GROUP BY
1
),
last_14_days AS (
SELECT
item,
AVG(rating) AS avg_rating_last_fourteen_days
FROM
sales
WHERE
rating IS NOT NULL AND
(entry_date >= NOW() + interval '-14 day' AND entry_date < NOW() + interval '1 day')
GROUP BY
1
)
SELECT
lsd.item,
avg_rating_last_seven_days,
avg_rating_last_fourteen_days
FROM
last_7_days AS lsd
INNER JOIN
last_14_days AS lfd ON lsd.item = lfd.item
Let me know in case it helped on improving your current performance!

SQL Query to get records from last week of every month for 4 years

I have below table
ABC Date
200 2019-02-22
-200 2019-02-23
1200 2019-02-24
-500 2019-02-25
'
'
'
'
-889 2015-01-11
I need to get values for from ABC for every day of last week of every month
select ABC
from table 1
where date between '2019-03-26' and '2019-03-30'
this is for month of march 2019. How do i create a loop such that it displays value for everyday of last week of every month for 3 years
You can use date arithmetic to get the last week of each month. In Terdata, I think this is one solution:
select abc
from table1
where (date >= (current_date - extract(day from date) * interval '1 day') - interval '6 day' and
date <= current_date - extract(day from date) * interval '1 day'
) or
(date >= (current_date - extract(day from date) * interval '1 day') - interval '1 month' - interval '6 day' and
date <= current_date - extract(day from date) * interval '1 day' - interval '1 month'
) or
(date >= (current_date - extract(day from date) * interval '1 day') - interval '2 month' - interval '6 day' and
date <= current_date - extract(day from date) * interval '1 day' - interval '12month'
);
SELECT ABC, DATE FROM table_1 WHERE DATEPART(wk, DATE) =
DATEPART(wk, EOMONTH(DATE)) AND DATE <= DATEADD(year,3,GETDATE())
DATEPART(wk, DATE) gives me the week number of that date, DATEPART(wk,EOMONTH(DATE)) gives me the week number of (the last day of the corresponding date's month). So, when I check this, I will only select dates belonging to the last week of every month. The next filter is to select only those dates which are lesser than 3 years from now (GETDATE()).

How to find the last 'working' day of last month?

Is there a way to find the last working day of last month? I know I can get last day of last month with SELECT (date_trunc('month', now())::date - 1), but how do I get the last "weekday"?
I believe this does what you want:
select (date_trunc('month', current_date) + interval '1 month' -
(case extract(dow from date_trunc('month', current_date) + interval '1 month')
when 0 then 2
when 1 then 3
else 1
end) * interval '1 day'
) as last_weekday_in_month
Such requests often suggest the need for a calendar table.
Piggy-backing off of Gordon's answer, I think this is what you want:
SELECT (
date_trunc('month', current_date) - interval '1 day' -
(case extract(dow from date_trunc('month', current_date) - interval '1 day')
when 0 then 2
when 6 then 1
else 0
end) * interval '1 day'
)::date as last_weekday_in_last_month;
Assuming your weekends are 0 (Sunday) and 6 (Saturday). It uses OP's original logic to find the last date of the last month, then Gordon's CASE logic to subtract more days if the last date is 0 or 6.

Vertica - WITH clause is not recognized in actual query

Please take a look at the following Vertica SQLcode:
WITH date_range AS
(SELECT YEAR(now() - interval '1' MONTH) ||MONTH(now() - interval '1' MONTH) ||'#'||DATE(TRUNC(now() - interval '1' MONTH, 'mm')) ||'#'||DATE(TRUNC(now(), 'mm') - interval '1' DAY) AS month1
, YEAR(now() - interval '2' MONTH) ||MONTH(now() - interval '2' MONTH) ||'#'||DATE(TRUNC(now() - interval '2' MONTH, 'mm')) ||'#'||DATE(TRUNC(now() - interval '1' MONTH, 'mm') - interval '1' DAY) AS month2
)
SELECT regexp_substr(
(SELECT month1
FROM date_range), '[^#]*', 1, 1)
I have a 420 rows-long query, and I need to use "month1" and "month2" as variables many time in my code. Unfortunately, Vertica still doesn't support variables, so I tried to use a WITH clause instead.
Unfortunately, it doesn't work, as I keep getting the following error message:
(4566) ERROR: Relation "date_range" does not exist
So help me God (or Stack Overflow)
I think this is the query you want:
WITH date_range AS (
SELECT YEAR(now() - interval '1' MONTH) ||MONTH(now() - interval '1' MONTH) ||'#'||DATE(TRUNC(now() - interval '1' MONTH, 'mm')) ||'#'||DATE(TRUNC(now(), 'mm') - interval '1' DAY) AS month1,
YEAR(now() - interval '2' MONTH) ||MONTH(now() - interval '2' MONTH) ||'#'||DATE(TRUNC(now() - interval '2' MONTH, 'mm')) ||'#'||DATE(TRUNC(now() - interval '1' MONTH, 'mm') - interval '1' DAY) AS month2
)
SELECT regexp_substr(month1, '[^#]*', 1, 1)
FROM date_range;
In an actual query, you would do this as:
SELECT regexp_substr(dr.month1, '[^#]*', 1, 1)
FROM date_range dr CROSS JOIN
. . .;
I often call such CTEs params to highlight that they are providing parameters to the query.

Generating time series for everyday's am9:00 to pm2:00

I want to generatie time series from am9:00 to pm2:00 everyday
My current time series generated from min(ticktime) to max(ticktime)
But I want to scope the series to everyday's am8:59 to pm3:00
how to do it ?
I tried to generate time between 1:11 ~ 1:15, But it didn't stop by 1:15
SELECT generate_series
(
date_trunc('second', min(ticktime)) ,
date_trunc('second', max(ticktime)) ,
interval '1 sec'
) AS ticktime FROM czces
WHERE
(date_part('hour', ticktime) >= 1 AND date_part('minute', ticktime) > 10 )
AND (date_part('hour', ticktime) <= 1 AND date_part('minute', ticktime) <= 15 )
I tried another solution but the outputed generating series still not under the BETWEEN interval.
Please check the result the series not stopped by 1:01 am
SELECT generate_series
(
min(ticktime)::timestamp,
max(ticktime)::timestamp,
'1 minute'::interval
) AS ticktime FROM czces
where ticktime::time between '00:01 am'::time AND '1:01 am'::time
~~~~
You need to add the appropriate where clause.
Examples of the syntax to use:
select dt
from generate_series(
now()::date - interval '1 day',
now()::date + interval '1 day',
'1 hour'
) as dt
where dt::time between '9:00 am'::time and '2:00 pm'::time;
select t
from generate_series(
now()::timestamptz(0) - interval '1 day',
now()::timestamptz(0) + interval '1 day',
'1 hour'
) as t
where t::time between '9:00 am'::time and '2:00 pm'::time;