Using PostgresQL 9.6, for a certain period of time, I have three input values:
"endDate": any date, being the end date
"months": a number of months between 0 and ca. 30
"days": a number of days between 0 and 29
The task is: Find the start date of that period. Requirement is: The result of age("end","start") (*) must always be the same as the input interval (month + days). In other words: Make the period storable with just start and end without explicitly saving the given interval, but making sure that the interval remains exactly the same.
My first simple attempt was
SELECT "endDate" - ("months" || ' mons ' || "days" ' days')::interval
However, that doesn't work for input
"endDate": 2017-06-29
"months": 4
"days": 19
The start date calculated by this approach will be 2017-02-09. And age('2017-06-29','2017-02-10') will return 4 mons 20 days which is one day too much.
The probable reason is that the minus operator most likely will first subtract the month, then if it lands on an "impossible date" like 2017-02-29, go to the previous "possible" date (here: 2017-02-28) and then subtracts the days, landing on 2017-02-09 - which is wrong here.
So, I came up with this idea:
WITH prep AS
( SELECT ("months"||' mons')::interval AS moninterval,
("days" || ' days')::interval AS dayinterval )
SELECT
CASE WHEN date_part('day',"endDate") > "days"
THEN (("endDate" - dayinterval) - moninterval)::date
ELSE ("endDate" - ( moninterval + dayinterval) )::date
END AS simstart
FROM prep
Basically, the idea is: If the number of days in the end date is bigger than the number of days in the interval, then first subtract the days, then the months. Otherwise, do as before.
That works for many cases. However, I still found an edge case where it doesn't:
"endDate": 2018-03-02
"months": 0
"days": 28
Regardless of the method used: The start date will always be calculated as 2018-02-02. And if you do SELECT age_forward('2018-03-02','2018-02-02') you will always end up with 1 mon - which is technically correct. However, it's wrong here, since the original input was 28 days.
(*) To be more precise: age_forward. See my answer to my own question here https://stackoverflow.com/a/51173709/2710714 However, I think it doesn't matter with the edge case problems described above.
Related
I am not a SQL user, it happens that I need to know what this type of SQL code means, can you help me please translating it to me?
CASE
WHEN DATEDIFF(to_date(B.DT_CAREPACK_END),
LAST_DAY(date_sub(add_months(current_date, 3), 20))) > 0
CASE statements let you introduce some conditional logic.
A full CASE sttatement would have a syntax like:
CASE
WHEN some logic is true THEN x
WHEN some other logic is true THEN y
ELSE THEN z
END as column_title
In your example, it doesn't look like you've provided the full statement as their is no END keyword.
Basically, this logic is checking when the difference between two dates (date-x and date-y) is positive or not. DATE_DIFF looks at the different between a start date (date-x) and an end date (date-y).
If date-x, the start date, is before date-y, the end date, then the result is positive. If the start date is after the end date, then the result is negative.
date-x is a date representation of the column DT_CAREPACK_END
date-y is taking the current_date, adding on 3 months (e.g. 4th September becomes 4th December), is is then subtracting 20 units (presumably days) and then setting that date to the last date of that month.
So, imagine DT_CAREPACK_END (presumably a date when something ends) is in the future and is 2022-10-02.
The inner logic here will take the current date (2022-09-04) and add 3 months to that date, making it 2022-12-04. Then, we are subtracting 20 days which is 2022-11-14. Then, we find the last day in that month, which would be 2022-11-30.
Finally, we look at the difference between 2022-10-02 (start date) and 2022-11-30 (end date). If that is a positive number, then the logic is satisfied. In this case, 2nd October is before 30th November, resulting in a positive logic and therefore the case logic is satisfied.
If the DT_CAREPACK_END is before the current_date logic, then it would be negative.
*N.B. I thought that date_add, date_sub and date_diff functions needed an interval unit to be explicitly stated (e.g. INTERVAL 20 DAY). I'm guessing the default here is days but that's an assumption on my part. I'm working in good-faith that the code snip is syntatically correct. *
Resources:-
Add Months:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions004.htm
Date Sub:
https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/date-and-time-functions.html#function_date-add
Last Day:
https://docs.oracle.com/cd/E17952_01/mysql-5.7-en/date-and-time-functions.html#function_last-day
Background
I've been working on some reporting views that get a multi-day work shift and are supposed to do some calculations based on data, but I'm a bit stuck here.
A typical shift is either 3 calendar days usually 1 half-day and two full days, or a whole week consisting of 2 half-days (end and start) and 5 full days.
Specifications
I have the following specifications for what is a full day and half-day. These rules are based on regulation and can't be changed.
2 half-days != 1 full-day, the 2 halves is more "valuable"
Given a started_at iso datetime and end_at iso datetime
I want to get two numbers, full_days, and half_days
A half day is
A day at the start of the range starting at or after 12.00
A day at the end of the range which ends before 19.00
A full day is
A day within the range (traditional 24hours)
A day at the start of the range starting before 12.00
A day at the end of the range which ends at or after 19.00
I'm thinking either a row per full-day and half-day or an aggregated row with half_days and full_days as two separate columns would be ideal in the view to connect it with my other views.
Simplified model
I simplified the data model to leave out unnecessary columns.
create table if not exists [trip]
(
trip_id integer
constraint trip_pk
primary key,
started_at text default (datetime('now')),
end_at text default (datetime('now'))
);
And I'm a bit stuck with how I should design this query. A simple time delta doesn't work.
SQLFiddle with sample data and answers: http://sqlfiddle.com/#!5/de7551/2
You can solve this with a CTE which calculates the day span (number of days the shift spans). Since half days are always 1, 2 or 0 (only occur on end and start) we don't actually need to consider each day by itself.
You can use julianday to get the day as a number, however julian days start at noon so you'll need to subtract 0.5 to get the "actual" day for your calculation. Floor the ending day to avoid a to long span if the end time is later then the start time on each respective day, and round up the result to include partial days as a spanned day.
At this point we can calculate number of half days by checking the end and start. To get the number of full days we simply subtract the half days from the result.
with trip_spans as (
select
ceil(julianday(end_at)-0.5 - floor(julianday(started_at)-0.5)) day_span
, t.*
, (
iif(time(started_at) > time('12:00'), 1, 0)
+
iif(time(end_at) <= time('19:00'), 1, 0)
) half_days
from trip t
)
select
trip_spans.*
, day_span-half_days full_days
from trip_spans
I am running the below query to get data recorded in the past 24 hours. I need the same data recorded starting midnight (DATE > 12:00 AM) and also data recorded starting beginning of the month. Not sure if using between will work or if there is better option. Any suggestions.
SELECT COUNT(NUM)
FROM TABLE
WHERE
STATUS = 'CNLD'
AND
TRUNC(TO_DATE('1970-01-01','YYYY-MM-DD') + OPEN_DATE/86400) = trunc(sysdate)
Output (Just need Count). OPEN_DATE Data Type is NUMBER. the output below displays count in last 24 hours. I need the count beginning midnight and another count starting beginning of the month.
The query you've shown will get the count of rows where OPEN_DATE is an 'epoch date' number representing time after midnight this morning*. The condition:
TRUNC(TO_DATE('1970-01-01','YYYY-MM-DD') + OPEN_DATE/86400) = trunc(sysdate)
requires every OPEN_DATE value in your table (or at least all those for CNLD rows) to be converted from a number to an actual date, which is going to be doing a lot more work than necessary, and would stop a standard index against that column being used. It could be rewritten as:
OPEN_DATE >= (trunc(sysdate) - date '1970-01-01') * 86400
which converts midnight this morning to its epoch equivalent, once, and compares all the numbers against that value; using an index if there is one and the optimiser thinks it's appropriate.
To get everything since the start of the month you could just change the default behaviour of trunc(), which is to truncate to the 'DD' element, to truncate to the start of the month instead:
OPEN_DATE >= (trunc(sysdate, 'MM') - date '1970-01-01') * 86400
And the the last 24 hours, subtract a day from the current time instead of truncating it:
OPEN_DATE >= ((sysdate - 1) - date '1970-01-01') * 86400
db<>fiddle with some made-up data to get 72 back for today, more for the last 24 hours, and more still for the whole month.
Based on your current query I'm assuming there won't be any future-dated values, so you don't need to worry about an upper bound for any of these.
*Ignoring leap seconds...
It sounds like you have a column that is of data type TIMESTAMP and you only want to select rows where that TIMESTAMP indicates that it is today's date? And as a related problem, you want to find those that are the current month, based on some system values like CURRENT TIMESTAMP and CURRENT DATE? If so, let's call your column TRANSACTION_TIMESTAMP instead of (reserved word) DATE. Your first query could be:
SELECT COUNT(NUM)
FROM TABLE
WHERE
STATUS = 'CLND'
AND
DATE(TRANSACTION_TIMESTAMP)=CURRENT DATE
The second example of finding all for the current month up to today's date could be:
SELECT COUNT(NUM)
FROM TABLE
WHERE
STATUS = 'CLND'
AND
YEAR(DATE(TRANSACTION_TIMESTAMP)=YEAR(CURRENT DATE) AND
MONTH(DATE(TRANSACTION_TIMESTAMP)=MONTH(CURRENT DATE) AND
DAY(DATE(TRANSACTION_TIMESTAMP)<=DAY(CURRENT DATE)
I'm try to write Oracle SQL function. Currently i try to get number of working day with cut-off point at 4.30. In problem is, how can i get the number of working day in same day as 0.
Example :
Staff opened request at 9.30 AM
Staff close the request at 4.15 (which is before cut off time)
So, the number of working day for example above should be 0 because it before cut-off time.
You could set the current day at 16.30 as a reference date and then calculate the difference between that date and the request date:
SELECT TRUNC(TO_DATE(TO_CHAR(SYSDATE,'DD-MM-RRRR') || ' 16:30','DD-MM-RRRR HH24:MI') - TO_DATE('09-12-2015 09:30','DD-MM-RRRR HH24:MI')) days
FROM DUAL
I have a table in a database with one column containing dates and another one containing scores. Basically, what I want to do is grab the best score in a given week.
Weeks can start on any given day (From Friday to Thursday, for instance), and that is defined by the user.
Here is what I have so far:
SELECT MAX(Series), DATE(DATE(Date, 'weekday 0'), '-7 days') dateStartOfWeek FROM SeriesScores
WHERE Season = '2010-2011'
AND dateStartOfWeek = '2010-08-29'
GROUP BY DateStartOfWeek
Where Series is the column containing the scores and Date is the (badly) named actual date.
The problem with this query is that it works for every day except for the day the week is supposed to be starting on.
For example: 2010-08-29 is a Sunday and in this example, I'm trying to find on which date the Sunday of the given week is. My function works for every day of that week except for 2010-08-29 (Sunday) since it tries to find the next day that is a Sunday (itself in this case). To compensate for that, I go back 7 days to get the correct Sunday, which creates the error for the already correct Sunday since this one doesn't need to go back 7 days or else it is one week off.
I figured I could solve this problem easily using Java, but I want to see how it should be done using SQL instead.
My solution (I don't even know if it can be done), would be to check if date and dateStartOfWeek are the same. If they are, don't substract 7 days from the date. If they're not, do as I did in my example. I don't know how to use conditions such as this one in SQL, though, and this is where I need help.
Thanks a lot in advance!
I think you need to use CASE operator - see http://sqlite.awardspace.info/syntax/sqlitepg09.htm
EDIT - try:
SELECT MAX(Series), CASE WHEN STRFTIME ( '%w', Date ) = 0 THEN DATE(Date, 'weekday 0') ELSE DATE(DATE(Date, 'weekday 0'), '-7 days') END AS dateStartOfWeek FROM SeriesScores
WHERE Season = '2010-2011'
AND dateStartOfWeek = '2010-08-29'
GROUP BY DateStartOfWeek
see http://www.sqlite.org/lang_datefunc.html