Adding months to date: PostgreSQL vs. Oracle - sql

PostgreSQL and Oracle behaviour in adding/subtracting months to/from date differs.
Basically, if we add 1 month to some day, which is not the last one of the month, they'll both return the same day number in the resulting month (or the last one for the resulting month if the day number we are adding to is greater, e.g. 28th of February when adding to 31th of January).
PostgreSQL:
# select '2015-01-12'::timestamptz + '1 month'::interval;
date
------------------------
2015-02-12 00:00:00+03
Oracle:
> select add_months('12-JAN-2015',1) from dual;
ADD_MONTH
---------
12-FEB-15
However.
If the day we are adding to is the last day of the month, Oracle will return the last day of the resulting month, even if it's bigger, and PostgreSQL will still return the same day number (or the lower one if the resulting month is shorter). This can lead to some inconsistency (even funny!), especially with adding/subtracting multiple times and even when grouping operations - in PostgreSQL the result differs:
Oracle:
> select add_months('28-FEB-2015',1) from dual;
ADD_MONTH
---------
31-MAR-15
> select add_months('31-JAN-2015',4) from dual;
ADD_MONTH
---------
31-MAY-15
> select add_months(add_months(add_months(add_months('31-JAN-2015',1),1),1),1) from dual;
ADD_MONTH
---------
31-MAY-15
PostgreSQL:
-- Adding 4 months at once:
# select '2015-01-31'::timestamptz + '4
months'::interval;
date
-------------------------------
2015-05-31 00:00:00+03
-- Adding 4 months by one:
# select '2015-01-31'::timestamptz + '1
months'::interval + '1 months'::interval + '1 months'::interval +'1
months'::interval;
date
-------------------------------
2015-05-28 00:00:00+03
-- Adding 4 months by one with grouping operations:
# select '2015-01-31'::timestamptz + ('1
months'::interval + '1 months'::interval) + '1 months'::interval +'1
months'::interval;
date
-------------------------------
2015-05-30 00:00:00+03
-- And even adding 4 months and then subtracting them does not return the initial date!
# select '2015-01-31'::timestamptz + '1 months'::interval + '1
months'::interval + '1 months'::interval +'1 months'::interval - '4 months'::interval;
date
------------------------
2015-01-28 00:00:00+03
I know I could always use something like
SELECT (date_trunc('MONTH', now())+'1 month'::interval - '1 day'::interval);
to get the last day of month and use it when adding months in PostgreSQL, but
the question is: why both of them chose to implement different standards, which one is better/worse and why.

Oracle specify that
If date is the last day of the month or if the resulting month has
fewer days than the day component of date, then the result is the last
day of the resulting month. Otherwise, the result has the same day
component as date.
PostgreSQL specify that
Note there can be ambiguity in the months returned by age because
different months have a different number of days. PostgreSQL's
approach uses the month from the earlier of the two dates when
calculating partial months. For example, age('2004-06-01',
'2004-04-30') uses April to yield 1 mon 1 day, while using May would
yield 1 mon 2 days because May has 31 days, while April has only 30.
You might want to have a look at the justify_days(interval) function provided by PostgreSQL.
why both of them chose to implement different standards, which one is
better/worse and why ?
None of them is better then the other (it is mostly opinion based), simply different. As of why they decided to implement different standards, honestly I don't think there really is a reason, probably just a matter of facts.

Related

PostgreSQL query to return the set of days between two dates

I need to return the dates between the 05 of the last month and the 05 of the current month Example today is the 16/08/2022 I recuperate therefore the whole of the days between the 05/07/2022 and the 05/08/2022
For the moment I try with this query
SELECT DATE from Db_name where date between date_trunc('month', current_date-1) and date_trunc('month',current_date)
Your initial query is very much on track. Just a couple additional things about dates:
current_date-1 subtracts 1 day, you need to subtract 1 month. Thus current_date - interval '1 month'.
date_trunc(somedate) returns the 1st of the month so
date_trunc('2022-08-17') returns 2022-08-01.
getting to the 5th of the month from the 1st just add interval '4 days'.
adding or subtracting intervals to dates results in timestamps.
Since you want dates so needs to be cast back to date.
select *
from db_name
where some_date between (date_trunc('month', current_date-interval '1 month') + interval '4 days')::date
and (date_trunc('month', current_date) + interval '4 days')::date;
But I will repeat do not any reserved word or data type as a name. At best is causes confusion, at worst it will run but do the wrong thing.

Get average for "last month" only

Pretty new to SQL and have hit a roadblock.
I have this query, which works fine:
SELECT
(COUNT(*)::float / (current_date - '2017-05-17'::date)) AS "avg_per_day"
FROM "table" tb;
I now want it to include only data from the last month, not all time.
I've tried doing something along the lines of:
SELECT
(COUNT(*)::float / (current_date - (current_date - '1 month' ::date)) AS "avg_per_day"
FROM "table" tb;
The syntax is clearly wrong, but I am not sure what the right answer is. Have googled around and tried various options to no avail.
I can't use a simple AVG because the number I require is an AVG per day for the last month of data. Thus I've done a count of rows divided by the number of days since the first occurrence to get my AVG per day.
I have a column which tells me the date of the occurrence, however there are multiple rows with the same date in the dataset. e.g.
created_at
----------------------------
Monday 27th June 2017 12:00
Monday 27th June 2017 13:00
Tuesday 28th June 2017 12:00
and so on.
I am counting the number of occurrences per day and then need to work out an average from that, for the last month of results only (they date back to May).
The answer depends on the exact definition of "last month" and the exact definition of "average count".
Assuming:
Your column is defined created_at timestamptz NOT NULL
You want the average number of rows per day - days without any rows count as 0.
Cover 30 days exactly, excluding today.
SELECT round(count(*)::numeric / 30, 2) -- simple now with a fixed number of days
FROM tbl
WHERE created_at >= (now()::date - 30)
AND created_at < now()::date -- excl. today
Rounding is optional, but you need numeric instead of float to use round() this way.
Not including the current day ("today"), which is ongoing and may result in a lower, misleading average.
If "last month" is supposed to mean something else, you need to define it exactly. Months have between 28 and 31 days, this can mean various things. And since you obviously operate with timestamp or timestamptz, not date, you also need to be aware of possible implications of the time of day and the current time zone. The cast to date (or the definition of "day" in general) depends on your current timezone setting while operating with timestamptz.
Related:
Ignoring timezones altogether in Rails and PostgreSQL
Select today's (since midnight) timestamps only
Subtract hours from the now() function
I think you just need a where clause:
SELECT
(COUNT(*)::float / (current_date - (current_date - '1 month' ::date)) AS "avg_per_day"
FROM "table" tb
WHERE created_at > (current_date - '1 month' ::date)
I believe Postgresql and other RDBMS has AVG() to calculate average.
SELECT AVG(tb.columnName) AS avg_per_month
FROM someTable tb
WHERE
tb.createdDate >= [start date of month] AND
tb.createdDate <= [end date of month]
Edit: I subtract current date with INTERVAL. I am on mobile phone so I cannot test.
SELECT
(COUNT(*)::float / (current_date - ( current_date - INTERVAL '1 month')) AS "avg_per_day"
FROM "table" tb;

SQLite query for n-th day of month

Say, I have the following table:
CREATE TABLE Data (
Id INTEGER PRIMARY KEY,
Value DECIMAL,
Date DATE);
Since the application is finance-related, user may choose, which day would be the first day of the month. For instance, if he receives salary every 10th of the month, he may set the first day of the month to be 10th.
I'd like to create a query, which returns average value for n-th day of month, as defined by user. For instance:
Date | Value
---------------+------
10.01.2016 | 10
11.01.2016 | 15
10.02.2016 | 20
11.03.2016 | 10
Result of the query should be:
Day | Average
----+--------
1 | 15
2 | 12.5
Note, that if user sets first day to 10th, 9th of the month may be 28th, 29th, 30th or 31st day of a month (depending on which month we're talking about). So this is not as simple as extracting day number from the date.
Assuming that the date values do not use the format dd.mm.yyyy but one of the supported date formats, you can use the built-in date functions to compute this.
To compute the difference, in days, between two dates, convert them into a date format that uses days as a number, i.e., Julian days.
To get the 'base' day for a month, we can use modifiers:
> SELECT julianday('2001-02-11') -
julianday('2001-02-11', 'start of month', '+10 days') + 2;
2.0
(The +2 is needed because we add to the 1st of the month, not the 0th, and we count beginning at 1, not 0.)
If the day is before the tenth, the computed value would become zero or negative, and we have to use the previous month instead:
> SELECT julianday('2001-02-09') -
julianday('2001-02-09', 'start of month', '-1 month', '+10 days') + 2;
31.0
Combining these results in this expression to compute the n for a date Date:
CASE
WHEN julianday(Date) -
julianday(Date, 'start of month', '+10 days') + 2 > 0
THEN julianday(Date) -
julianday(Date, 'start of month', '+10 days') + 2
ELSE julianday(Date) -
julianday(Date, 'start of month', '-1 month', '+10 days') + 2
END
You can the use this in your query:
SELECT CASE...END AS Day,
AVG(Value) AS Average
FROM Data
GROUP BY Day;
Seems like I found in parallel a different solution:
SELECT avg(Amount),
strftime("%d", Date, "-9 days") AS day
FROM (
SELECT sum(Amount) AS Amount,
strftime("%Y-%m-%d", Date, "start of day") AS Date
FROM Operations
GROUP BY strftime("%Y-%m-%d", Date, "start of day")
)
GROUP BY day;
Where "-9 days" is for 10th of the month (so first_day-1).

How to get how many days passed since start of this year?

I have a query which uses needs to know how many days passed since 1st of January in the current year.
Which means that if the query runs for example in:
2nd Jan 2017 than it should return 2 (as 2 days passed since 1st Jan
2017).
10th Feb 2016 than it should return 41 (as 41 days passed since 1st
Jan 2016).
basically it needs to take Current Year from Curent Date and count the days since 1/1/(Year).
i have the current year with: SELECT EXTRACT(year FROM CURRENT_DATE);
I created the 1st of Jan with:
select (SELECT EXTRACT(year FROM CURRENT_DATE)::text || '-01-01')::date
How do I get the difference from this date to Current_Date?
Basically this question can be Given two dates, how many days between them?
Something like age(timestamp '2016-01-01', timestamp '2016-06-15') isn't good because I need the result only in days. while age gives in years,months and days.
An easier approach may be to extract the day of year ("doy") field from the date:
db=> SELECT EXTRACT(DOY FROM CURRENT_DATE);
date_part
-----------
41
And if you need it as a number, you could just cast it:
db=> SELECT EXTRACT(DOY FROM CURRENT_DATE)::int;
date_part
-----------
41
Note: The result 41 was produced by running the query today, February 9th.
Given two dates, how many days between them
Just subtract one from the other.
In your case you could just round the current_date to the start of the year and subtract that from the current date:
select current_date - date_trunc('year', current_date)::date
The ::date cast is necessary to get the result as an integer, otherwise the result will be an interval.
Another solution is to use DATEDIFF
SELECT DATE_PART('day', now()::timestamp - '2016-01-01 00:00:00'::timestamp);

Teradata SQL Same Day Prior Year in same Week

Need help figuring out how to determine if the date is the same 'day' as today in teradata. IE, today 12/1/15 Tuesday, same day last year was actually 12/2/2014 Tuesday.
I tried using current_date - INTERVAL'1'Year but it returns 12/1/2014.
You can do this with a bit of math if you can convert your current date's "Day of the week" to a number, and the previous year's "Day of the week" to a number.
In order to do this in Teradata your best bet is to utilize the sys_calendar.calendar table. Specifically the day_of_week column. Although there are other ways to do it.
Furthermore, instead of using CURRENT_DATE - INTERVAL '1' YEAR, it's a good idea to use ADD_MONTHS(CURRENT_DATE, -12) since INTERVAL arithmetic will fail on 2012-02-29 and other Feb 29th leap year dates.
So, putting it together you get what you need with:
SELECT
ADD_MONTHS(CURRENT_DATE, -12)
+
(
(SELECT day_of_week FROM sys_calendar.calendar WHERE calendar_date = CURRENT_DATE)
-
(SELECT day_of_week FROM sys_calendar.calendar WHERE calendar_date = ADD_MONTHS(CURRENT_DATE, -12))
)
This is basically saying: Take the current dates day of week number (3) and subtract from it last years day of week number (2) to get 1. Add that to last year's date and you'll have the same day of the week as current date.
I tested this for all dates between 01/01/2010 and CURRENT_DATE and it worked as expected.
Why don't you simply subtract 52 weeks?
current_date - 364
The SQL below will get you to the abbreviated name for the day of week, it's cumbersome but it works across versions of Teradata.
SELECT CAST(CAST(ADD_MONTHS(CURRENT_DATE, -12) AS DATE FORMAT 'E3') AS CHAR(3)) AS LY_DayOfWeek
, CAST(CAST(CURRENT_DATE) AS DATE FORMAT 'E3') AS CHAR(3)) AS CY_DayOfWeek
Dates are internally represented at integers in Teradata as (Year-1900) * 100000 + (MONTH * 100) + DAY. You may be able to do some creative arithmetic to figure out that 12/1/2015 Tuesday was 12/2/2014 Tuesday last year.