I have a table "Holidays" in my database, that contains a range of days when it's the holidays, defined with two column: start & end.
Holidays (id, name, start, end)
Now, if I have in input, two dates (from & to), I'd like to list all the dates that are not in the holidays.
Suppose the holidays are from 2012/06/05 to 2012/06/20, and I request:
from=2012/06/01, to=2012/06/10 ; The result would be 01, 02, 03, 04
from=2012/06/01, to=2012/06/22 ; The result would be 01, 02, 03, 04, 21, 22
from=2012/06/15, to=2012/06/22 ; The result would be 21, 22
But I can't figure out how to get this list of "opened" days without hitting the database for every days requested in the range from->to.
How could I do that?
There are many solutions, but it pretty much depends on how many entries you have in the database and how many requests you do. If you are making a lot of request, than you can do some thing like this:
-> create a boolean array that will determine if a day is holiday or not;
first element points to some predefined date (e.g. 1.1.2012),
second element to 2.1.2012, etc.
-> initialize an array to 0
-> for each holiday you do
-> make a for loop initialized with holiday start date and
expression for each pass: current date = holiday start date + 1 day
-> covert the current date to index (number of days since start date - 1.1.2012)
-> set the array[index] to 1
Now you should have a simple array containing 0 for non-holiday day and 1 for holiday day
for each query (request) you now do
-> for loop that goes from request start date to request end date
-> convert the current date to index (number of days since 1.1.2012)
-> check if array[index] is 0 or 1
But keep in mid, that this solution is ok for many query (requests). If you have to do the first part for every request, than this solution does not make sense and it is better to write a sql query.
Here's how I finally done that, it seems to work :
SELECT start, end FROM holidays WHERE
(start > :START AND end < :END) OR
(start < :START AND end > :END) OR
(start BETWEEN :START AND :END) OR
(end BETWEEN :START AND :END);
This returns only the rows where my :START/:END dates touch at least one holiday. It covers those possibilities :
start is before the begin of an holiday, and end if before the end of an holiday (before, in)
start is before the begin of an holiday, and end if after the end of an holiday (before,after)
start is after the begin of an holiday, and end if before the end of an holiday (in, in)
start is after the begin of an holiday, and end if after the end of an holiday (in, after)
I think I cover all the possibilities with that.
Then I loop over the result and build an array of dates that goes from start to end, for each rows.
And I finally loop over my initial range date, if one of those date is in the array, I remove them.
Here's a solution that can give you the solution in a single (albeit slightly convoluted) SQL statement (this is Oracle):
with all_days as (
select :start_date + (level - 1) dt
from dual
connect by :start_date + (level - 1) <= :end_date
)
select a.dt
from all_days a
where not exists (
select 1
from holidays h
where h.start_dt <= a.dt and h.end_dt >= a.dt
)
order by a.dt
For example, assuming the following holiday table:
NAME START_DT END_DT
-------------- ------------------------- -------------------------
Test Holiday 1 07-JUN-12 13-JUN-12
Test Holiday 2 17-JUN-12 18-JUN-12
And using 5th June as :start_date and 20th June as :end_date, you'd get the following output:
DT
-------------------------
05-JUN-12
06-JUN-12
14-JUN-12
15-JUN-12
16-JUN-12
19-JUN-12
20-JUN-12
(which provides the dates in a range minus any dates specified in a range in the holiday table).
Hope that helps.
Related
I have a Java system that uses Sequence in PK in database (postgreSQL and PGadmin)
This sequence should take the current month (with two digits) followed by the current year.
It turns out that a certain time of day arrives and the command below is executed (Java project class):
String sql = "select case when trim(substring(cast((SELECT last_value FROM sequencia.Tb_um) as varchar),1,4)) <> trim(to_char(current_date,'MMYY')) then"
+ " setval(‘sequencia.Tb_um', cast(trim(to_char(current_date,'MMYY')||'001') as integer), true) "
+ " else 0 end as valor";
But I don't know what happens when the leading zero of the month is removed, leaving for example: “422001” in the DB (the corret: "0422001"), the question is: what is it that is deleting this zero?
Thanks for any help!
I am working on an android app. It has a database that stores some events(say, when the user presses a button, that special event is stored in the database) and timestamp. I want to know if the user has pressed that button daily at least once for 5 consecutive days.
I came across this stackoverflow answer that tells me to use recursion of SQLite. I tried to use the SQLite Recursion to 'loop for 5 days' but I am getting an error. I don't understand what I am doing wrong. Help.
Here is my code:
WITH RECURSIVE
recursiveDailyEvents (startTimeMillis, endTimeMillis, eventCount) AS
(
1612244382565, 1612330782565, (select unique count from event_tracking_table where event_tracking_table_event_id = 'post_image' and 1612244382565 <= event_tracking_table_timestamp and event_tracking_table_timestamp <= 1612330782565 )
UNION ALL startTimeMillis + 86400000, endTimeMillis + 86400000 FROM recursiveDailyEvents
Limit 5)
select * from recursiveDailyEvents;
);
This is the error from sqlite browser:
near "1612244382565": syntax error: WITH RECURSIVE
recursiveDailyEvents (startTimeMillis, endTimeMillis, eventCount) AS
(
1612244382565
But I was expecting a table with startTimeMillis, endTimeMillis, and a count (1 or 0).
What am I doing wrong? Or how should I write this recursion?
Edit
Here is some sample data
event_tracking_table_row_id| event_tracking_table_event_id| event_tracking_table_timestamp
1|app_open|1612169104224
2|post_image|1612169437373
3|post_image|1612169738068
4|app_open|1612170216320
5|post_image|1612170507935
6|app_open|1612689116738
7|post_image|1612689316673
8|post_video|1612689579697
9|post_video|1612689609683
10|app_open|1612689664683
... ... ...
Here, event_tracking_table_event_id is in millisecond.
Expected output
If I understand correctly, the recursion should generate a table of start time millisecond, end time millisecond, and eventCount between those time limits.
So today (2 February 20201) at some time, the epoch time was 1612244382565, and after 24 hours, the end time is 1612330782565, and so on.
1612244382565 , 1612330782565, 1 // 1st day start time, end time, event count
1612330782565 , 1612417182565, 0 // 2nd day start time, end time, event count
... ... // 5 rows for 5 consecutive days.
I am trying my best to be as clear as possible.
If you want the starting and ending time of each day and whether the button was clicked, you can do it with conditional aggregation:
SELECT date(event_tracking_table_timestamp / 1000, 'unixepoch', 'localtime') day,
MIN(event_tracking_table_timestamp) min_time,
MAX(event_tracking_table_timestamp) max_time,
MAX(event_tracking_table_event_id = 'post_image') event,
FROM event_tracking_table
GROUP BY day
If you want the number of times the button was clicked for each day:
SELECT date(event_tracking_table_timestamp / 1000, 'unixepoch', 'localtime') day,
MIN(event_tracking_table_timestamp) min_time,
MAX(event_tracking_table_timestamp) max_time,
SUM(event_tracking_table_event_id = 'post_image') event_count
FROM event_tracking_table
GROUP BY day
If you want the rows for the last 5 days, add a WHERE clause before GROUP BY day:
WHERE date(event_tracking_table_timestamp / 1000, 'unixepoch', 'localtime') >=
date('now', '-5 days', 'localtime')
See the demo.
I'm using the function TO_DATE(datecolumn,'yyyymmdd' ) as DATE_REAL
When performing my SQL request I'm getting these errors:
Exception in component tOracleInput_1
java.sql.SQLDataException: ORA-01847: le jour du mois doit être compris entre 1 et le dernier jour du mois
ORA-01847: "day of month must be between 1 and last day of month"
Is there a way to ignore these errors and take the date as it is, even if the date of month is bigger then is valid for the month?
Upgrade to Oracle 12.2 and use the default on conversion error clause of to_date:
to_date(datecolumn default null on conversion error, 'yyyymmdd')
(You may need to nest that within coalesce or case expressions to reattempt the conversion when the date comes out null due to conversion errors.)
Or, write your own PL/SQL function that accepts a string and a format, like to_date() but with additional steps to handle invalid data, and use that in place of to_date(). (If on 12.1 or later, make it pragma udf).
create or replace function to_date_safe(p_datestr varchar2) return date
as
l_result date;
invalid_day_for_month exception;
pragma exception_init(invalid_day_for_month, -1847);
pragma udf;
begin
begin
l_result := to_date(p_datestr,'YYYYMMDD');
exception
when invalid_day_for_month then
l_result := last_day(to_date(substr(p_datestr,1,6),'YYYYMM'));
end;
return l_result;
end to_date_safe;
(Edit) From Oracle 12.1 you can define a PL/SQL function inline with the SQL query.
create table demo (datecolumn varchar2(8));
insert all
into demo (datecolumn) values ('20180101')
into demo (datecolumn) values ('20180399')
into demo (datecolumn) values ('20180299')
select * from dual;
with function to_date_safe(p_datestr varchar2) return date
as
invalid_day_for_month exception;
pragma exception_init(invalid_day_for_month, -1847);
l_result date;
begin
begin
l_result := to_date(p_datestr,'YYYYMMDD');
exception
when invalid_day_for_month then
l_result := last_day(to_date(substr(p_datestr,1,6),'YYYYMM'));
end;
return l_result;
end;
select datecolumn
, to_date_safe(datecolumn) as converted_date
from demo
/
DATECOLUMN CONVERTED_DATE
---------- -------------
20180101 01-JAN-2018
20180399 31-MAR-2018
20180299 28-FEB-2018
3 rows selected.
Or, do the conversion in Java.
You could (as #William mentioned) write a function that attempts to convert the string to a date using the format model you're expecting it to be, and then handles whatever specific errors you expect to see. For your example issue, if you want an invalid day number to be treated as the last day of that month you could do:
create or replace function updateDate(p_date varchar2) return date as
l_date date;
e_bad_day exception;
pragma exception_init (e_bad_day, -1847);
begin
begin
-- try to convert
l_date := to_date(p_date,'yyyymmdd');
exception
when e_bad_day then
-- ignore the supplied day value and get last day of month
l_date := last_day(to_date(substr(p_date, 1, 6), 'yyyymm'));
end;
return l_date;
end;
/
The first to_date() is in a nested block. If that gets the specific exception -1847 then the exception handler tries to use just the first six characters of the string to a date - which gives you the first day of that month - and then uses the last_day() function to give you, well, the last day of that month.
Quick demo giving a few sample values via a CTE:
alter session set nls_date_format = 'YYYY-MM-DD';
with t (dt) as (
select '20180218' from dual
union all select '20160299' from dual
union all select '20180299' from dual
union all select '20160435' from dual
)
select dt, updateDate(dt)
from t;
DT UPDATEDATE
-------- ----------
20180218 2018-02-18
20160299 2016-02-29
20180299 2018-02-28
20160435 2016-04-30
It won't handle any other errors, either from the first conversion attempt or from the second if something else is still wrong. Depending on how bad your data is you may need to handle other scenarios in a similar way.
Since you have no way of knowing what the original date was supposed to be, merely that whatever was entered wasn't valid, it may be safer to throw the date away completely and treat it as null rather than the last day of the month, but that comes down to how you want/need to work with the bad values.
And as already mentioned, you should not be storing dates as strings in the first place - if the data type was correct you wouldn't have these types of issues. Invalid values would have been caught and could have been corrected by the user at that point, instead of you having to try to guess what they might have meant.
Just check how the Date format is stored in the database and accordingly change your format.
eg : to_date(start_time,'dd-Mon-YYYY')
select start_time, case when substr('2016-04-35',9,2) > to_char(last_day( to_date(substr('2016-04-35',6,2) ||substr('2016-04-35',1,4),'mmyyyy')),'dd')then
to_date(substr('2016-04-35',9,2)-(substr('2016-04-35',9,2)-to_char(last_day( to_date(substr('2016-04-35',6,2) ||substr('2016-04-35',1,4),'mmyyyy')),'dd'))
||substr('2016-04-35',6,2) ||substr('2016-04-35',1,4) ,'ddmmyyyy')
else
to_Date(start_time)
end from table_name;
I want to retrieve a record which has a date field whose value is closer to a given date.How should I proceed?
Below is the table,
id |employeeid|region |startdate |enddate |
1 1234 abc 2014-11-24 2015-01-17
2 1234 xyz 2015-01-18 9999-12-31
Here, I should retrieve the record whose enddate is closer to the startdate of another record say,'2015-01-18', so it should retrieve the 1 st record.I tried the following queries
1.
SELECT l.region
FROM ABC.location l where l.EmployeeId=1234
ORDER BY ABS( DATEDIFF('2015-01-18',l.Enddate) );
2.
SELECT l.region
FROM ABC.location l where l.EmployeeId=1234
ORDER BY ABS( DATEDIFF(l.Enddate,'2015-01-18') );
But, none of them is working. Kindly help me in this.
Thanks,
Poorna.
You might want to try this:
Query query = session.createQuery("SELECT l.region, ABS( DATEDIFF('2015-01-18',l.Enddate) ) as resultDiff FROM ABC.location l where l.EmployeeId=1234 ORDER BY resultDiff");
query.setFirstResult(0);
query.setMaxResults(1);
List result = query.list();
Well, Unix timestamps are expressed as a number of seconds since 01 Jan 1970, so if you subtract one from the other you get the difference in seconds. The difference in days is then simply a matter of dividing by the number of seconds in a day:
(date_modified - date_submitted) / (24*60*60)
or
(date_modified - date_submitted) / 86400
get the minimum of them.
refer this question it may be helpful::::Selecting the minimum difference between two dates in Oracle when the dates are represented as UNIX timestamps
I need to count the number of days between 2 dates in JPA.
For example :
CriteriaBuilder.construct(
MyCustomBean.class
myBean.get(MyBean_.beginDate), //Expression<Date>
myBean.get(MyBean_.endDate), //Expression<Date>
myDiffExpr(myBean) //How to write this expression from the 2 Expression<Date>?
);
So far, I tried :
CriteriaBuilder.diff(). but it does not compile because this method expects some N extends Number and the Date does not extend Number.
I tried to extend the PostgreSQL82Dialect (as my target database is PostgreSQL) :
public class MyDialect extends PostgreSQL82Dialect {
public MyDialect() {
super();
registerFunction("datediff",
//In PostgreSQL, date2 - date1 returns the number of days between them.
new SQLFunctionTemplate(StandardBasicTypes.LONG, " (?2 - ?1) "));
}
}
This compiles and the request succeeds but the returned result is not consistent (78 days between today and tomorrow).
How would you do this?
It looks like you are looking for a solution with JPQL to perform queries like SELECT p FROM Period p WHERE datediff(p.to, p.from) > 10.
I'm afraid there is no such functionality in JPQL so I recommend using native SQL. Your idea if extending Dialect with Hibernate's SQLFunctionTemplate was very clever. I'd rather change it to use DATE_PART('day', end - start) as this is the way to achieve days difference between dates with PostgreSQL.
You might also define your function in PostgreSQL and using it with criteria function().
'CREATE OR REPLACE FUNCTION "datediff"(TIMESTAMP,TIMESTAMP) RETURNS integer AS \'DATE_PART('day', $1 - $2);\' LANGUAGE sql;'
cb.function("datediff", Integer.class, end, start);
JPA 2.1 provides for use of "FUNCTION(funcName, args)" in JPQL statements. That allows such handling.
I finally found that the problem comes from the fact that the order of the parameters is not the one I expected :
/*
*(?2 - ?1) is actually equivalent to (? - ?).
* Hence, when I expect it to evaluate (date2 - date1),
* it will actually be evaluated to (date1 - date2)
*/
new SQLFunctionTemplate(StandardBasicTypes.LONG, " (?2 - ?1) "));
I opened a new question in order to know if this behavior is a bug or a feature :
1) CriteriaBuilder.diff(). but it does not compile because this method expects some N extends Number and the Date does not extend Number.
Try to use no of mili seconds for each date as shown below.
Date date = new Date()//use your required date
long millisecond = date.getTime();//Returns no of mili seconds from 1 Jan, 1970 GMT
Long in Number in java and according to autoboxing you can use this. May be this can help.