Calculate the days to reach a certain date - PostgreSQL - sql

I need to create a query to calculate the difference in days until a date reach another date. Something like the "how many days until my birthday".
Current_date | Reach_date
2000-01-01 | 2015-01-03
-- Should Return: 2
2015-03-01 | 2021-03-05
-- Should Return: 4
The most similar built-in function I found to solve this problem, was using "age()", but it returns me "year, month and days":
select age(current_date,reach_date) from sample_table;
age
-------------------------
3 years 10 mons 1 day
I also tried to use "extract()" trying to get the difference in days, but it just returns me the part of the age function of the days. At my last sample, instead of it returns me more than 1000 days, it returns me just 1.

Try if this works for you. It checks where it's a leap year to calculate the difference correctly, and then uses different logic to calculate the difference between the dates depending on whether the dates are in the same year or not.
with cte as
(
SELECT *,
CASE WHEN extract(year from CurrentDate)::INT % 4 = 0
and (extract(year from CurrentDate)::INT % 100 <> 0
or extract(year from CurrentDate)::INT % 400 = 0)
THEN TRUE
ELSE FALSE
END AS isLeapYear,
Extract(day from (Reach_date - CurrentDate)) AS diff_in_days
FRoM test
)
SELECT CurrentDate,
Reach_date,
CASE WHEN isLeapYear
THEN
CASE WHEN diff_in_days < 366
THEN diff_in_days
ELSE Extract(day from AGE(Reach_date, CurrentDate))
END
ELSE CASE WHEN diff_in_days < 365
THEN diff_in_days
ELSE Extract(day from AGE(Reach_date, CurrentDate))
END
END AS diff
FROM cte
Test here: SQL Fiddle

SELECT
d_date,
'2021-01-01'::date - '2020-01-01'::date AS diff_2021_minus_2020,
CASE WHEN (date_part('month', d_date)::integer) = 1
AND (date_part('day', d_date)::integer) = 1 THEN
(date_trunc('year', d_date) + interval '1 year')::date - date_trunc('year', d_date)::date
WHEN ((d_date - (date_trunc('year', d_date))::date)) <= 182 THEN
(d_date - (date_trunc('year', d_date))::date)
ELSE
365 - (d_date - (date_trunc('year', d_date))::date)
END AS till_to_birthday
FROM (
VALUES ('2021-12-01'::date),
('2021-06-01'::date),
('2020-01-01'::date),
('2021-01-01'::date),
('2021-09-01'::date),
('2021-11-01'::date),
('2020-06-01'::date)) s (d_date);
returns:
+------------+----------------------+------------------+
| d_date | diff_2021_minus_2020 | till_to_birthday |
+------------+----------------------+------------------+
| 2021-12-01 | 366 | 31 |
| 2021-06-01 | 366 | 151 |
| 2020-01-01 | 366 | 366 |
| 2021-01-01 | 366 | 365 |
| 2021-09-01 | 366 | 122 |
| 2021-11-01 | 366 | 61 |
| 2020-06-01 | 366 | 152 |
+------------+----------------------+------------------+

The behavior that you've got with using age() is because extract() only extract the amount of days but it won't convert months and years into days for you before extraction.
On a SQL Server you could use DATEDIFF() but in Postgre you have to compute it yourself by substracting dates, as shown in this answer.
There's also few examples with all the time units here.

Related

Extract 30 minutes from timestamp and group it by 30 mins time interval -PGSQL

In PostgreSQL I am extracting hour from the timestamp using below query.
select count(*) as logged_users, EXTRACT(hour from login_time::timestamp) as Hour
from loginhistory
where login_time::date = '2021-04-21'
group by Hour order by Hour;
And the output is as follows
logged_users | hour
--------------+------
27 | 7
82 | 8
229 | 9
1620 | 10
1264 | 11
1990 | 12
1027 | 13
1273 | 14
1794 | 15
1733 | 16
878 | 17
126 | 18
21 | 19
5 | 20
3 | 21
1 | 22
I want the same output for same SQL for 30 mins. Please suggest
SELECT to_timestamp((extract(epoch FROM login_time::timestamp)::bigint / 1800) * 1800)::timestamp AS interval_30_min
, count(*) AS logged_users
FROM loginhistory
WHERE login_time::date = '2021-04-21' -- inefficient!
GROUP BY 1
ORDER BY 1;
Extracting the epoch gets the number of seconds since the epoch. Integer division truncates. Multiplying back effectively rounds down, achieving the same as date_trunc() for arbitrary time intervals.
1800 because 30 minutes contain 1800 seconds.
Detailed explanation:
Truncate timestamp to arbitrary intervals
The cast to timestamp makes me wonder about the actual data type of login_time? If it's timestamptz, the cast depends on your current time zone setting and sets you up for surprises if that setting changes. See:
How do I match an entire day to a datetime field?
Subtract hours from the now() function
Ignoring time zones altogether in Rails and PostgreSQL
Depending on the actual data type, and exact definition of your date boundaries, there is a more efficient way to phrase your WHERE clause.
You can change the column on which you're aggregating to use the minute too:
select
count(*) as logged_users,
CONCAT(EXTRACT(hour from login_time::timestamp), '-', CASE WHEN EXTRACT(minute from login_time::timestamp) < 30 THEN 0 ELSE 30 END) as HalfHour
from loginhistory
where login_time::date = '2021-04-21'
group by HalfHour
order by HalfHour;

Review scripts which are older than 3 months and in the last 30 days

I'm try to run a query that will allow me to see where we have scripts running that are older than 3 months old over the last 30 days delivery, so we know they need to be updated.
I have been able to build the query to show me all the scripts and their last regen dates (with specific dates put in) but can't work out;
How to look at only the last 30 days data.
How to see only the scripts where the date_regen column is older than 3 months from today's date - From the last 30 days data that I'm reviewing.
EXAMPLE TABLE
visit_datetime | client | script | date_regen |
2019/10/04 03:32:51 | 1 | script1 | 2019-09-17 13:12:01 |
2019/09/27 03:32:52 | 2 | script2 | 2019-07-18 09:44:02 |
2019/10/06 03:32:50 | 3 | script3 | 2019-03-18 14:08:02 |
2019/10/02 06:28:24 | 4 | script6 | 2019-09-11 10:02:01 |
2019/03/01 06:28:24 | 5 | script7 | 2019-02-11 10:02:01 |
The below examples haven't been able to get me what I need. My idea was that I would get the current date (using now()) and then knowing that, look at all data in the last 30 days.
After that I would then WHERE month,-3 (so date_regen 3 months+ old from the current date.
However I can't get it to work. I also looked at trying to do -days but that also had no success.
-- WHERE MONTH = MONTH(now()) AND YEAR = YEAR(now())
-- WHERE date_regen <= DATEADD(MONTH,-3,GETDATE())
-- WHERE DATEDIFF(MONTH, date_regen, GetDate()) >= 3
Code I am currently using to get the table
SELECT split_part(js,'#',1) AS script,
date_regen,
client
FROM table
WHERE YEAR=2019 AND MONTH=10 AND DAY = 01 (This where is irrelevant as I would need to use now() but I don't know what replaces "YEAR/MONTH/DAY ="
GROUP BY script,date_regen,client
ORDER BY client DESC;
END GOAL
I should only see client 3 as clients 1+2+4 have tags where the date_regen is in the last 3 months, and client 5 has a visit_datetime out of the 30 limit.
visit_datetime | client | script | date_regen |
2019/10/06 03:32:50 | 3 | script3 | 2019-03-18 14:08:02 |
I think you want simple filtering:
select t.*
from t
where visit_datetime >= current_timestamp - interval 30 day and
date_regen < add_months(current_timestamp, -3)

Group timestamped record by 5, 10, 15 minutes block

I have minute to minute financial records stored in similar format in my table,
dt | open | high | low | close | vol
---------------------+----------+----------+----------+----------+-------
2018-05-04 15:30:00 | 171.0000 | 171.3000 | 170.9000 | 171.0000 | 42817
2018-05-04 15:29:00 | 170.8000 | 171.0000 | 170.8000 | 170.9500 | 32801
2018-05-04 15:28:00 | 170.8500 | 171.0000 | 170.8000 | 170.8000 | 22991
2018-05-04 15:27:00 | 170.8500 | 170.8500 | 170.7500 | 170.8000 | 40283
2018-05-04 15:26:00 | 170.9500 | 171.0000 | 170.8000 | 170.8500 | 46636
and so on.
I want to group them into blocks of 5 minutes, 10 minutes, 60 minutes just like candlesticks. Using date_trunc('hour', dt) is not possible as I want to group them as block of last 60 minutes, last 15 minutes etc.
I am using PostgreSQL.
You should use a GROUP BY with :
floor(extract('epoch' from dt) / 300)
to have your data grouped in 5 minutes intervals. 300 is the number of seconds in 5 minutes. Thus if you want 10 minutes, you'd divide by 600. If you want 1 hour, by 3600.
If you want your interval to begin at 00 05 10, use floor(). If you want them to finish at 00, 05, 10, use ceil()
In the SELECT clause, you should re-transform the Unix epoch used in the GROUP BY into a timestamp using
to_timestamp(floor((extract('epoch' from dt) / 300)) * 300) as ts
Its not clear if you want all the "block" results in the same query, I assumed yes if you want a candlestick graph. I have also logically deduced the right aggregate function (MIN, MAX, AVG, SUM) for each column, following their names . You might have to adapt this.
Here we go :
SELECT '5 minutes' as block,
to_timestamp(floor((extract('epoch' from dt) / 300)) * 300) as ts,
round(AVG(open),4) as avg_open,
round(MAX(high),4) as max_high,
round(MIN(low),4) as min_low,
round(AVG(close),4) as avg_close,
SUM(vol) as sum_vol
FROM mytable
GROUP BY floor(extract('epoch' from dt) / 300)
UNION ALL
SELECT '10 minutes' as block,
to_timestamp(floor((extract('epoch' from dt) / 600)) * 600) as ts,
round(AVG(open),4) as avg_open,
round(MAX(high),4) as max_high,
round(MIN(low),4) as min_low,
round(AVG(close),4) as avg_close,
SUM(vol) as sum_vol
FROM mytable
GROUP BY floor(extract('epoch' from dt) / 600)
UNION ALL
SELECT '1 hour' as block,
to_timestamp(floor((extract('epoch' from dt) / 3600)) * 3600) as ts,
round(AVG(open),4) as avg_open,
round(MAX(high),4) as max_high,
round(MIN(low),4) as min_low,
round(AVG(close),4) as avg_close,
SUM(vol) as sum_vol
FROM mytable
GROUP BY floor(extract('epoch' from dt) / 3600)
Results:
block ts avg_open max_high min_low avg_close sum_vol
5 minutes 04.05.2018 17:30:00 171 171,3 170,9 171 42817
5 minutes 04.05.2018 17:25:00 170,8625 171 170,75 170,85 142711
10 minutes 04.05.2018 17:20:00 170,8625 171 170,75 170,85 142711
10 minutes 04.05.2018 17:30:00 171 171,3 170,9 171 42817
1 hour 04.05.2018 17:00:00 170,89 171,3 170,75 170,88 185528
Test it on REXTESTER
You can use generate_series() to create any range you want
SQL DEMO:
SELECT dd as start_range, dd + '30 min'::interval as end_range
FROM generate_series
( '2018-05-05'::timestamp
, '2018-05-06'::timestamp
, '30 min'::interval) dd
;
Then check if your record fall on that range.

How can I get a table of counts grouped by month on axis x and by hour on axis y in PostgreSQL?

There is a log table with a lot of events, I would like to know what is statistical data, i.e. at what hour each month how many events happened.
Data sample:
date_create | event
---------------------+---------------------------
2018-03-01 18:00:00 | Something happened
2018-03-05 18:15:00 | Something else happened
2018-03-06 19:00:00 | Something happened again
2018-04-01 18:00:00 | and again
The result should look like this:
hour | 03 | 04
------+----+----
18 | 2 | 1
19 | 1 | 0
I can make it with CTE, but then it is significant manual work each time. My guess would be that it can be made with funciton, but probably it is already there.
You can use aggregation. I'm thinking:
select extract(hour from date_create) as hh,
sum(case when extract(month from date_create) = 3 then 1 else 0 end) as month_03,
sum(case when extract(month from date_create) = 4 then 1 else 0 end) as month_04
from t
group by hh
order by hh;

Date Functions Conversions in SQL

Is there a way using TSQL to convert an integer to year, month and days
for e.g. 365 converts to 1year 0 months and 0 days
366 converts to 1year 0 months and 1 day
20 converts to 0 year 0 months and 20 days
200 converts to 0 year 13 months and 9 days
408 converts to 1 year 3 months and 7 days .. etc
I don't know of any inbuilt way in SQL Server 2008, but the following logic will give you all the pieces you need to concatenate the items together:
select
n
, year(dateadd(day,n,0))-1900 y
, month(dateadd(day,n,0))-1 m
, day(dateadd(day,n,0))-1 d
from (
select 365 n union all
select 366 n union all
select 20 n union all
select 200 n union all
select 408 n
) d
| n | y | m | d |
|-------|---|---|----|
| 365 | 1 | 0 | 0 |
| 366 | 1 | 0 | 1 |
| 20 | 0 | 0 | 20 |
| 200 | 0 | 6 | 19 |
| 408 | 1 | 1 | 12 |
Note that zero used in in the DATEDADD function is the date 1900-01-01, hence 1900 is deducted from the year calculation.
Thanks to Martin Smith for correcting my assumption about the leap year.
You could try without using any functions just by dividing integer values if we consider all months are 30 days:
DECLARE #days INT;
SET #days = 365;
SELECT [Years] = #days / 365,
[Months] = (#days % 365) / 30,
[Days] = (#days % 365) % 30;
#days = 365
Years Months Days
1 0 0
#days = 20
Years Months Days
0 0 20