Count From Time Range In Postgresql - postgresql

I have a query that count the total number of users registered in our service per day, per hour. My problem is that i want to appear the hour in range style. You can see below:
Query:
SELECT
case
when extract(dow from us.created::timestamp) = 0 then 'Sunday'
when extract(dow from us.created::timestamp) = 1 then 'Monday'
when extract(dow from us.created::timestamp) = 2 then 'Tuesday'
when extract(dow from us.created::timestamp) = 3 then 'Wednesday'
when extract(dow from us.created::timestamp) = 4 then 'Thursday'
when extract(dow from us.created::timestamp) = 5 then 'Friday'
when extract(dow from us.created::timestamp) = 6 then 'Saturday'
end as wday,
extract(hour from us.created::timestamp) as whour,
count(us.id)
FROM users us
GROUP BY wday,whour order by wday, whour
Query Result:
wday whour count
Friday 0 364
Friday 1 156
Friday 2 79
Friday 3 39
Friday 4 55
Friday 5 32 ....
I want to count and appear the results in this format:
wday whour count
Friday 0-1 364
Friday 1-2 156
Friday 2-3 79
Friday 3-4 39
Friday 4-5 55
Friday 5-6 32 ....
How can i do this?

try smth like :
WITH a as (
SELECT
to_char(us.created::timestamp,'Day') as wday,
extract(hour from us.created::timestamp) as whour,
count(us.id)
FROM users us
GROUP BY wday,whour
)
select
wday
, coalesce(whour,'-',lead(whour) over (partition by wday order by whour)) whour
, count
from a
order by wday, whour
here's the logic sample:
t=# with v(i,e) as (
values
('Friday',2),('Friday',3),('Saturday',4),('Saturday',5),('Saturday',6),('Friday',4),('Friday',5),('Friday',0),('Friday',1)
)
select i,e,concat(e,'-',lead(e) over (partition by i order by e))
from v;
i | e | concat
----------+---+--------
Friday | 0 | 0-1
Friday | 1 | 1-2
Friday | 2 | 2-3
Friday | 3 | 3-4
Friday | 4 | 4-5
Friday | 5 | 5-
Saturday | 4 | 4-5
Saturday | 5 | 5-6
Saturday | 6 | 6-
(9 rows)

Related

how to filter my database with only "Monday" queries?

I am trying to extract only monday from timestamp (in time,date,month format) in my database (would do count on it after wards). I tried to convert my dates to string characters. I was able to get all days in text format.
select to_char (payment_date, 'dy') as days from payment;
however, when i try to add where in it, to filter days, it gives an error.
select to_char (payment_date, 'dy') as days
from payment
where days.payment_date = 'mon';
You might want something like this (all of the code below is available on the fiddle here):
Generate a table with all of the dates in the first half of 2022.
CREATE TABLE dat AS
SELECT the_day FROM GENERATE_SERIES
('2022-01-01'::TIMESTAMPTZ, '2022-06-30'::TIMESTAMPTZ, '1 DAY') AS t(the_day);
and then run:
SELECT
the_day::DATE,
EXTRACT(ISODOW FROM the_day),
to_char(the_day, 'Day')
FROM
dat
WHERE
EXTRACT(ISODOW FROM the_day) = 1;
Result:
the_day extract to_char
2022-01-03 1 Monday
2022-01-10 1 Monday
2022-01-17 1 Monday
2022-01-24 1 Monday
2022-01-31 1 Monday
2022-02-07 1 Monday
2022-02-14 1 Monday
2022-02-21 1 Monday
2022-02-28 1 Monday
2022-03-07 1 Monday
...
... snipped for brevity
...
or similarly:
SELECT
the_day::DATE,
EXTRACT(ISODOW FROM the_day),
to_char(the_day, 'DAY')
FROM
dat
WHERE
to_char(the_day, 'DAY') = 'WEDNESDAY';
Result:
the_day extract to_char
2022-01-05 3 WEDNESDAY
2022-01-12 3 WEDNESDAY
2022-01-19 3 WEDNESDAY
2022-01-26 3 WEDNESDAY
2022-02-02 3 WEDNESDAY
2022-02-09 3 WEDNESDAY
...
... snipped for brevity
...

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)

Should I use GROUPING SETS, CUBE, or ROLLUP in Postgres

We just upgraded last month to Postgres 10, so I'm new to a few of its feautures.
So this query requests that I display the days each student is taken care of and require a sum of how many students are taken care of for each weekday
select distinct s.studentnr,(CASE When lower(cd.weekday) like lower('MONDAY')
then 1 else 0 end) as MONDAY,
(CASE When lower(cd.weekday) like lower('TUESDAY')
then 1 else 0 end) as TUESDAY,
(CASE When lower(cd.weekday) like lower('WEDNESDAY')
then 1 else 0 end) as WEDNESDAY,
(CASE When lower(cd.weekday) like lower('THURSDAY')
then 1 else 0 end) as THURSDAY,
(CASE When lower(cd.weekday) like lower('FRIDAY')
then 1 else 0 end) as FRIDAY,
scp.durationid
from student s
full join studentcarepreference scp on s.id = scp.studentid
full join careday cd on cd.studentcarepreferenceid = scp.id
join pupil per on per.id = s.personid
join studentschool ss ON ss.studentid = s.id
join duration d on d.id = sdc.durationid
AND d.id BETWEEN ss.validfrom AND ss.validuntil
where sdc.durationid = 1507
and cd.weekday is not null
order by s.studentnr
where s.studentnr and cd.weekday are both varchar type
resulting in
However I need the following data as follows.
Required result
Which approach is best to use in this kind of query?
new results after change to code
select case grouping(studentnr)
when 0 then studentnr
else count(distinct studentnr)|| ' students'
end studentnr
, count(case lower(cd.weekday) when 'monday' then 1 end) monday
, count(case lower(cd.weekday) when 'tuesday' then 1 end) teusday
, count(case lower(cd.weekday) when 'wednesday' then 1 end) wednesday
, count(case lower(cd.weekday) when 'thursday' then 1 end) thursday
, count(case lower(cd.weekday) when 'friday' then 1 end) friday
from mydata
group by rollup ((studentnr))
order by studentnr
Nearly there I guess, just the results or values are wrong. what would you suggest I look into to correcgt the results?
It looks like you want to ROLLUP yourdata using a GROUPING SET:
select case grouping(studentnr)
when 0 then studentnr
else count(distinct studentnr)|| ' students'
end studentnr
, count(distinct case careday when 'monday' then studentnr end) monday
, count(distinct case careday when 'tuesday' then studentnr end) teusday
, count(distinct case careday when 'wednesday' then studentnr end) wednesday
, count(distinct case careday when 'thursday' then studentnr end) thursday
, count(distinct case careday when 'friday' then studentnr end) friday
, durationid
from yourdata
group by rollup ((studentnr, durationid))
Which yields the desired results:
| studentnr | monday | teusday | wednesday | thursday | friday | durationid |
|------------|--------|---------|-----------|----------|--------|------------|
| 10177 | 1 | 1 | 1 | 1 | 1 | 1507 |
| 717208 | 1 | 1 | 1 | 1 | 1 | 1507 |
| 722301 | 1 | 1 | 1 | 1 | 0 | 1507 |
| 3 students | 3 | 3 | 3 | 3 | 2 | (null) |
The second set of parenthesis in the ROLLUP indicates that studentnr and durationid should be summarized at the same level when doing the roll up.
With just one level of summarization, there's not much difference between ROLLUP and CUBE, however to use GROUPING SETS would require a slight change to the GROUP BY clause in order to get the lowest desired level of detail. All three of the following GROUP BY statements produce equivalent results:
group by rollup ((studentnr, durationid))
group by cube ((studentnr, durationid))
group by grouping sets ((),(studentnr, durationid))

How to get last 3 Months of "Monday to Sunday" dates In Redshift?

How can I get last 3 Months of "Monday to Sunday" dates in Redshift?
S.no Start_dt End_dt week
1 18-Jul-16 24-Jul-16 Week1
2 25-Jul-16 31-Jul-16 Week2
3 1-Aug-16 7-Aug-16 Week3
4 8-Aug-16 14-Aug-16 Week4
5 15-Aug-16 21-Aug-16 Week5
6 22-Aug-16 28-Aug-16 Week6
7 29-Aug-16 4-Sep-16 Week7
8 5-Sep-16 11-Sep-16 Week8
9 12-Sep-16 18-Sep-16 Week9
10 19-Sep-16 25-Sep-16 Week10
11 26-Sep-16 2-Oct-16 Week11
12 3-Oct-16 9-Oct-16 Week12
13 10-Oct-16 16-Oct-16 Week13
I've tried this:
select
trunc(date_trunc('week',sysdate)) st_dt,
trunc(date_trunc('week', sysdate)+6) ed_dt,
'week'||row_number() over (order by null) as week
but it only returns the current week's Monday and Sunday.
You can use generate_series() to generate a range of dates:
SELECT
trunc(day) as start_date,
trunc(day + 6) as end_date
FROM
(select date_trunc('week', sysdate) + (generate_series(1, 12) * interval '1 week') as day)
ORDER BY 1 ASC
This results in:
week start week end
2016-10-24 2016-10-30
2016-10-31 2016-11-06
2016-11-07 2016-11-13
2016-11-14 2016-11-20
2016-11-21 2016-11-27
2016-11-28 2016-12-04
2016-12-05 2016-12-11
2016-12-12 2016-12-18
2016-12-19 2016-12-25
2016-12-26 2017-01-01
2017-01-02 2017-01-08
2017-01-09 2017-01-15
Please note that generate_series() in Amazon Redshift cannot be joined with existing tables. It can only be used as a "Leader-only" query.

PostgreSQL custom week number - first week containing Feb 1st

I'm new to SQL functions and trying to create a calendar table that displays custom week numbers, with each week starting with Saturday and ending on Friday. The first week of each year always contains Feb.1st of that year.
For example, if the day of the week of Feb. 1st for a particular year is Tuesday, then the first week for that year is from Jan. 29 to Feb. 4.
I've been struggling with this problem for a couple days and the only solution I can come up with is as follows:
First, I created a calendar table with a column called "CustomizedWeekNo" to reflect the year cycle starting from the week containing Feb. 1st. But the first day of each week is Monday.
Create Table Calendar
(CalendarDate Date, WeekNo smallInt, WeekDayNo text, CustomizedWeekNo smallInt)
Create or Replace Function CustomizeWeekNumber()
Returns void
as $$
Declare beginDate Date :='2015-01-31'; endDate Date := '2017-01-27';
Begin
While beginDate <= endDate loop
Insert Into Calendar (CalendarDate, WeekNo, WeekDayNo, CustomizedWeekNo)
Select
beginDate As CalendarDate
,DATE_PART('week', beginDate::timestamp)::smallint As WeekNo
,(Case When DATE_PART('isodow', beginDate::timestamp)::smallint = 6
Then 'Sat'
When DATE_PART('isodow', beginDate::timestamp)::smallint = 7
Then 'Sun'
When DATE_PART('isodow', beginDate::timestamp)::smallint = 1
Then 'Mon'
When DATE_PART('isodow', beginDate::timestamp)::smallint = 2
Then 'Tue'
When DATE_PART('isodow', beginDate::timestamp)::smallint = 3
Then 'Wed'
When DATE_PART('isodow', beginDate::timestamp)::smallint = 4
Then 'Thur'
Else 'Fri'
End) As WeekDayNo;
,(Case When beginDate < '2016-01-04'
Then DATE_PART('week', beginDate::timestamp)::smallint - 5
When beginDate >= '2016-01-04' and beginDate < '2016-01-30'
Then (date_part('week', '2016-01-03'::timestamp)::smallint - 5 + date_part('week', beginDate::timestamp)::smallint)
When beginDate >= '2016-01-30' and beginDate < '2017-01-02'
Then date_part('week', beginDate::timestamp)::smallint - 4
Else
date_part('week', '2017-01-01'::timestamp)::smallint - 4 + date_part('week', beginDate::timestamp)::smallint
End) As CustomizedWeekNo;
Select (beginDate + interval'1 day') into beginDate;
End loop;
End; $$
language plpgsql;
# Run the function
select CustomizeWeekNumber()
Next, I update the "CustomizedWeekNo" column
-- My customized week starts from every Saturday and ends on every Friday
update calendar
set CustomizedWeekNo = CustomizedWeekNo + 1
where WeekDayNo in ('Sat', 'Sun');
Lastly, I create another function to return the information I need. I also reformat the value of the "CustomizedWeekNo" to include the specific year.
create or replace function update_CustomizedWeek(date, date)
returns table(Calendar_Date Date, Week_No int, WeekDay_No text, Customized_Week_No int)
as $$
begin
return query
select t.CalendarDate, t.WeekNo, t.WeekDayNo,
case when t.CustomizedWeekNo <= 9
then (date_part('year', t.CalendarDate::timestamp)::text||'0'||t.CustomizedWeekNo::text)::int
else (date_part('year', t.CalendarDate::timestamp)::text||t.CustomizedWeekNo::text)::int
end
from Calendar t
where t.CalendarDate >= $1 and t.CalendarDate <= $2
order by t.CalendarDate;
end; $$
language plpgsql;
--Example
select * from update_CustomizedWeek('2015-01-30', '2015-02-10')
The final result will look like:
Calendar_Date | Week_No | WeekDay_No | Customized_Week_No
------------- | ------- | ---------- | -------------------
2015-01-31 | 5 | Sat | 201501
2015-02-01 | 5 | Sun | 201501
2015-02-02 | 6 | Mon | 201501
2015-02-03 | 6 | Tue | 201501
2015-02-04 | 6 | Wed | 201501
2015-02-05 | 6 | Thur | 201501
2015-02-06 | 6 | Fri | 201501
2015-02-07 | 6 | Sat | 201502
2015-02-08 | 6 | Sun | 201502
2015-02-09 | 7 | Mon | 201502
2015-02-10 | 7 | Tue | 201502
As you can see, I used a lot of "hard coding" here. I would like to be able to generate a date range along with the customized week number for any year, not just 2016 or 2017. Any help is really appreciated.
date_trunc() truncates to the previous Monday. You can still use it by adding 2 days to the input (the difference between Sat and Mon), then subtract 2 days from the output. Works perfectly.
This query produces your desired output exactly:
SELECT d::date AS "Calendar_Date"
, EXTRACT('WEEK' FROM d)::int AS "Week_No"
, to_char(d, 'Dy') AS "WeekDay_No"
, base_nr + (rn::int - 1) / 7 AS "Customized_Week_No"
FROM (
SELECT date_trunc('week', feb3) - interval '2 days' AS day1 -- subtract 2 days
, EXTRACT('year' FROM feb3)::int * 100 + 1 AS base_nr
FROM (SELECT timestamp '2015-02-03') input(feb3) -- add 2 days, so Feb 3 (!)
) t, generate_series (day1
, day1 + interval '1 year - 1 day'
, interval '1 day') WITH ORDINALITY AS d(d, rn);
Just provide Feb 3 of the respective year: timestamp '2015-02-03'.