How can i get number of quarters between two dates in PostgreSQL? - sql

I have MS SQL function DATEDIFF
SELECT DATEDIFF(QQ, 0, '2018-09-05')
that returns 474(integer).
What is PostgreSQL equivalent of this function ?

MSSQL 0 is the date '1900-01-01' in DATEDIFF(QQ, 0, '2018-09-05'), that function will get the number of QUARTER from 1900-01-01 to 2018-09-05
But PostgreSQL does not have a QUARTER number function.
You can try to use
EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') to get this month QUARTER number.
date_part('year',age('2018-09-05','1900-01-01')) get year number between
'2018-09-05' and '1900-01-01'.
then do some calculation.
select (EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') +
date_part('year',age('2018-09-05','1900-01-01')) * 4) -1 QUARTER
Results:
| quarter |
|---------|
| 474 |

I think the current accepted is incorrect.
e.g. if you change date '1900-01-01' to '2017-07-01' you actually get 6 quarter diff.
But expected result should be:
include first and last quarter: 5
exclude first and last quarter: 3
exclude last quarter: 4
select
age('2018-09-05','2017-07-01') age,
(EXTRACT(QUARTER FROM TIMESTAMP '2018-09-05') +
date_part('year',age('2018-09-05','2017-07-01')) * 4) -1 QUARTER
Result:
age |quarter|
--------------------|-------|
1 year 2 mons 4 days| 6|
The reason is age('2018-09-05','2017-07-01') return 1 year 2 mons 4 days (maybe more than 12 month).
The answer i use to calculate number of quarter:
with params as (
select
'2017-07-01 00:00:00'::timestamp date_from,
'2018-09-05 00:00:00'::timestamp date_to
)
select
extract( quarter from date_from)::int date_from_quarter,
(extract( quarter from date_to))::int date_to_quarter,
age(date_to, date_from) date_diff,
(extract(year from date_to) - extract(year from date_from))::int how_many_yr,
-- exclude begin and end
(extract(year from date_to) - extract(year from date_from))::int* 4 - extract( quarter from date_from)::int + (extract( quarter from date_to))::int - 1 quarter_diff,
-- exclude begin quarter
(extract(year from date_to) - extract(year from date_from))::int* 4 - extract( quarter from date_from)::int + (extract( quarter from date_to))::int quarter_diff_include_current_quarter
from params
;
Result:
date_from_quarter|date_to_quarter|date_diff |how_many_yr|quarter_diff|quarter_diff_include_current_quarter|
-----------------|---------------|--------------------|-----------|------------|------------------------------------|
3| 3|1 year 2 mons 4 days| 1| 3| 4|

Its complicated on Postgres:
( (DATE_PART('year', '2018-09-05') - DATE_PART('year', '1900-01-01')) * 12
+ (DATE_PART('month', '2018-09-05') - DATE_PART('month', '1900-01-01'))
) / 4
Additional resources: topic

Let's say you have two dates in the example table: start_date and end_date
Here is how to get the difference between these dates in number of quarters:
select * (year(end_date)*4 + quarter(end_date)) - (year(start_date)*4 + quarter(start_date)) as quarter_diff
from example

Related

Get days of the week from a date range in Postgres

So I have the following table :
id end_date name number_of_days start_date
1 "2022-01-01" holiday1 1 "2022-01-01"
2 "2022-03-20" holiday2 1 "2022-03-20"
3 "2022-04-09" holiday3 1 "2022-04-09"
4 "2022-05-01" holiday4 1 "2022-05-01"
5 "2022-05-04" holiday5 3 "2022-05-02"
6 "2022-07-12" holiday6 9 "2022-07-20"
I want to check if a week falls in a holiday range.
So far I can select the holidays that overlap with my choosen week( week_start_date, week_end_date) , but i cant get the exact days in which the overlap happens.
this is the query i'm using, i want to add a mechanism to detect the DAYS OF THE WEEK IN WHICH THE OVERLAP HAPPENS
SELECT * FROM holidays
where daterange(CAST(start_date AS date), CAST(end_date as date), '[]') && daterange('2022-07-18', '2022-07-26','[]')
THE CURRENT QUERY RETURNS THE OVERLLAPPING HOLIDA, (id = 6), however i'm trying to get the exact DAYS OF THE WEEK in which the overlap happens ( in this case, it should be monday,tuesday , wednesday)
You can use the * operator with tsranges, generate a series of dates with the lower and upper dates and finally with to_char print the days of the week, e.g.
SELECT
id, name, start_date, end_date, array_agg(dow) AS days
FROM (
SELECT *,
trim(
to_char(
generate_series(lower(overlap), upper(overlap),'1 day'),
'Day')) AS dow
FROM holidays
CROSS JOIN LATERAL (SELECT tsrange(start_date,end_date) *
tsrange('2022-07-18', '2022-07-26')) t (overlap)
WHERE tsrange(start_date,end_date) && tsrange('2022-07-18', '2022-07-26')) j
GROUP BY id,name,start_date,end_date,number_of_days;
id | name | start_date | end_date | days
----+----------+------------+------------+----------------------------
6 | holiday6 | 2022-07-12 | 2022-07-20 | {Monday,Tuesday,Wednesday}
(1 row)
Demo: db<>fiddle

How to convert a year (with comma) into Year,Month and Days

I have some values that represent years.
For example 37,8 years.
I want to convert it in SQL Oracle to Three columns:
Year: 37
Month: 9
Days: 18
How can I do that in my query?
select
trunc(n) as years
,to_char(date'2020-01-01' + mod(n,1)*365, 'mm') as months
,to_char(date'2020-01-01' + mod(n,1)*365, 'dd') as days
from (select 37.8 n from dual);
YEARS MONTHS DAYS
37 10 19
You can use EXTRACT to get the values:
SELECT EXTRACT( YEAR FROM dt ) - 1970 AS years,
EXTRACT( MONTH FROM dt ) - 1 AS months,
EXTRACT( DAY FROM dt ) - 1 AS days
FROM (
SELECT DATE '1970-01-01' + 37.8 * 365.25 AS dt
FROM DUAL
)
Which outputs:
YEARS | MONTHS | DAYS
----: | -----: | ---:
37 | 9 | 19
db<>fiddle here

How to group dates in Quarters in SQLite

I need to group my dates as Quarters, April to June as Q1, Jul to Sep as Q2, Oct to Dec as Q3 and Jan to March as Q4
I need to add another column besides close_dates showing Quarters. I cannot find any date function i can use.
Any ideas on this.
You can extract the month part and use a case expression:
select
close_date,
case
when 0 + strftime('%m', close_date) between 1 and 3 then 'Q4'
when 0 + strftime('%m', close_date) between 4 and 6 then 'Q1'
when 0 + strftime('%m', close_date) between 7 and 9 then 'Q2'
when 0 + strftime('%m', close_date) between 10 and 12 then 'Q3'
end as quarter
from mytable
The addition of 0 is there to force the conversion of the result of strftime() to a number.
This could also be expressed using date artihmetics (which lets you generate the fiscal year too):
select
close_date,
strftime('%Y', close_date, '-3 months')
|| 'Q' || ((strftime('%m', close_date, '-3 months') - 1) / 4) as year_quarter
from mytable
The format of your dates is not YYYY-MM-DD which is the only valid date format for SQLite.
So if you want to extract the month of a date, any date function that SQLite supports will fail.
You must use the string function SUBSTR() to extract the month and then other functions like NULLIF() and COALESCE() to adjust the quarter to your requirement.
Assuming that the format of your dates is DD/MM/YYYY:
SELECT Close_Date,
'Q' || COALESCE(NULLIF((SUBSTR(Close_Date, 4, 2) - 1) / 3, 0), 4) AS Quarter
FROM tablename
If the format is MM/DD/YYYY then change SUBSTR(Close_Date, 4, 2) to SUBSTR(Close_Date, 1, 2) or just Close_Date because SQLite will implicitly convert the date to a number which will be the starting digits of the date.
See the demo.
Results:
> Close_Date | Quarter
> :--------- | :------
> 01/04/2019 | Q1
> 01/05/2019 | Q1
> 01/10/2019 | Q3
> 01/09/2019 | Q2
> 01/06/2019 | Q1
> 01/09/2019 | Q2
> 01/04/2019 | Q1
> 01/07/2019 | Q2
I would do it with arithmetic rather than a case expression:
select floor( (strftime('%m', close_date) + 2) / 3 ) as quarter

Migrate to Standard SQL: choose the closest sunday from current date

I need to migrate from legacy to standard SQL this query:
SELECT MAX(FECHA)
FROM(
SELECT FECHA, DAYOFWEEK(FECHA) AS DIA
FROM(
SELECT DATE(DATE_ADD(TIMESTAMP("2017-05-29"), pos - 1, "DAY")) AS FECHA
FROM (
SELECT ROW_NUMBER() OVER() AS pos, *
FROM (
FLATTEN((
SELECT SPLIT(RPAD('', 1 + DATEDIFF(TIMESTAMP(CURRENT_DATE()),
TIMESTAMP("2017-05-29")), '.'),'') AS h
FROM (SELECT NULL)),h
)))
))
WHERE DIA=1
The query must return the previous closest sunday date from current date.
When I run this in standard SQL I get
Syntax error: Expected keyword JOIN but got ")" at [12:2] (after FROM (SELECT NULL)),h
The query must return the previous closest sunday date from current date.
#standardSQL
SELECT
DATE_SUB(CURRENT_DATE(), INTERVAL EXTRACT(DAYOFWEEK FROM CURRENT_DATE()) - 1 DAY)
You can replace CURRENT_DATE() with any date and it will return previous closest Sunday
You can use DATE_TRUNC with the WEEK part to truncate to the most recent Sunday. For example:
#standardSQL
WITH Input AS (
SELECT date
FROM UNNEST([
DATE '2017-06-26',
DATE '2017-06-24',
DATE '2017-05-04']) AS date
)
SELECT
date,
FORMAT_DATE('%A', date) AS dayofweek,
DATE_TRUNC(date, WEEK) AS previous_sunday
FROM Input;
This returns:
+------------+-----------+-----------------+
| date | dayofweek | previous_sunday |
+------------+-----------+-----------------+
| 2017-06-24 | Saturday | 2017-06-18 |
| 2017-05-04 | Thursday | 2017-04-30 |
| 2017-06-26 | Monday | 2017-06-25 |
+------------+-----------+-----------------+

Calculating difference between daily sum and a average for the same day of the week in defined time range. SQL 10g Oracle

Hi I'm working with data depending mostly on the day of the week. Data is formatted in a table
Date - position - count/number.
There are multiple different positions.
I was able to sort my data for a each day of the week using.
select MOD(to_char(time, 'J'),7),
sum(COUNT))
from TABLE
where time > sysdate -x
group by to_char(time, 'J')
order by to_char(time, 'J');
This outputs daily sums according to day of the week.
Now I'm able to get an average for a single day of a week in a year.
This code outputs an average for only Sunday
SELECT AVG(asset_sums)
FROM (
select MOD(to_char(time, 'J'),7),
sum(COUNT)) as asset_sums
from table
where time > sysdate -365
and MOD(TO_CHAR(time, 'J'), 7) + 1 IN (7)
group by to_char(time, 'J')
order by to_char(time, 'J')
);
My goal is to be able to get a table with daily sum compared with yearly average for that particular day of the week.
For example yearly average number for Mondays is 57 , Tuesdays 60.
This week my Monday is 59 and Tuesday is 57. Output of the table is
Monday +2, Tuesday -3.
What is the easiest way / most efficient ?
Thanks for your help.
Edit : Format of my data
Date : yyyy-mm-dd | Place : xxxx | Number( of customers) 0 to 10000
2013-09-16 | AAAA | 1534
2013-09-16 | AAAB | 534
2013-09-17 | AAAA | 1434
2013-09-17 | AAAC | 834
2013-09-18 | AAAA | 134
2013-09-18 | AAAD | 183
Needed output
2013-09-16 | Day of the week | Sum | Average monday this year | Difference Sum-AVG
2013-09-16 | 1 (= Monday) | 2068 | 2015| 53
For clarity I will use subquery factoring. First, select the current weeks data. Next, subquery the sum for the day over the current week. Then, subquery the sum for each day over the past year. Then, average the daily sum of each day for each day of the week. Finally, join the two and display the difference.
with
this_week as (
select
time
from table
where time > x - 7
group by time
),
this_week_dly_sum as (
select
to_char(time, 'd') day,
sum(count) sum
from this_week
group by to_char(time, 'd')
),
this_year_dly_sum as (
select
time,
sum(count) sum
from table
where time > x - 365
group by time
),
this_year_dly_avg as (
select
to_char(day, 'd'),
avg(sum) avg
from this_year_dly_sum
group by to_char(day, 'd')
)
select
this_week.time,
to_char(this_week.time, 'day') day of week,
this_week_dly_sum.sum,
this_year_dly_avg.avg,
this_week_dly_sum.sum - this_year_dly_avg.avg difference
from this_week
inner join this_week_dly_sum
on to_char(this_week.time, 'd') = this_week_dly_sum.day
inner join this_year_dly_avg
on to_char(this_week.time, 'd').day = this_year_dly_avg.
group by time
;
You can use analytic function for this.
select date1, to_char(date1, 'd'),
sum(val) over(partition by to_char(date1, 'd')),
avg(val) over(partition by to_char(date1, 'd')),
sum(val) over(partition by to_char(date1, 'd'))-
avg(val) over(partition by to_char(date1, 'd'))
from table1
time > add_month(sysdate,-12);
This will give you daily counts for the last year:
SELECT TRUNC(time, 'DD') AS date,
SUM(count) AS asset_sum
FROM yourtable
WHERE time > SYSDATE - 365
GROUP BY TRUNC(time, 'DD')
You can modify it to additionally return averages per day of the week for the specified range:
SELECT TRUNC(time, 'DD') AS date,
SUM(count) AS asset_sum,
AVG(SUM(count)) OVER
(PARTITION BY TO_CHAR(TRUNC(time, 'DD'), 'D')) AS asset_sum_avg
FROM yourtable
WHERE time > SYSDATE - 365
GROUP BY TRUNC(time, 'DD')
At this point you have all the initial data you need but probably for more days than necessary. You can use the above query as a derived table to limit the rows to just those where date > SYSDATE - x:
WITH last_year_by_day AS
(
SELECT TRUNC(time, 'DD') AS date,
SUM(count) AS asset_sum,
AVG(SUM(count)) OVER
(PARTITION BY TO_CHAR(TRUNC(time, 'DD'), 'D')) AS asset_sum_avg
FROM yourtable
WHERE time > SYSDATE - 365
GROUP BY TRUNC(time, 'DD')
)
SELECT date,
TO_CHAR(TRUNC(time, 'DD'), 'D') AS day_of_week,
asset_sum,
asset_sum_avg,
asset_sum - asset_sum_avg AS asset_sum_diff
FROM last_year_by_day
WHERE date > SYSDATE - x
;
As some expressions are being repeated multiple times, it can be a good idea to re-factor the query to avoid the repetition. Here's one way:
WITH last_year AS
(
SELECT TRUNC(time, 'DD') AS date,
TO_CHAR(time, 'D') AS day_of_week,
count
FROM yourtable
WHERE time > SYSDATE - 365
),
last_year_by_day AS
(
SELECT date,
day_of_week,
SUM(count) AS asset_sum,
AVG(SUM(count)) OVER (PARTITION BY day_of_week) AS asset_sum_avg
FROM last_year
GROUP BY date, day_of_week
)
SELECT date,
day_of_week,
asset_sum,
asset_sum_avg,
asset_sum - asset_sum_avg AS asset_sum_diff
FROM last_year_by_day
WHERE date > SYSDATE - x
;
One last note is about TO_CHAR('D'), which is used to obtain the day_of_week values. Since you are using a different method for the same results, you may not be aware that the results of TO_CHAR('D') are affected by the NLS_TERRITORY setting. You may want to use an ALTER SESSION statement to set NLS_TERRITORY to the value that would cause TO_CHAR('D') to return 1 for Monday, 2 for Tuesday etc. Here is the list of territories supported.