How to get last month's date from specific date in postgresql - sql

I have a max date in my query and If the max date is current month then I want always get previous month’s date. How can I do that?
For example, today is 20160825. If the max date is 20160801 then I want to get 20160701. But, if the max date is 20160501 then I just want to get without changes - 20160501.
SELECT
DEFN_DK,
MAX(SNAPSHOT_MTH)
FROM myTable
WHERE TOT_AMT >0
GROUP BY DEFN_DK

Since your SNAPSHOT_MTH column is an integer (why?) you can not use any of the otherwise very useful timestamp functions. So it's back to integer math, creating a "month" from your snapshot "date" through integer division by 100. This can be compared to CURRENT_DATE by converting that to a string and then casting it to an integer. Going back 1 month similarly requires some math. Not entirely efficient, but here goes:
SELECT DEFN_DK,
CASE max(SNAPSHOT_MTH) / 100
WHEN tochar(CURRENT_DATE, 'YYYYMM')::int THEN
((max(SNAPSHOT_MTH) / 100) - 1) * 100 + 1
-- or max(SNAPSHOT_MTH) - 100, if you know it always ends in 01
ELSE max(SNAPSHOT_MTH)
END AS SNAPSHOT_MTH
FROM myTable
WHERE TOT_AMT > 0
GROUP BY DEFN_DK;

select
defn_dk,
case max(snapshot_mth)
when date_trunc('month', current_date) then max(snapshot_mth) - interval '1 month'
else max(snapshot_mth)
end
from mytable
where tot_amt >0
group by defn_dk

show this, two examples:
select
case when date_trunc('month',dd) = date_trunc('month',now())
then dd - interval'1 month' else date_trunc('day',dd) end
from
(
select '2016-08-04'::date as dd
) d;
and
select
case when date_trunc('month',dd) = date_trunc('month',now())
then dd - interval'1 month' else date_trunc('day',dd) end
from
(
select '2016-05-04'::date as dd
) d;

Related

Group by date intervall Oracle

I want to retrieve sum of weight data from a table over a whole month.
what I need help with is that I want to group the result into 2 parts
sum of 1-15 of the month
and second line 16-31 of the month.
SELECT(SUM(B.SCALE_WEIGHT) FROM TRACKING.DATALOG_TAB B WHERE B.MATERIALID= 1 AND B.SCALE_EVENTDATE BETWEEN TO_DATE(TRUNC(TO_DATE('2020-10-1', 'YYYY-MM-DD'),'MONTH')) AND TO_DATE(TRUNC(TO_DATE('2020-10-1', 'YYYY-MM-DD'), 'MONTH')+30)
GROUP BY(somthing like this - 1-15 and 16-31)
Here is one option:
select
1 + floor(extract(day from scale_eventdate) / 16) as fortnight,
sum(b.scale_weight) as sum_scale_weight
from tracking.datalog_tab b
where
materialid = 1
and scale_eventdate >= date '2020-10-01'
and scale_eventdate < date '2020-11-01'
group by 1 + floor(extract(day from scale_eventdate) / 16)
This extracts the day number from the date, and then use artithmetics: every day from the 1 to to the 15th of the month included goes to fortnight number 1, and everything afterwards goes to bucket 2.
We could also do this with to_char() and a case expression, which is somewhat more expressive:
select
case when to_char(scale_eventdate, 'dd') <= '15' then 1 else 2 end as fortnight,
sum(b.scale_weight) as sum_scale_weight
from tracking.datalog_tab b
where
materialid = 1
and scale_eventdate >= date '2020-10-01'
and scale_eventdate < date '2020-11-01'
group by case when to_char(scale_eventdate, 'dd') <= '15' then 1 else 2 end
Note that I changed the date filtering logic to use standard date literals, which makes the query shorter and more readable.

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

Search for holiday doy IN (string) (Postgresql)

This work:
WITH month AS (
SELECT date_part('doy',d.dt) as doy,
dt::date as date
FROM generate_series('2017-01-01','2017-01-15', interval '1 day') as d(dt)
)
SELECT date,
CASE
WHEN doy IN (1,2,3) THEN 0 ELSE 8 END
FROM month
http://sqlfiddle.com/#!15/aed15/10
But if I store 1,2,3 as a string
CREATE TABLE holidays
(id int4,days character(60));
INSERT INTO holidays
(id,days)
VALUES
('2017','1,2,3');
...and replace 1,2,3 with this string:
WITH month AS (
SELECT date_part('doy',d.dt) as doy,
dt::date as date
FROM generate_series('2017-01-01','2017-01-15', interval '1 day') as d(dt)
)
SELECT date, days,
CASE
WHEN doy::text IN (days) THEN 0 ELSE 8 END
FROM month
LEFT JOIN holidays ON id=2017
http://sqlfiddle.com/#!15/aed15/13
It seems that 'days' is not casted correct. But I cannot figure out how.
TIA,
the shortest solution here would be turning string list to array and using ANY construct:
WITH month AS (
SELECT date_part('doy',d.dt) as doy,
dt::date as date
FROM generate_series('2017-01-01','2017-01-15', interval '1 day') as d(dt)
)
SELECT date, days,
CASE
WHEN doy::text = ANY(concat('{',days,'}')::text[]) THEN 0 ELSE 8 END
FROM month
LEFT JOIN holidays ON id=2017
But I would rethink the whole solution, as it feels wrong

Date diff in Teradata

I'm trying to do date difference between two dim_ck fields. If it's < 30, then I need to set a flag Y, else N.
Steps followed:
As it's a dim_ck, I converted it to Date from dim_ck.
There is one value with -3, so it didn't allow me to convert, so I tried to set a default date to -3 and then was able to convert it to Date. The below query returns Y and N result in Terdata DB, but it's not taking the date difference correctly.
For ex: 1998/07/31 - 2015/10/01 = Y, it's actually > 30, so it should return N. Please help.
Query:
SELECT date_dim_ck,
end_date_dim_ck,
CASE
WHEN( CASE
WHEN date_dim_ck = -3 THEN (To_date('01/01/1753', 'mm/dd/yyyy'))
ELSE Cast(( date_dim_ck - 19000000 ) AS DATE)
END - ( Cast (( end_date_dim_ck - 19000000 ) AS DATE) ) ) < 30
THEN 'Y'
ELSE 'N'
END AS FLAG
FROM table
#1: You didn't convert SQL Server's DATEDIFF (day, date_dim_ck, end_date_dim_ck) correctly.
SELECT DATE '1998-07-31' - DATE '2015-10-01' returns -6217 which is way less than 30 :-)
#2: There's no need for applying TO_DATE plus a format, Teradata supports Standard SQL date literals, DATE 'YYYY-MM-DD'.
CASE
WHEN Cast (( end_date_dim_ck - 19000000 ) AS DATE) -
CASE
WHEN date_dim_ck = -3
THEN DATE '1753-01-01'
ELSE Cast(( date_dim_ck - 19000000 ) AS DATE)
END < 30
THEN 'Y'
ELSE 'N'
END AS FLAG
Assuming that 1753-01-01 actually doesn't exist within the data:
CASE
WHEN Cast ((end_date_dim_ck - 19000000 ) AS DATE)
- Cast((NullIf(date_dim_ck,-3) - 19000000 ) AS DATE) < 30
THEN 'Y'
ELSE 'N'
END AS FLAG
Btw, the lowest date Teradat supports is DATE '0001-01-01'.

Weekly total sums

I have a table in a PostgreSQL database containing dates and a total count per day.
mydate total
2012-05-12 12
2012-05-14 8
2012-05-13 4
2012-05-12 12
2012-05-15 2
2012-05-17 1
2012-05-18 1
2012-05-21 1
2012-05-25 1
Now I need to get the weekly totals for a given date range.
Ex. I want to get the weekly totals from 2012-05-01 up to 2012-05-31.
I'm looking at this output:
2012-05-01 2012-05-07 0
2012-05-08 2012-05-14 36
2012-05-15 2012-05-22 5
2012-05-23 2012-05-29 1
2012-05-30 2012-05-31 0
This works for any given date range:
CREATE FUNCTION f_tbl_weekly_sumtotals(_range_start date, _range_end date)
RETURNS TABLE (week_start date, week_end date, sum_total bigint)
LANGUAGE sql AS
$func$
SELECT w.week_start, w.week_end, COALESCE(sum(t.total), 0)
FROM (
SELECT week_start::date, LEAST(week_start::date + 6, _range_end) AS week_end
FROM generate_series(_range_start::timestamp
, _range_end::timestamp
, interval '1 week') week_start
) w
LEFT JOIN tbl t ON t.mydate BETWEEN w.week_start and w.week_end
GROUP BY w.week_start, w.week_end
ORDER BY w.week_start
$func$;
Call:
SELECT * FROM f_tbl_weekly_sumtotals('2012-05-01', '2012-05-31');
Major points
I wrapped it in a function for convenience, so the date range has to be provided once only.
The subquery w produces the series of weeks starting from the first day of the given date range. The upper bound is capped with LEAST to stay within the upper bound of the given date range.
Then LEFT JOIN to the data table (tbl in my example) to keep all weeks in the result, even where no data rows are found.
The rest should be obvious. COALESCE to output 0 instead of NULL for empty weeks.
Data types have to match, I assumed mydate date and total int for lack of information. (The sum() of an int is bigint.)
Explanation for my particular use of generate_series():
Generating time series between two dates in PostgreSQL
Using this function
CREATE OR REPLACE FUNCTION last_day(date)
RETURNS date AS
$$
SELECT (date_trunc('MONTH', $1) + INTERVAL '1 MONTH - 1 day')::date;
$$ LANGUAGE 'sql' IMMUTABLE STRICT;
AND generate_series (from 8.4 onwards) we can create the date partitions.
SELECT wk.wk_start,
CAST(
CASE (extract(month from wk.wk_start) = extract(month from wk.wk_start + interval '6 days'))
WHEN true THEN wk.wk_start + interval '6 days'
ELSE last_day(wk.wk_start)
END
AS date) AS wk_end
FROM
(SELECT CAST(generate_series('2012-05-01'::date,'2012-05-31'::date,interval '1 week') AS date) AS wk_start) AS wk;
Then putting it together with the data
CREATE TABLE my_tab(mydate date,total integer);
INSERT INTO my_tab
values
('2012-05-12'::date,12),
('2012-05-14'::date,8),
('2012-05-13'::date,4),
('2012-05-12'::date,12),
('2012-05-15'::date,2),
('2012-05-17'::date,1),
('2012-05-18'::date,1),
('2012-05-21'::date,1),
('2012-05-25'::date,1);
WITH month_by_week AS
(SELECT wk.wk_start,
CAST(
CASE (extract(month from wk.wk_start) = extract(month from wk.wk_start + interval '6 days'))
WHEN true THEN wk.wk_start + interval '6 days'
ELSE last_day(wk.wk_start)
END
AS date) AS wk_end
FROM
(SELECT CAST(generate_series('2012-05-01'::date,'2012-05-31'::date,interval '1 week') AS date) AS wk_start) AS wk
)
SELECT month_by_week.wk_start,
month_by_week.wk_end,
SUM(COALESCE(mt.total,0))
FROM month_by_week
LEFT JOIN my_tab mt ON mt.mydate BETWEEN month_by_week.wk_start AND month_by_week.wk_end
GROUP BY month_by_week.wk_start,
month_by_week.wk_end
ORDER BY month_by_week.wk_start;