Grouping by week starting on Tuesday in Postgresql - postgresql

I have a code here where it groups all data according to their week starting day, such as Sunday, Monday, and Tuesday. Code works on Monday and Sunday but on Tuesday, as you can see, Oct 12 is grouped on Tuesday the following week.
Code:
select case when day.week_starting = 'Sunday' then date_trunc('week', table1.date::date + 1)::date - 1
when day.week_starting = 'Tuesday' then date_trunc('week', table1.date::date + 2)::date + 1
when day.week_starting = 'Monday' then date_trunc('week', table1.date::date)::date
end as local_date_created, date

Update: I found the workaround. Here:
case when day.week_starting = 'Sunday' then date_trunc('week', table1.date::date + 1)::date - 1
when day.week_starting = 'Tuesday' then date_trunc('week', table1.date::date)::date + 1
when day.week_starting = 'Monday' then date_trunc('week', table1.date::date)::date
end as local_date_created,date

Related

I need help in writing a subquery

I have a query like this to create date series:
Select month
From
(select to_char(created_date, 'Mon') as Month,
created_date::date as start_day,
(created_date::date + interval '1 month - 1 day ')::date as end_day
from generate_series(date '2021-01-26',
date '2022-04-26', interval '1 month') as g(created_date)) AS "thang"
And the table looks like this:
month
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Now I want to count the status from the KYC table.
So I try this:
Select
(Select month
From
(select to_char(created_date, 'Mon') as Month,
created_date::date as start_day,
(created_date::date + interval '1 month - 1 day ')::date as end_day
from generate_series(date '2021-01-26',
date '2022-04-26', interval '1 month') as g(created_date)) AS "thang"),
count(*) filter (where status = 4) as "KYC_Success"
From kyc
group by 1
I hope the result will be like this:
Month | KYC_Success
Jan | 234
Feb | 435
Mar | 546
Apr | 157
But it said
error: more than one row returned by a subquery used as an expression
What should I change in this query?
Let us assume that the table KYC has a timestamp column called created_date and the status column, and, that you want to count the success status per month - even if there was zero success items in a month.
SELECT thang.month
, count(CASE WHEN kyc.STATUS = 'success' THEN 1 END) AS successes
FROM (
SELECT to_char(created_date, 'Mon') AS Month
, created_date::DATE AS start_date
, (created_date::DATE + interval '1 month - 1 day ')::DATE AS end_date
FROM generate_series(DATE '2021-01-26', DATE '2022-04-26', interval '1 month') AS g(created_date)
) AS "thang"
LEFT JOIN kyc ON kyc.created_date>= thang.start_date
AND kyc.created_date < thang.end_date
GROUP BY thang.month;

Postgresql: how to add date and avoid weekend?

i have following table proses
id title created_at(Timestamp) slaTime(Integer)
1 Proses One 2012-11-22 13:50:09.924 4
2 Proses Two 2012-11-22 13:50:09.924 6
I want to find the deadline with calculation deadline = created_at + slaTime(No. of days). but it also avoid a weekend.
for example on id 1, 2012-11-22 is monday so the deadline is 2012-11-26 (friday)
but on id 2, the deadline should be 2012-11-30 (tuesday) not 2012-11-28(sunday)
You can use EXTRACT(DOW FROM timestamp) in order to define the day of the week as Sunday(0) to Saturday(6), and then add some extra days depending on the resulting day of the week :
SELECT CASE
WHEN d.curr_date = 0
THEN created_at + INTERVAL '1 day'
WHEN d.curr_date = 6
THEN created_at + INTERVAL '2 days'
ELSE created_at
END
FROM process
CROSS JOIN LATERAL (SELECT EXTRACT(DOW FROM (created_at :: date + slaTime)) AS curr_date) AS d

Postgresql I want to exclude weekends from year, month, and day data based on today (1 year's worth)

SELECT to_char(date_trunc('day', (current_date - days)), 'YYYY-MM-DD')
AS date
FROM generate_series(0, 365, 1)
AS days
result
2021-11-12
2021-11-11
2021-11-10
2021-11-09
2021-11-08
2021-11-07 .....
2020-11-16
2020-11-15
2020-11-14
2020-11-13
2020-11-12 ..... end
How do I exclude weekend data from the year's worth of data?
SELECT to_char(running_day, 'YYYY-MM-DD') AS date
FROM (select current_date - generate_series(1, 365, 1)) AS t(running_day)
WHERE extract('isodow' from running_day) between 1 and 5
ORDER BY running_day;
Just add a WHERE clause:
SELECT to_char(day, 'YYYY-MM-DD') AS date
FROM generate_series(current_date - interval '1 year',
current_date,
interval '1 day') AS g(day)
where extract(isodow from g.day) not in (6,7)
I used the ISO definition of the day numbering with Monday = 1, Saturday = 6 and Sunday = 7.
You can use extract(dow ...) if you prefer Sunday = 0

Get week number, with weeks starting on Sunday, like Excel WEEKNUM

In PostgreSQL (I'm on version 9.6.6), what's the simplest way to get the week number, starting on Sunday?
DATE_PART('week',x) returns:
The number of the ISO 8601 week-numbering week of the year. By definition, ISO weeks start on Mondays and the first week of a year contains January 4 of that year. In other words, the first Thursday of a year is in week 1 of that year. (doc)
Say my query is like:
WITH dates as (SELECT generate_series(timestamp '2014-01-01',
timestamp '2014-01-31',
interval '1 day'
)::date AS date
)
SELECT
date,
TO_CHAR(date,'Day') AS dayname,
DATE_PART('week',date) AS weekofyear
FROM dates
Returns:
date dayname weekofyear
--------------------------------
2014-01-01 Wednesday 1
2014-01-02 Thursday 1
2014-01-03 Friday 1
2014-01-04 Saturday 1
2014-01-05 Sunday 1 <- I want this to be 2
2014-01-06 Monday 2
2014-01-07 Tuesday 2
2014-01-08 Wednesday 2
So far I have tried:
SELECT
date,
TO_CHAR(date,'Day') AS dayname,
DATE_PART('week',date) AS week_iso,
DATE_PART('week',date + interval '1 day') AS week_alt
FROM dates
which won't quite work if the year begins on a Sunday.
Also, I want week 1 to contain January 1 of that year. So if January 1 is a Saturday, I want week 1 to be one day long (instead of being week 53 in the ISO style). This behavior is consistent with the Excel WEEKNUM function.
To get the week number of the year, with weeks starting on Sunday, we need to know how many Sundays between the first day of the year and the target date.
I adapted the solution here by #Erwin Brandstetter. This solution counts Sundays inclusive of the first day of the year and exclusive of the target date.
Then, because I want the first (partial) week to be week one (not zero), I need to add 1 unless the first day of the year is a Sunday (in which case it's already week one).
WITH dates as (SELECT generate_series(timestamp '2014-01-01',
timestamp '2014-01-31',
interval '1 day'
)::date AS date
)
SELECT
date,
TO_CHAR(date,'Day') AS dayname,
DATE_PART('week',date) AS week_iso,
((date - DATE_TRUNC('year',date)::date) + DATE_PART('isodow', DATE_TRUNC('year',date)) )::int / 7
+ CASE WHEN DATE_PART('isodow', DATE_TRUNC('year',date)) = 7 THEN 0 ELSE 1 END
AS week_sundays
FROM dates
Returns
date dayname weekofyear week_sundays
--------------------------------
2014-01-01 Wednesday 1 1
2014-01-02 Thursday 1 1
2014-01-03 Friday 1 1
2014-01-04 Saturday 1 1
2014-01-05 Sunday 1 2
2014-01-06 Monday 2 2
2014-01-07 Tuesday 2 2
To show how this works for years starting on Sunday:
2017-01-01 Sunday 52 1
2017-01-02 Monday 1 1
2017-01-03 Tuesday 1 1
2017-01-04 Wednesday 1 1
2017-01-05 Thursday 1 1
2017-01-06 Friday 1 1
2017-01-07 Saturday 1 1
2017-01-08 Sunday 1 2
The task is not as daunting as it first appears. It mainly requires finding the first Sun on or after the 1-Jan. That date becomes the last day of the first week. From there calculation of subsequent weeks is merely. a matter of addition. The other significant point is with week definition there will always be 53 week per year and the last day of the last week is 31-Dec. The following generates an annual calendar for this week definition.
create or replace function non_standard_cal(year_in integer)
returns table (week_number integer, first_day_of_week date, last_day_of_week date)
language sql immutable leakproof strict rows 53
as $$
with recursive cal as
(select 1 wk, d1 start_of_week, ds end_of_week, de stop_date
from (select d1+substring( '0654321'
, extract(dow from d1)::integer+1
, 1)::integer ds
, d1, de
from ( select make_date (year_in, 1,1) d1
, make_date (year_in+1, 1,1) -1 de
) a
) b
union all
select wk+1, end_of_week+1, case when end_of_week+7 > stop_date
then stop_date
else end_of_week+7
end
, stop_date
from cal
where wk < 53
)
select wk, start_of_week, end_of_week from cal;
$$ ;
As a general rule I avoid magic numbers, but sometimes they're useful; as in this case. In magic number (actually a string) '0654321' each digit represents the number of days needed to reach the first Mon on or after 1-Jan when indexed by the standard day numbering system (0-6 as Sun-Sat). The result is the Mon being the last day of the first week. That generatess the 1st row of the recursive CTE. The remaining rows just add the appropriate number days for each week until the 53 weeks have been generated. The following shows the years needed to ensure each day of week gets it's turn to 1-Jan (yea some days duplicate). Run individual years to validate its calendar.
do $$
declare
cal record;
yr_cal cursor (yr integer) for
select * from non_standard_cal(2000+yr) limit 1;
begin
for yr in 18 .. 26
loop
open yr_cal(yr);
fetch yr_cal into cal;
raise notice 'For Year: %, week: %, first_day: %, Last_day: %, First day is: %'
, 2000+yr
,cal.week_number
,cal.first_day_of_week
,cal.last_day_of_week
,to_char(cal.first_day_of_week, 'Day');
close yr_cal;
end loop;
end; $$;
Following may work - tested with two cases in mind:
WITH dates as (SELECT generate_series(timestamp '2014-01-01',
timestamp '2014-01-10',
interval '1 day'
)::date AS date
union
SELECT generate_series(timestamp '2017-01-01',
timestamp '2017-01-10',
interval '1 day'
)::date AS date
)
, alt as (
SELECT
date,
TO_CHAR(date,'Day') AS dayname,
DATE_PART('week',date) AS week_iso,
DATE_PART('week',date + interval '1 day') AS week_alt
FROM dates
)
select date, dayname,
week_iso, week_alt, case when week_alt <> week_iso
then week_alt
else week_iso end as expected_week
from alt
order by date
Output:
date dayname week_iso week_alt expected_week
2014-01-01 Wednesday 1 1 1
2014-01-02 Thursday 1 1 1
2014-01-03 Friday 1 1 1
2014-01-04 Saturday 1 1 1
2014-01-05 Sunday 1 2 2
2014-01-06 Monday 2 2 2
2014-01-07 Tuesday 2 2 2
....
2017-01-01 Sunday 52 1 1
2017-01-02 Monday 1 1 1
2017-01-03 Tuesday 1 1 1
2017-01-04 Wednesday 1 1 1
2017-01-05 Thursday 1 1 1
2017-01-06 Friday 1 1 1
2017-01-07 Saturday 1 1 1
2017-01-08 Sunday 1 2 2
This query works perfectly replacing monday with sunday as the start of the week.
QUERY
SELECT CASE WHEN EXTRACT(day from '2014-01-05'::date)=4 AND
EXTRACT(month from '2014-01-05'::date)=1 THEN date_part('week',
'2014-01-05'::date) ELSE date_part('week', '2014-01-05'::date + 1)
END;
OUTPUT
date_part
-----------
2
(1 row)

PGSQL syntax to fetch last year's last two month records from current year Jan or Feb

can anyone tell me the logic of getting last year's last 2 months records from the next year Jan or feb ?. For eg: I want to compare the sales from Jan 2016 as current month and Dec 2015 as previous month and Nov 2015 as two months before. I tried like this, but not working if it is for Jan or feb
(case when extract(month from m.validfrom) = extract(month from current_date)-1 then 'Previous Month'
when extract(month from m.validfrom) = extract(month from current_date)-2 then 'Two Months Before'
when extract(month from m.validfrom) = extract(month from current_date) then 'Current Month' end ) as month,
date_trunc
case date_trunc('month', m.validfrom)
when date_trunc('month', current_date - interval '1 month') then 'Previous Month'
when date_trunc('month', current_date - interval '2 month') then 'Two Months Before'
when date_trunc('month', current_date) then 'Current Month'
end as month