Week Day Starting from a Certain Day (01 Jan 2021) in Postgres - postgresql

I am trying to get week numbers in a Year starting from a certain day
I've checked the stack but quite confused.
SELECT EXTRACT(WEEK FROM TIMESTAMP '2021-01-01'),
extract('year' from TIMESTAMP '2021-01-01')
The output is 53|2021
I want it to be 01|2021
I understand the principle of the isoweek but I want the year to start in 01-01-2021
The aim is to use intervals from this day to determine week numbers
Week N0| End Date
1 | 01-01-2021
2 | 01-08-2021
5 | 01-29-2021
...

This is really strange way to determine the week number, but in the end it's a simple math operation: the number of days since January first divided by 7.
You can create a function for this:
create function custom_week(p_input date)
returns int
as
$$
select (p_input - date_trunc('year', p_input)::date) / 7 + 1;
$$
language sql
immutable;
So this:
select date, custom_week(date)
from (
values
(date '2021-01-01'),
(date '2021-01-08'),
(date '2021-01-29')
) as v(date)
yields
date | custom_week
-----------+------------
2021-01-01 | 1
2021-01-08 | 2
2021-01-29 | 5

Related

Only get rows based on flag for weekend / weekday / both

I have a whole bunch of tariffs, some work on weekends, some work on weekdays some on both. Sometimes I'll be querying on NOW() but sometimes I'll be querying on datetime column.
id | Weekday | Weekend | Price
1 | 1 | 0 | 0.04
2 | 0 | 1 | 0.02
date
2020-04-15 00:00:00
2012-04-16 00:00:00
The date is from another table and is not related to the Price / days of week.
I know I can get the weekend dates by
SELECT * FROM tariff where EXTRACT(ISODOW FROM date) IN (6,7)
however I can't think of how I'd get rows that are either weekend / weekdays or both given a date.
** edit **
Updated the tables to show the dates are seperate. What I'm trying to get is the tariff that corresponds to the date in that table, whether it's on a week day or a weekend (or both but I can extrapolate that).
The weekend 1 is the tariff that is used for weekends, weekdays 1, all days is both.
Cannot give you a query, supply anything to query. Nor can we be sure that the columns Weekday and Weekend mean as you didn't tell us. But if we take them as boolean indicator where 1 means desired may some thing like will work for you.
select ...
from ...
where ...
and ( (weekday = 1 and weekend =1)
or (weekday = 1 and extract(isodow from date) not in (6,7))
or (weekend = 1 and extract(isodow from date) in (6,7))
) ;

Defining a custom week using days of the month or year in postgresql

I am running a simple query to get weekly revenue from our sales
SELECT date_trunc('week', payment_date) AS week, sum(payment_amount)
FROM payment
WHERE payment_date BETWEEN '2010-jan-1' AND '2016-dec-31'
GROUP BY week
Now I need my week start and end date to be static for every year. All 52 weeks of the year need to be accounted for e.g.
Week 1: Jan 1-7
Week 2: Jan8-14
Week 3: Jan15-21
Week 4: Jan22-28
Week 5: Jan29-Feb4 and so forth
I did some investigation and figured out that I need a user defined function using the payment_date as argument and returning a week value. I can then call this function in the SQL query above, in place of the date_trunc() function.
How can I use an incremental loop to assign a week value to the payment_date?
Can I also use this return value in group by clause in the SQL query?
Some explanation with detailed examples will be highly appreciated since I have basic to intermediate knowledge of SQL.
---------------Edit--------------
I'm trying to use 2 functions now to take into account the leap year, where I would still want March 4th to be included in the 9th week. Ive tried to use the function by &klin and convert it to SQL, I keep getting "syntax error at or near 'int' on line 9. My code is below.
create or replace function is_leap_year(int)
returns boolean language sql as $$
select $1 % 4 = 0 and ($1 % 100 <> 0 or $1 % 400 = 0)
$$;
create or replace function week_no(timestamp)
returns int language sql as $body$
declare
y int;
day_shift int;
begin
y = extract(year from $1);
day_shift = 1 + (is_leap_year(y) and $1 > make_date(y, 2, 28))::int;
return ((extract(doy from $1)::int)- day_shift) / 7+ 1;
end
$body$;
SELECT week_no(payment_date) as week_number, sum(payment_amount)
from payment p join payment_event pe on p.payment_event_id =
pe.payment_event_id
where payment_date between '2016-jan-1' and '2017-jan-1'
and pe.payment_event_type_id != 2
group by week_number
order by week_number
First, there are problems with your requirements.
Now I need my week start and end date to be static for every year.
They can't be. Leap years happen. February 29 will either shift start and end dates one year out of every four, or you'll need to allow one week to have eight days.
All 52 weeks of the year need to be customized for . . .
I think you mean that all 52 weeks need to be accounted for. But 52 * 7 = 364. You're missing a day.
I think the simplest expression that calculates a week number from a date is (extract(doy from payment_date)::integer / 7) as week. I don't know whether it's worth putting that into a function. Instead, I might start with creating a view that uses that expression.
But a calculation won't do anything special about February 29, or about the fact that every year has more than 52 * 7 days.
I really think your best bet here is to build a table instead of using a calculation.
create table weeks (
calendar_date date primary key,
week_num integer not null
check (week_num between 1 and 53)
);
Populate it with this dates for 2016 and 2017, and with calculated weeks, to give us a starting point. (2016 was a leap year.)
insert into weeks
select
('2016-01-01'::date + (n || ' days')::interval)::date as calendar_date
, extract(doy from ('2016-01-01'::date + (n || ' days')::interval)::date)::integer / 7 + 1 as calencar_week
from generate_series (0, 730) n;
Let's look at week 9.
select *
from weeks
where week_num = 9
order by calendar_date;
calendar_date week_num
--
2016-02-25 9
2016-02-26 9
2016-02-27 9
2016-02-28 9
2016-02-29 9
2016-03-01 9
2016-03-02 9
2017-02-25 9
2017-02-26 9
2017-02-27 9
2017-02-28 9
2017-03-01 9
2017-03-02 9
2017-03-03 9
In 2016, the calculated week 9 ran from 2016-02-25 to 2016-03-02. In 2017, it ran from 2016-02-25 to 2017-03-03. But now that all these week numbers are in a table, you can adjust them any way you like. You can even change the adjustments from year to year if it makes sense to do that.
Use doy (the day of the year) in the way like this:
create or replace function week_no(date)
returns int language sql as $$
select ((extract(doy from $1)::int)- 1) / 7+ 1
$$;
with the_table(a_date) as (
values
('2017-01-01'::date),
('2017-01-07'),
('2017-01-08'),
('2017-01-14'),
('2017-01-15'),
('2017-01-22')
)
select extract(doy from a_date)::int as doy, week_no(a_date)
from the_table;
doy | week_no
-----+---------
1 | 1
7 | 1
8 | 2
14 | 2
15 | 3
22 | 4
(6 rows)
If you want to correct the week number so that March 4th is always in 9th week (even in a leap year), use this handy function:
create or replace function is_leap_year(int)
returns boolean language sql as $$
select $1 % 4 = 0 and ($1 % 100 <> 0 or $1 % 400 = 0)
$$;
Your function may look like this (I've used the plpgsql language for better readability though this also can be coded as an sql function):
create or replace function week_no_corrected(date)
returns int language plpgsql as $$
declare
y int = extract (year from $1);
day_shift int = 1 + (is_leap_year(y) and $1 > make_date(y, 2, 28))::int;
begin
return ((extract(doy from $1)::int)- day_shift) / 7+ 1;
end;
$$;
with the_table(a_date) as (
values
('2016-03-03'::date),
('2016-03-04'),
('2016-03-05'),
('2017-03-03'),
('2017-03-04'),
('2017-03-05')
)
select a_date, week_no(a_date), week_no_corrected(a_date)
from the_table;
a_date | week_no | week_no_corrected
------------+---------+-------------------
2016-03-03 | 9 | 9
2016-03-04 | 10 | 9
2016-03-05 | 10 | 10
2017-03-03 | 9 | 9
2017-03-04 | 9 | 9
2017-03-05 | 10 | 10
(6 rows)
In an SQL function you cannot use variables, assignments may be replaced by derived tables:
create or replace function week_no_corrected(date)
returns int language sql as $$
select ((extract(doy from $1)::int)- day_shift) / 7 + 1
from (
select 1 + (is_leap_year(y) and $1 > make_date(y, 2, 28))::int as day_shift
from (
select extract (year from $1)::int as y
) s
) s
$$;
By breaking your problem down to month-day strings it will allow you to use the same logic across multiple years
mysql> SELECT "01-07" < "01-08";
+-------------------+
| "01-07" < "01-08" |
+-------------------+
| 1 |
+-------------------+
1 row in set (0.08 sec)
A simple date format of %m-%d works for comparing the payment dates to the week buckets you want to assign.
To manually assign all 52 week ranges, you can use a case statement:
SET #md_format="%m-%d";
SELECT
CASE
WHEN (date_format(`input_date`, #md_format) < "01-08") THEN 1
WHEN (date_format(`input_date`, #md_format) < "01-15") THEN 2
WHEN (date_format(`input_date`, #md_format) < "01-22") THEN 3
-- ... All other cases
ELSE 52
END;
See the docs for the syntax to define a function
Functions will allow you to do operations like:
SELECT week_bucket(payment_date) `week`, SUM(revenue) `revenue`
FROM my_table
WHERE week_bucket(payment_date) > 13
AND week_bucket(payment_date) < 15
GROUP BY `week`;

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'.

db2 - Get Week of the Month

Hello guys I need know the number of the week of a month
For example:
Date | WeekOfTheMonth
2015-04-15 | 3
2015-03-01 | 1
2015-01-08 | 2
Beacuse in docs only see Week of the year
Thanks
Test this SQL query
SELECT KURRENT - FIRSTY + 1 FROM ( -- AVOID CURRENT AND FIRST KEYWORD
SELECT
WEEK_ISO(DATE(1) + (YEAR(date_colum)-1) YEARS + (MONTH(date_colum)-1) MONTHS )AS FIRSTY ,
WEEK_ISO(date(date_column)) AS KURRENT
FROM TEST_DATE_TABLE
) AS T
Try this:
SELECT week_iso(current date) -
week_iso(last_day(current date) - 1 month + 1 day)
FROM sysibm.sysdummy1
You don't mention your platform, but VARCHAR_FORMAT has a W format string, which gives the week of the month:
Week of the month (1-5), where week 1 starts on the first day of the
month and ends on the seventh day.
SELECT VARCHAR_FORMAT(CURRENT TIMESTAMP, 'W') FROM SYSIBM.SYSDUMMY1

What is the datatype of Months(only months) in postgres?

I need to create a table with a column named as "months" in postgresql."Month" column should have January, February,etc not as 1, 2,3, etc. And I need to retrieve data ordered by months. what is the datatype I should used and how can I retrieve data ordered by month?
If you only need to save months, and not entire dates, I'd create an enum:
CREATE TYPE month_enum AS ENUM
('January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
);
It is best to save the month as an integer and show the month name at query time:
with months(month) as (
select generate_series(1, 12)
)
select
month as month_number,
to_char(
'1999-12-31'::date + month * interval '1 month',
'Month'
) as month_name
from months
order by month_number; -- or by month_name
month_number | month_name
--------------+------------
1 | January
2 | February
3 | March
4 | April
5 | May
6 | June
7 | July
8 | August
9 | September
10 | October
11 | November
12 | December
To make it easy to build the query create a function returning the month name:
create or replace function month_name(month integer)
returns text as $$
select
to_char(
'1999-12-31'::date + month * interval '1 month',
'Month'
);
$$ language sql;
Now it is simply:
with months(month) as (
select generate_series(1, 12)
)
select
month as month_number,
month_name(month)
from months
order by month_number;
From what you've asked, you have a few options depending on what you need:
If you only need Months as a static record but you don't actually need the time, you can use enum, as Mureinik answered,
If you need the Month as part of a specific time, you could use datetime.
Assuming you went with ENUM you can just use SELECT * FROM "Month" ORDER BY id ASC.
a_horse_with_no_name does have a point by saying that it might be best to use numerical values for months due to localization issues. You can make Month its own separate table for different month names in different languages, but there's probably a more effective way to do it. Alternatively, could have the number for each month and upon querying you could call the name of the month based on the number in your project like suggested. That way you can call a different name depending on localization.