SQLite query for n-th day of month - sql

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

Related

Calculate the end day of a week with DATE_TRUNC in PostgreSQL

Is there a way to change the showing date to the end of each week instead of the beginning of the week.
Here's my code:
SELECT date_trunc('week', day + '1 day'::interval)::date - '1 day'::interval AS anchor, AVG(value) AS average
FROM daily_metrics
WHERE metric = 'daily-active-users'
GROUP BY anchor
ORDER BY anchor
And the result is as below:
What I want to achieve is to make it 2018-03-03 (the end of the self defined week) instead of 2018-02-25 (the beginning of the self defined week), 2018-03-10 instead of 2018-03-04...
Your trick with shifting back and forth by one day works just fine to get the start of your custom week. You get the end or your custom week (Saturday) by adding 5 instead of subtracting 1:
SELECT date_trunc('week', day + interval '1 day')::date + 5 AS anchor ...
Adding an integer to the date, signifying days.
Related:
PostgreSQL custom week number - first week containing Feb 1st
Simply try
SELECT date_trunc('week', day::DATE + 1)::date + 5 AS anchor, AVG(value) AS average
FROM daily_metrics
WHERE metric = 'daily-active-users'
GROUP BY anchor
ORDER BY anchor
When a date is the start date of a week adding 6 (1 + 5) days will move the date to the last date of the week. The the addition of one is to move sundays to the following week and the 5 to get the end of the week from the start date.
Note, PostgreSQL allows the addition of integers (= days) to dates.

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.

Adding months to date: PostgreSQL vs. Oracle

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.

SQL Count days till first of the month

How would I count the days from a date till the first of the following month
Example:
--Start Date
07-07-2011
How many days till:
-- The 1st of the succeeding month of the start date above
08-01-2011
Expected Result (in days):
25
So if I counted the day I get 25, so running this query gets me the desired timestamp:
SELECT CURRENT_DATE + INTERVAL '25 DAYS'
Results:
2011-08-01 00:00:00
just can't think of a way to get the number of days, any suggestions?
Or start date, end date, number of days between?
I don't have a PostgreSQL server handy, so this is untested, but I would try:
SELECT (DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 MONTH') - CURRENT_DATE

Group SQL results by week and specify "week-ending" day

I'm trying to select data grouped by week, which I have working, but I need to be able to specify a different day as the last day of the week. I think something needs to go near INTERVAL (6-weekday('datetime')) but not sure. This kind of SQL is above my pay-grade ($0) :P
SELECT
sum(`value`) AS `sum`,
DATE(adddate(`datetime`, INTERVAL (6-weekday(`datetime`)) DAY)) AS `dt`
FROM `values`
WHERE id = '123' AND DATETIME BETWEEN '2010-04-22' AND '2010-10-22'
GROUP BY `dt`
ORDER BY `datetime`
Thanks!
select
sum(value) as sum,
CASE WHEN (weekday(datetime)<=3) THEN date(datetime + INTERVAL (3-weekday(datetime)) DAY)
ELSE date(datetime + INTERVAL (3+7-weekday(datetime)) DAY)
END as dt
FROM values
WHERE id = '123' and DATETIME between '2010-04-22' AND '2010-10-22'
GROUP BY dt
ORDER BY datetime
This does look pretty evil but, this query will provide you with a sum of value grouped by a week ending on a Thursday (weekday() return of 3).
If you wish to change what day the end of the week is you just need to replace the 3's in the case statement, ie if you wanted Tuesday you would have it say
CASE WHEN (weekday(datetime)<=1) THEN date(datetime + INTERVAL (1-weekday(datetime)) DAY)
ELSE date(datetime + INTERVAL (1+7-weekday(datetime)) DAY)
I hope this helps.
Simple solution that I like. This will return the date for the start of the week assuming the week ends Sunday and starts Monday.
DATE(`datetime`) - INTERVAL WEEKDAY(`datetime`) AS `dt`
This can easily be adjusted to have a week ending on Thursday because Thursday is 3 days earlier than Sunday
DATE(`datetime`) - INTERVAL WEEKDAY(`datetime` + INTERVAL 3 DAY) AS `dt`
this returns for the start of the week that starts on Friday and ends on Thursday.
You can group on this no problem. If you want to use get the end of the week based on the start you do this
DATE(`datetime`) - INTERVAL -6 + WEEKDAY(`datetime` + INTERVAL 3 DAY) AS `dt`
I think you must choose between Sunday and Monday? When you can use DATE_FORMAT for grouping by string format of date, and use %v for grouping by Mondays and %v for grouping by Sundays.
SELECT
sum(`value`) AS `sum`,
DATE_FORMAT(`datetime`,'%v.%m.%Y') AS `dt`
FROM `values`
WHERE id = '123' AND DATETIME BETWEEN '2010-04-22' AND '2010-10-22'
GROUP BY DATE_FORMAT(`datetime`,'%v.%m.%Y')
ORDER BY `datetime`
How to use DATE_FORMAT
I don't remember the exact math, but you can get WEEKDAY to wrap around on different days of the week by adding or subtracting days to its argument. You'll need to tinker with different values of x and y in the expression:
x-weekday(adddate(`datetime`, INTERVAL y DAY))