Query filtering by week - sql

How to query (SELECT) in Postgresql, so that the results of a column with different dates, are between Sunday and Saturday of the current week.
Query fake example:
SELECT * FROM table WHERE datecolumn BETWEEN CURRENT WEEK
In another query, I have the number of the week in the year. How to make a SELECT for these dates, applying in the WHERE clause the specific week number in the specific year.
Query fake example:
SELECT * FROM table WHERE datecolumn BETWEEN WEEK15 FROM year 2020

Perhaps you can use something like this:
SELECT *
FROM table
WHERE
EXTRACT(week FROM datecolumn) = EXTRACT(week FROM NOW())
AND
EXTRACT(isoyear FROM datecolumn) = EXTRACT(isoyear FROM NOW())
The week is ISO-8601 week number. 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.
In the ISO week-numbering system, it is possible for early-January dates to be part of the 52nd or 53rd week of the previous year, and for late-December dates to be part of the first week of the next year.
For example, 2005-01-01 is part of the 53rd week of year 2004, and 2006-01-01 is part of the 52nd week of year 2005, while 2012-12-31 is part of the first week of 2013.
It's recommended to use the isoyear field together with week to get consistent results.
If you need custom (non-ISO) week numbering - you will have to craft your own calculation.

I would recommend the following pair of conditions:
where
date_column >= current_date - extract(dow from current_date) * interval '1 day'
and date_column < current_date - (extract(dow from current_date) - 8) * interval '1 day'
Postgres' date_trunc(week, ...) starts weeks on Monday, so we need something a little more complicated, using extract(dow from ...), which returns 0 on Sundays.
The advantage of this approach is that it is SARGeable, since no function is applied to the column being filtered. This means that this would happily take advantage of an index on the date column.

I would use date_trunc(), but like this. For the current week:
where datecolumn >= date_trunc('week', now()) and
datecolumn < date_trunc('week', now()) + interval '1 week'
For the nth week of the year, this is trickier. I think this does what you want:
where datecolumn >= (date_trunc('week', now()) -
(extract(week from now()) - 1) * interval '1 week' +
<n> * interval '1 week'
) and
datecolumn < (date_trunc('week', now()) -
(extract(week from now()) - 1) * interval '1 week' +
(<n> + 1) * interval '1 week'
)
Both of these are structured so the computations are NOT on the columns, so they are compatible with using indexes.

Related

Get last week's data from a table with a creation date

Using the following query for retrieving last week data,but I am getting error as
Postgres ERROR: syntax error at or near "CAST" Position: 127
I don't know where the error is:
SELECT count(*), extract(day from createdon) AS period
FROM orders
WHERE servicename =:serviceName AND createdon BETWEEN
CAST(NOW() AS CAST(DATE-EXTRACT(DOW FROM NOW()) AS INTEGER-7)) AND
CAST(NOW() AS CAST(DATE-EXTRACT(DOW from NOW()) AS INTEGER))
GROUP BY extract(day from createdon)
ORDER BY extract(day from createdon);
You are overcomplicating things. To get last week's data, just get everything after the "start of this week" minus 7 days:
The "start of the this week" can be evaluated using date_trunc('week', current_date).
If you subtract 7 days you get the start of the previous week: date_trunc('week', current_date) - interval '7' day. If you subtract 1 day, you get the end of the previous week.
date_trunc always uses Monday as the start of the week, so if your week starts on Sunday, just subract one more, e.g. date_trunc('week', current_date)::date - 8 will be the Sunday of the previous week
Putting that all together you get:
SELECT count(*), extract(day from createdon) AS period
FROM orders
WHERE servicename =:serviceName
AND createdon
between date_trunc('week', current_date)::date - 7
and date_trunc('week', current_date)::date - 1
GROUP BY extract(day from createdon)
ORDER BY extract(day from createdon);
If your columns are timestamp columns you can simply cast createdon to a date to get rid of the time part:
AND createdon::date
between date_trunc('week', current_date)::date - 7
and date_trunc('week', current_date)::date
Note that a regular index on createdon will not be used for that condition, you would need to create an index on createdon::date if you need the performance.
If you can't (or don't want to) create such an index, you need to use something different then between
AND createdon >= date_trunc('week', current_date)::date - 7
AND createdon < date_trunc('week', current_date)::date
(Note the use of < instead of <= which is what `between is using)
Another option is to convert the date information to a combination of week and year:
AND to_char(createdon, 'iyyy-iw') = to_char(date_trunc('week', current_date)::date - 7, 'iyyy-iw')
Note, that I used the ISO week definition for the above. If you are using a different week numbering system, you need a different format mask for the to_char() function.
If you work with the North American week system (whose weeks start on Sunday), your original approach was good enough, just use the correct syntax of CAST(<epr> AS <type>):
SELECT COUNT(*),
EXTRACT(DAY FROM createdon) period
FROM orders
WHERE servicename = 'Cell Tower Monitoring'
AND createdon BETWEEN CURRENT_DATE - CAST(EXTRACT(DOW FROM CURRENT_DATE) AS INTEGER) - 7
AND CURRENT_DATE - CAST(EXTRACT(DOW FROM CURRENT_DATE) AS INTEGER) - 1
GROUP BY EXTRACT(DAY FROM createdon)
ORDER BY EXTRACT(DAY FROM createdon);
Note: this assumes that createdon is a DATE column. If it's a TIMESTAMP (or TIMESTAMP WITH TIME ZONE), you need a slightly different version:
SELECT COUNT(*),
EXTRACT(DAY FROM createdon) period
FROM orders
WHERE servicename = 'Cell Tower Monitoring'
AND createdon >= CURRENT_TIMESTAMP - INTERVAL '1 day' * (EXTRACT(DOW FROM CURRENT_TIMESTAMP) + 7)
AND createdon < CURRENT_TIMESTAMP - INTERVAL '1 day' * EXTRACT(DOW FROM CURRENT_TIMESTAMP)
GROUP BY EXTRACT(DAY FROM createdon)
ORDER BY EXTRACT(DAY FROM createdon);
If you want to use the ISO week system (whose weeks start on Monday), then just use ISODOW instead of DOW. Or, you could use the date_trunc('week', ...) function, like in #a_horse_with_no_name's answer.
If you want to use another week systems (f.ex. which starts on Saturday), you'll need some extra logic inside CASE expressions, as subtracting 1 from DOW will not give the expected results at the start of that kind of week (f.ex. on Saturday it would give the week 2 weeks before).

PostgreSQL Query to select data from last week?

I have a table which has all the purchases of my costumers. I want to select all entries from the last week, (week start from Sunday).
id value date
5907 1.20 "2015-06-05 09:08:34-03"
5908 120.00 "2015-06-09 07:58:12-03"
I've tried this:
SELECT id, valor, created, FROM compras WHERE created >= now() - interval '1 week' and parceiro_id= '1'
But I got the data from the last week including data from this week, I only want data from the last week.
How to get data only from last week ?
This condition will return records from Sunday till Saturday last week:
WHERE created BETWEEN
NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
There is an example:
WITH compras AS (
SELECT ( NOW() + (s::TEXT || ' day')::INTERVAL )::TIMESTAMP(0) AS created
FROM generate_series(-20, 20, 1) AS s
)
SELECT to_char( created, 'DY'::TEXT), created
FROM compras
WHERE created BETWEEN
NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
In answer to #d456:
Wouldn't using BETWEEN include midnight on Sunday at both ends of the interval?
That right, BETWEEN includes midnight on Sunday at both ends of the interval. To exclude midnight on Sunday at end of interval it is necessary to use operators >= and <:
WITH compras AS (
SELECT s as created
FROM generate_series( -- this would produce timestamps with 20 minutes step
(now() - '20 days'::interval)::date,
(now() + '20 days'::interval)::date,
'20 minutes'::interval) AS s
)
SELECT to_char( created, 'DY'::TEXT), created
FROM compras
WHERE TRUE
AND created >= NOW()::DATE-EXTRACT(DOW FROM NOW())::INTEGER-7
AND created < NOW()::DATE-EXTRACT(DOW from NOW())::INTEGER
Postgres by default starts weeks on a Sunday, so you are in luck. You can use date_trunc() to get the beginning of the previous week:
WHERE (created >= date_trunc('week', CURRENT_TIMESTAMP - interval '1 week') and
created < date_trunc('week', CURRENT_TIMESTAMP)
)
EDIT:
Postgres by default starts week for date_trunc on Monday, but for dow on Sunday. So, you can do what you want by using that logic, which Nicolai has in his answer.

postgres query to check for records not falling between two time periods using the ISO WEEK number and year from DB

i do have a query which works fine but I was just wondering if there are other ways or alternate method to bettter this.
I have a table where i am fetching those records exceeding or do not fall between 1 year time interval however there is only the year and ISO week number column in the table (integer values).
basically the logic is to check ISO WEEK - YEAR falls between 'current_date - interval '1 year' AND current_date.
My query is as below :
select * from raj_weekly_records where
(date_dimension_week > extract(week from current_date) and date_dimension_year = extract(year from current_date) )
or (date_dimension_week < extract(week from current_date) and (extract(year from current_date)-date_dimension_year=1) )
or(extract(year from current_date)-date_dimension_year>1);
Here date_dimension_week and date_dimension_year are the only integer parameters by which I need to check is there any other alternate or better way?.This code is working fine no issues here.
Here is an idea. Convert the year/week to a numeric format: YYYYWW. That is, the year times 100 plus the week number. Then you can do the logic with a single comparison:
select *
from raj_weekly_records
where date_dimension_year * 100 + date_dimension_week
not between (extract(year from current_date) - 1) * 100 + extract(week from current_date) and
extract(year from current_date) * 100 + extract(week from current_date)
(There might be an off-by one error, depending on whether the weeks at the ends are included or excluded.)
select *
from raj_weekly_records
where
date_trunc('week',
'0001-01-01 BC'::date + date_dimension_year * interval '1 year'
)
+ (date_dimension_week + 1) * interval '1 week'
- interval '1 day'
not between
current_date - interval '1 year' and current_date

Generate series of week intervals for given month

In a Postgres 9.1 database, I am trying to generate a series of weeks for a given month but with some constraints. I need all weeks to start on Monday and get cut when they start or end in another month.
Example:
For February, 2013 I want to generate a series like this:
start
------------------------
2013-02-01 00:00:00+00
2013-02-04 00:00:00+00
2013-02-11 00:00:00+00
2013-02-18 00:00:00+00
2013-02-25 00:00:00+00
The query that I have now looks like this:
SELECT GREATEST(date_trunc('week', dates.d),
date_trunc('month',dates.d)) as start
FROM generate_series(to_timestamp(1359676800),to_timestamp(1362095999), '1 week') as dates(d)
This query gets me the first 4 weeks but it's missing the week from the 25th. Is it possible to get the last week?
SELECT generate_series(date_trunc('week', date '2013-02-01' + interval '6 days')
, date_trunc('week', date '2013-02-01' + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION SELECT date '2013-02-01'
ORDER BY 1;
This variant does not need a subselect, GREATEST or GROUP BY and only generates the required rows. Simpler, faster. It's cheaper to UNION one row.
Add 6 days to the first day of the month before date_trunc('week', ...) to compute the first Monday of the month.
Add 1 month and subtract 1 day before date_trunc('week', ...) to get the last Monday of the month.
This can conveniently be stuffed into a single interval expression: '1 month - 1 day'
UNION (not UNION ALL) the first day of the month to add it unless it's already included as Monday.
Note that date + interval results in timestamp, which is the optimum here. Detailed explanation:
Generating time series between two dates in PostgreSQL
Automation
You can provide the start of the date series in a CTE:
WITH t(d) AS (SELECT date '2013-02-01') -- enter 1st of month once
SELECT generate_series(date_trunc('week', d + interval '6 days')
, date_trunc('week', d + interval '1 month - 1 day')
, interval '1 week')::date AS day
FROM t
UNION SELECT d FROM t
ORDER BY 1;
Or wrap it into a simple SQL function for convenience with repeated calls:
CREATE OR REPLACE FUNCTION f_week_starts_this_month(date)
RETURNS SETOF date AS
$func$
SELECT generate_series(date_trunc('week', $1 + interval '6 days')
, date_trunc('week', $1 + interval '1 month - 1 day')
, interval '1 week')::date AS day
UNION
SELECT $1
ORDER BY 1
$func$ LANGUAGE sql IMMUTABLE;
Call:
SELECT * FROM f_week_starts_this_month('2013-02-01');
You would pass the date for the first day of the month, but it works for any date. You the first day and all Mondays for the following month.
select
greatest(date_trunc('week', dates.d), date_trunc('month',dates.d)) as start
from generate_series('2013-02-01'::date, '2013-02-28', '1 day') as dates(d)
group by 1
order by 1

How do I determine the last day of the previous month using PostgreSQL?

I need to query a PostgreSQL database to determine records that fall within today's date and the last day of the previous month. In other words, I'd like to retrieve everything that falls between December 31, 2011 and today. This query will be re-used each month, so next month, the query will be based upon the current date and January 31, 2012.
I've seen this option, but I'd prefer to avoid using a function (if possible).
Both solutions include the last day of the previous month and also include all of "today".
For a date column:
SELECT *
FROM tbl
WHERE my_date BETWEEN date_trunc('month', now())::date - 1
AND now()::date
You can subtract plain integer values from a date (but not from a timestamp) to subtract days. This is the simplest and fastest way.
For a timestamp column:
SELECT *
FROM tbl
WHERE my_timestamp >= date_trunc('month', now()) - interval '1 day'
AND my_timestamp < date_trunc('day' , now()) + interval '1 day'
I use the < operator for the second condition to get precise results (read: "before tomorrow").
I do not cast to date in the second query. Instead I add an interval '1 day', to avoid casting back and forth.
Have a look at date / time types and functions in the manual.
For getting date of previous/last month:
SELECT (date_trunc('month', now())::date - 1) as last_month_date
Result: 2012-11-30
For getting number of days of previous/last month:
SELECT DATE_PART('days', date_trunc('month', now())::date - 1) last_month_days
Result: 30
Try this:
SELECT ...
WHERE date_field between (date_trunc('MONTH', now()) - INTERVAL '1 day')::date
and now()::date
...
Try
select current_date - cast((date_part('day', current_date) + 1) as int)
take from http://wiki.postgresql.org/wiki/Date_LastDay, and modified to return just the days in a month
CREATE OR REPLACE FUNCTION calc_days_in_month(date)
RETURNS double precision AS
$$
SELECT EXTRACT(DAY FROM (date_trunc('MONTH', $1) + INTERVAL '1 MONTH - 1 day')::date);
$$ LANGUAGE 'sql' IMMUTABLE STRICT;
select calc_days_in_month('1999-05-01')
returns 31
Reference is taken from this blog:
You can use below function:
CREATE OR REPLACE FUNCTION fn_GetLastDayOfMonth(DATE)
RETURNS DATE AS
$$
SELECT (date_trunc('MONTH', $1) + INTERVAL '1 MONTH - 1 day')::DATE;
$$ LANGUAGE 'sql'
IMMUTABLE STRICT;
Sample executions:
SELECT *FROM fn_GetLastDayOfMonth(NOW()::DATE);