CASE WHEN function & date function to change now() > to an assumption date - sql

WITH latest AS (
SELECT
DISTINCT customer_id,
MAX(submitted_on) AS latest_order
FROM orders
GROUP BY 1
),
AA AS (
SELECT
DISTINCT o.customer_id,
latest.latest_order,
now() - INTERVAL '91 days' AS reference_more_than_90D,
now() - INTERVAL '31 days' AS reference_more_than_31D,
now() - INTERVAL '30 days' AS reference_more_than_30D
FROM orders AS o
LEFT JOIN latest
ON o.customer_id = latest.customer_id
GROUP BY 1,2,3,4,5
)
SELECT
DISTINCT o.customer_id,
latest.latest_order,
AA.reference_more_than_30D,
AA.reference_more_than_31D,
AA.reference_more_than_90D,
CASE
WHEN latest.latest_order >= AA.reference_more_than_31D THEN 'r'
WHEN latest.latest_order <= AA.reference_more_than_30D THEN 'a'
ELSE 'l'
END AS status
FROM orders AS o
LEFT JOIN latest
ON o.customer_id = latest.customer_id
LEFT JOIN AA
ON o.customer_id = AA.customer_id
With above this is the output
Seems that my CASE WHEN function isn't working right as 2021-04-29 is an older date than 2022-06-17/2022-06-16 and 2022-04-17 > The status should reflect 'l'
how should i change now() > to an assumption date like 2021-07-01 in this case ? Taking into consideration i only have 2021 & 2020 orders to look at
Thanks

Seems that my CASE WHEN function isn't working right as 2021-04-29 is an older date than 2022-06-17/2022-06-16 and 2022-04-17 > The status should reflect 'l'.
I think you don't need to use those left joins. Please refer to the below script.
WITH latest AS (
SELECT
DISTINCT customer_id,
MAX(submitted_on) AS latest_order
FROM orders
GROUP BY 1
),
AA AS (
SELECT
DISTINCT customer_id,
latest_order,
now() - INTERVAL '91 days' AS reference_more_than_90D,
now() - INTERVAL '31 days' AS reference_more_than_31D,
now() - INTERVAL '30 days' AS reference_more_than_30D
FROM latest
GROUP BY 1,2,3,4,5
)
SELECT
DISTINCT customer_id,
latest_order,
reference_more_than_30D,
reference_more_than_31D,
reference_more_than_90D,
CASE
WHEN latest_order >= reference_more_than_31D THEN 'r'
WHEN latest_order <= reference_more_than_30D THEN 'a'
ELSE 'l' END
AS status
FROM AA
How should I change now() > to an assumption date like 2021-07-01 in this case ? Taking into consideration, I only have 2021 & 2020 orders to look at.
\set variable
hank=> \set name hank
hank=> \set time '2018-02-06 10:09:00'
hank=> select * from tb2 where c2=:'name' and c3>=:'time';
c1 | c2 | c3
----+------+----------------------------
1 | hank | 2018-02-06 10:08:00.78750
How can I set now() to '2021-07-01' ? . I need to make an assumption of the current date to be '2021-07-01'.
CREATE OR REPLACE FUNCTION get_data (v_Date TIMESTAMP WITHOUT TIME ZONE)
RETURNS TABLE (
latest_order TIMESTAMP WITHOUT TIME ZONE,
reference_more_than_30D TIMESTAMP WITHOUT TIME ZONE,
reference_more_than_31D TIMESTAMP WITHOUT TIME ZONE,
reference_more_than_90D TIMESTAMP WITHOUT TIME ZONE
)
AS $$
BEGIN
RETURN QUERY SELECT v_Date,
v_Date - INTERVAL '30 days',
v_Date - INTERVAL '31 days',
v_Date - INTERVAL '91 days';
END;
$$ LANGUAGE plpgsql;
select * from get_data('2021-07-01');

I would suggest avoiding the use of now() as this includes millisecond precision. Instead you probably only need date precision so you could use current_date instead. Plus you really don't need select distinct at all, nor do you need multiple joins etc.
WITH AA
AS (
SELECT customer_id
, CURRENT_DATE - INTERVAL '91 days' AS reference_more_than_90D
, CURRENT_DATE - INTERVAL '31 days' AS reference_more_than_31D
, CURRENT_DATE - INTERVAL '30 days' AS reference_more_than_30D
, MAX(submitted_on) AS latest_order
FROM orders
GROUP BY customer_id
)
SELECT AA.customer_id
, AA.latest_order
, AA.reference_more_than_30D
, AA.reference_more_than_31D
, AA.reference_more_than_90D
, CASE
WHEN AA.latest_order >= AA.reference_more_than_31D
THEN 'r'
WHEN AA.latest_order <= AA.reference_more_than_30D
THEN 'a'
ELSE 'l'
END AS STATUS
FROM AA
If you ned to use a preset date instead of current_date then just use a date literal instead e.g.
SELECT
customer_id
, '2021-07-01'::timestamp - INTERVAL '91 days' AS reference_more_than_90D
, '2021-07-01'::timestamp - INTERVAL '31 days' AS reference_more_than_31D
, '2021-07-01'::timestamp - INTERVAL '30 days' AS reference_more_than_30D
, MAX(submitted_on) AS latest_order
FROM orders
GROUP BY customer_id
)
SELECT AA.customer_id
, AA.latest_order
, AA.reference_more_than_30D
, AA.reference_more_than_31D
, AA.reference_more_than_90D
, CASE
WHEN AA.latest_order >= AA.reference_more_than_31D
THEN 'r'
WHEN AA.latest_order <= AA.reference_more_than_30D
THEN 'a'
ELSE 'l'
END AS STATUS
FROM AA
see this db<>fiddle here

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!

get List of counts from table based on dates in sql

I have to fetch List of counts from table by department here is my table structure
empid empname department departmentId joinedon
i want to populate all the joined employee on today , yesterday and More than 2 days like [12,25,89] i.e
12* joined today
25 joined yesterday
81 joined all prior to yesterday(2+day)
* 0 if there isn't any entries for given date range.
You would use aggregation on a case expression:
select (case when joinedon::date = current_date then 'today'
when joinedon::date = current_date - interval '1 day' then 'yesterday'
when joinedon::date < current_date - interval '1 day' then 'older'
end) as grp,
count(*)
from t
group by grp;
In additional to #Gordon Linoff answer:
SELECT
days.day,
coalesce(t.cnt, 0) count
FROM (
SELECT * FROM (VALUES ('today'), ('yesterday'), ('older')) AS days (day)
)days
LEFT JOIN (
SELECT (CASE WHEN joinedon::date = current_date THEN 'today'
WHEN joinedon::date = current_date - interval '1 day' THEN 'yesterday'
WHEN joinedon::date < current_date - interval '1 day' THEN 'older'
end) as day,
count(*) cnt
FROM t
GROUP BY day
) t on t.day = days.day;
Test it here
You can use the group by as follows:
select department,
(case when joinedon::date = current_date then 'today'
when joinedon::date = current_date - interval '1 day' then 'yesterday'
when joinedon::date < current_date - interval '1 day' then 'More than 2 days'
end) as grp,
Coalesce(count(*),0)
from t
group by grp, department;

Oracle SQL - How to retrieve the ID Count difference between today vs yesterday

I have a table that captures when a customer purchases a product. It captures a unique purchase id along with a timestamp of when the purchase was made.
I want to be able to query, the difference between how many purchases were taken today vs yesterday?
Not sure how to query this on oracle?
You can use conditional aggregation:
select sum(case when trunc(datecol) = trunc(sysdate - 1) then 1 else 0 end) as num_yesterday,
sum(case when trunc(datecol) = trunc(sysdate) then 1 else 0 end) as num_today,
sum(case when trunc(datecol) = trunc(sysdate) then 1
when trunc(datecol) = trunc(sysdate - 1) then -1
end) as diff
from t
where datecol >= trunc(sysdate - 1);
you can use the Group function to grouping the purchase day with timestamp information and count the purchase id.
select trunc(purchase_ts) Day, count(purchase_id) Count
from purchase
group by trunc(purchase_ts)
order by 1
Using TRUNC on the column will prevent Oracle from using an index on that column (instead you would need a separate function-based index); instead use a CASE statement to test whether the date is between the start of the day and the start of the next day and then COUNT the values between those ranges:
SELECT COUNT(
CASE
WHEN TRUNC( SYSDATE ) - INTERVAL '1' DAY <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE )
THEN 1
END
) AS count_for_yesterday,
COUNT(
CASE
WHEN TRUNC( SYSDATE ) <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE ) + INTERVAL '1' DAY
THEN 1
END
) AS count_for_today
FROM your_table
WHERE TRUNC( SYSDATE ) - INTERVAL '1' DAY <= your_date_column
AND your_date_coumn < TRUNC( SYSDATE ) + INTERVAL '1' DAY

Query to get sales from this month and previous month

We'd like to get total sales for this month and previous month. The query is:
SELECT sum(CASE
WHEN date_trunc('month', date_start)= date_trunc('month', now()) THEN sales
ELSE NULL
END) AS curr_sales,
sum(CASE
WHEN date_trunc('month', date_start)= date_trunc('month', now()- interval '1' MONTH) THEN sales
ELSE NULL
END) AS pr_sales
FROM sales
But it returned this error:
"Specified types or functions (one per INFO message) not supported on Redshift tables. We're running Postgresql 8.0.2. Any ideas? Thanks!
Use date_trunc('month', current_date) instead of date_trunc('month', now()) to get current month in redshift.
now() is not a supported function in redshift but current_date will return a date in the current session time zone (UTC by default) in the default format: YYYY-MM-DD.
UPDATE
Query w/ Sample Data >>
with sales(sales, date_start) as(
select 1 , current_date union
select 2 , current_date union
select 2 , current_date - interval '1' month union
select 3 , current_date - interval '1' month
)
SELECT sum(CASE
WHEN date_trunc('month', date_start)= date_trunc('month', current_date) THEN sales
ELSE NULL
END) AS curr_sales,
sum(CASE
WHEN date_trunc('month', date_start)= date_trunc('month', current_date - interval '1' MONTH) THEN sales
ELSE NULL
END) AS pr_sales
FROM sales;
And results are coming as expected:
curr_sales pr_sales
3 5

Select count while count is greater than a specific number in postgres sql

I want to get last month records from table. I have tried:
SELECT count(*) as numberOfRows from Table where created_at > CURRENT_DATE - INTERVAL '1 months'
It's Ok, but I want to add some conditions:
If numberOfRows >= 10, do nothing (numberOfRows can be 20, 30, ...)
else if numberOfRows < 10, select from this table until numberOfRows
= 10 (last 2 months, 3 months, etc...).
How can I do that?
Thanks in advances!
WITH curr_month_cnt AS (
SELECT COUNT(*) AS cnt
FROM your_table
WHERE created_at > CURRENT_DATE - INTERVAL '1 months'
)
SELECT *
FROM your_table
WHERE created_at > CURRENT_DATE - INTERVAL '1 months'
UNION ALL
SELECT t.*
FROM
(
SELECT *
FROM your_table
WHERE
created_at <= CURRENT_DATE - INTERVAL '1 months' AND
(SELECT cnt FROM curr_month_cnt) < 10
ORDER BY created_at desc
LIMIT
GREATEST(0, 10 - (SELECT cnt FROM curr_month_cnt))
) t
This will return a maximum of 10 records, starting with the most recent month and going backwards. In the event that the latest month have not 10 records, then two and three months old data would be returned, in that order.
Based on your description, you would seem to want:
select greatest(10, count(*)) as numberOfRows
from Table
where created_at > CURRENT_DATE - INTERVAL '1 months';
This seems rather surprising. Perhaps you want:
select (case when sum( (CURRENT_DATE - INTERVAL '1 months' ) :: int) >= 10
then sum( (CURRENT_DATE - INTERVAL '1 months' ) :: int)
else least(10, count(*))
end) as numberOfRows
from Table
where created_at > CURRENT_DATE - INTERVAL '1 months';