Timestamp filter in PLSQL - sql

Let's say I have an table containing a user rating of movies like IMDB movie database.
ID
USER_REVIEW
USER_RATING
USER_ID
1
blub
10
1
2
blob
9
2
3
blab
7
3
These table inserts have been taken yesterday.
The entries with the id 1 and 2 have been taken between 16:00 and 16:10. The third entry with id 3 have been taken between 16:10 and 16:15 How can I find a select statement that filters me the first two entries between 16:00 and 16:10?
Or is it possible to find out which are the exact timestamps of the inserts from yesterday?

is it possible to find the exact timestamp of the inserts from yesterday?
Sure, if there's a timestamp column in the table.
As it looks as if such a column doesn't exist, a simple option to add it is
alter table movies add date_insert date default sysdate;
Oracle will auto-populate that column with SYSDATE (unless you explicitly insert it), which means that your current INSERT statement remains "as is", you don't have to change it a bit.
Then you'd - for example - select rows inserted yesterday as
select *
from movies
where trunc(date_insert) = trunc(sysdate - 1)
Or, as you asked, rows inserted yesterday (10th of August 2021) between 16:00 and 16:10:
select *
from movies
where date_insert between to_date('10.08.2021 16:00', 'dd.mm.yyyy hh24:mi')
and to_date('10.08.2021 16:10', 'dd.mm.yyyy hh24:mi')

try the GETDATE() function.
here is something to help you:
https://www.mssqltips.com/sqlservertip/1145/date-and-time-conversions-using-sql-server/

Related

How to make a group query to select multiple rows?

I have a DateTime column (timestamp 2022-05-22 10:10:12) with a batch of stamps per each day.
I need to filter the rows where stamp is before 9am (here is no problem) and I'm using this code:
SELECT * FROM tickets
WHERE date_part('hour'::text, tickets.date_in) < 9::double precision;
The output is the list of the rows where the time in timestamp is less than 9 am (50 rows from 2000).
date_in
2022-05-22 08:10:12
2022-04-23 07:11:13
2022-06-15 08:45:26
Then I need to find all the days where at least one row has a stamp before 9 am - and here I'm stuck. Any idea how to select all the days where at least one stamp was before 9 am?
The code I'm trying:
SELECT * into temp1 FROM tickets
WHERE date_part('hour'::text, tickets.date_in) < 9::double precision
ORDER BY date_part('day'::text, date_in);
Select * into temp2
from tickets, temp1
where date_part('day'::text, tickets.date_in) = date_part('day'::text, temp1.date_in);
Update temp2 set distorted_route = 1;
But this is giving me nothing.
Expected output is to get all the days where at least one route was done before 9am:
date_in
2022-05-22 08:10:12
2022-05-22 10:11:45
2022-05-22 12:14:59
2022-04-23 07:11:13
2022-04-23 11:42:25
2022-06-15 08:45:26
2022-06-15 15:10:57
Should I make an additional table (temp1) to feed it with the first query result (just the rows before 9am) and then make a cross table query to find in the source table public.tickets all the days which are equal to the public.temp1?
Select * from tickets, temp1
where TO_Char(tickets.date_in, 'YYYY-MM-DD')
= TO_Char(temp1.date_in, 'YYYY-MM-DD');
or like this:
SELECT *
FROM tickets
WHERE EXISTS (
SELECT date_in FROM TO_Char(tickets.date_in, 'YYYY-MM-DD') = TO_Char(temp1.date_in, 'YYYY-MM-DD')
);
Ideally, I'd want to avoid using a temporary table and make a request just for one table.
After that, I need to create a view or update and add some remarks to the source table.
Assuming you mean:
How to select all rows where at least one row exists with a timestamp before 9 am of the same day?
SELECT *
FROM tickets t
WHERE EXISTS (
SELECT FROM tickets t1
WHERE t1.date_in::date = t.date_in::date -- same day
AND t1.date_in::time < time '9:00' -- time before 9:00
AND t1.id <> t.id -- exclude self
)
ORDER BY date_id; -- optional, but typically helpful
id being the PK column of your undisclosed table.
But be aware that ...
... typically you'll want to work with timestamptz instead of timestamp. See:
Ignoring time zones altogether in Rails and PostgreSQL
https://wiki.postgresql.org/wiki/Don%27t_Do_This#Don.27t_use_timestamp_.28without_time_zone.29
... this query is slow for big tables, because it cannot use a plain index on (date_id) (not "sargable"). Related:
How do you do date math that ignores the year?
There are various ways to optimize performance. The best way depends on undisclosed information for performance questions.

BQ: Select latest date from multiple columns

Good day, all. I wrote a question relating to this earlier, but now I have encountered another problem.
I have to calculate the timestamp difference between the install_time and contributer_time columns. HOWEVER, I have three contributor_time columns, and I need to select the latest time from those columns first then subtract it from install time.
Sample Data
users
install_time
contributor_time_1
contributor_time_2
contributor_time_3
1
8:00
7:45
7:50
7:55
2
10:00
9:15
9:45
9:30
3
11:00
10:30
null
null
For example, in the table above I would need to select contributor_time_3 and subtract it from install_time for user 1. For user 2, I would do the same, but with contributor_time_2.
Sample Results
users
install_time
time_diff_min
1
8:00
5
2
10:00
15
3
11:00
30
The problem I am facing is that 1) the contributor_time columns are in string format and 2) some of them have 'null' string values (which means that I cannot cast it into a timestamp.)
I created a query, but I am am facing an error stating that I cannot subtract a string from timestamp. So I added safe_cast, however the time_diff_min results are only showing when I have all three contributor_time columns as a timestamp. For example, in the sample table above, only the first two rows will pull.
The query I have so far is below:
SELECT
users,
install_time,
TIMESTAMP_DIFF(install_time, greatest(contributor_time_1, contributor_time_2, contributor_time_3), MINUTE) as ctct_min
FROM
(SELECT
users,
install_time,
safe_cast(contributor_time_1 as timestamp) as contributor_time_1,
safe_cast(contributor_time_2 as timestamp) as contributor_time_2,
safe_cast(contributor_time_3 as timestamp) as contributor_time_3,
FROM
(SELECT
users,
install_time,
case when contributor_time_1 = 'null' then '0' else contributor_time_1 end as contributor_time_1,
....
FROM datasource
Any help to point me in the right direction is appreciated! Thank you in advance!
Consider below
select users, install_time,
time_diff(
parse_time('%H:%M',install_time),
greatest(
parse_time('%H:%M',contributor_time_1),
parse_time('%H:%M',contributor_time_2),
parse_time('%H:%M',contributor_time_3)
),
minute) as time_diff_min
from `project.dataset.table`
if applied to sample data in your question - output is
Above can be refactored slightly into below
create temp function latest_time(arr any type) as ((
select parse_time('%H:%M',val) time
from unnest(arr) val
order by time desc
limit 1
));
select users, install_time,
time_diff(
parse_time('%H:%M',install_time),
latest_time([contributor_time_1, contributor_time_2, contributor_time_3]),
minute) as time_diff_min
from `project.dataset.table`
less verbose and no redundant parsing - with same result - so just matter of preferences
You can use greatest():
select t.*,
time_diff(install_time, greatest(contributor_time_1, contributor_time_2, contributor_time_3), minute) as diff_min
from t;
Note: this assumes that the values are never NULL, which seems reasonable based on your sample data.

Changing date in Str format to Datetime format in SQL

Really basic question but i have zero experience with SQL. I'm using Tableau to do visualisation with data stored in my company's Oracle server, which contains multiple sheets. The primary table i am working with is named YQ005. One of the fields in the primary table I'm working with contains dates but stored as a String in YYYYMMDD format.
I have to convert this to Date format but doing it through Tableau raises the error "ORA-01843: Not a valid month". How can i do a custom SQL query to select this field, convert it to Date-time format and place this new data in a new column?
Littlefoot has a solid answer but it is definitely not for the inexperienced.
The basic function to convert the string to a date is:
select to_date(yyyymmdd, 'yyyymmdd')
If you are having problems with the month, you can just extract it out to check it:
select (case when substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
You can also add a check that the value is all numbers:
select (case when regexp_like(yyyymmdd, '^[0-9]{8}') and
substr(yyyymmdd, 5, 2) between '01' and '12'
then to_date(yyyymmdd, 'yyyymmdd')
end)
Validating dates in Oracle gets much more complicated if you have to validate the whole date -- each month has a different number of days and leap years further complicate matters. But months should always be between 01 and 12.
Error you got means that some values in that table - on 5th and 6th place - don't have a valid month value. For example, it might be 20191823 (there's no 18th month, is there?).
Unfortunately, that's what happens when people store dates as strings. There's no easy way out. If you want to do it with SQL only, you might fail or succeed (if you're VERY lucky). For example, have a look at this example:
SQL> desc yq005
Name Null? Type
----------------------------------------- -------- ----------------
DATUM VARCHAR2(8)
SQL> select * From yq005;
DATUM
--------
20191221
13000815
00010101
19302533 -- 25th month and 33rd day
2013Ab12 -- Ab month
2ooo0513 -- year with letters "o" instead of digits "0"
6 rows selected.
SQL>
A query whose where clause tries to identify invalid values:
SQL> alter session set nls_date_format = 'dd.mm.yyyy.';
Session altered.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
RESULT
-----------
21.12.2019.
15.08.1300.
01.01.0001.
SQL>
lines #3, 4 and 5 are trying to identify valid year/month/day. The first two are OK, more or less, but - it'll miserably fail on at least half of all months because e.g. 20191131 is "valid", but there are no 31 days in November
line #6 eliminates values that aren't all digits
Just to check that 20191131:
SQL> insert into yq005 values ('20191131');
1 row created.
SQL> select to_date(datum, 'yyyymmdd') result
2 from yq005
3 where substr(datum, 1, 4) between '0000' and '9999'
4 and substr(datum, 5, 2) between '00' and '12'
5 and substr(datum, 7, 2) between '01' and '31'
6 and regexp_like(datum, '^\d+$');
ERROR:
ORA-01839: date not valid for month specified
no rows selected
SQL>
As I said, it won't work; the same goes for other 30-days months, as well as February.
You could try to create a function which converts string to date; if it succeeds, fine. If not, skip that value:
SQL> create or replace function f_valid_date_01 (par_datum in varchar2)
2 return number
3 is
4 -- return 1 if PAR_DATUM is a valid date; return 0 if it is not
5 l_date date;
6 begin
7 -- yyyymmdd is format you expect
8 l_date := to_date(par_datum, 'yyyymmdd');
9 return 1;
10 exception
11 when others then
12 return 0;
13 end;
14 /
Function created.
SQL>
Let's use it:
SQL> select datum original_value,
2 to_char(to_date(datum, 'yyyymmdd'), 'dd.mm.yyyy') modified_value
3 from yq005
4 where f_valid_date_01 (datum) = 1;
ORIGINAL MODIFIED_V
-------- ----------
20191221 21.12.2019
13000815 15.08.1300
00010101 01.01.0001
SQL>
Just the opposite - fetch invalid dates:
SQL> select datum
2 from yq005
3 where f_valid_date_01 (datum) = 0;
DATUM
--------
19302533
2013Ab12
2ooo0513
20191131
SQL>
This is just one option you might use; there certainly are others, just Google for them. The bottom line is: always store dates into a DATE datatype column and let the database take care about (in)valid values.
[EDIT: how to populate a new column with a valid date]
If there's no date datatype column in the table, add it:
SQL> alter table yq005 add new_datum date;
Table altered.
Now run the update; mind the where clause:
SQL> update yq005 set
2 new_datum = to_date(datum, 'yyyymmdd')
3 where f_valid_date_01(datum) = 1;
3 rows updated.
SQL> select * From yq005;
DATUM NEW_DATUM
-------- -----------
20191221 21.12.2019.
13000815 15.08.1300.
00010101 01.01.0001.
19302533
2013Ab12
2ooo0513
20191131
7 rows selected.
SQL>
The best solution would be to have whoever maintains your database alter the table definition to store dates using the DATE datatype instead of some form of string.
But if you can't or don't wish to alter the Oracle schema, then I would try using the DATEPARSE() function in Tableau, as follows (assuming your date field is named XXX_DATE)
In Tableau, rename XXX_DATE to XXX_DATE_ORGINAL
Define a calculated field called XXX_DATE as DATEPARSE("YYYYMMdd", [XXX_DATE_ORIGINAL])
Hide the original field XXX_DATE_ORIGINAL
Now you can use your XXX_DATE field as a date in Tableau
The renaming and hiding of the original field is not strictly necessary. I just find it helps keep the data source understandable. For more info, see the Tableau online help for DateParse

SQL: Update a column after a specific number of days

I have a table that lists the bugs along with the info regarding to who it was assigned and who resolved it.
Bugs | Assigned to | Resolved by
--------------------------------
1 Dev1
2 Dev2
3 Dev3
If after a specific number of days (for e.g., 14 days), if the field 'Resolved by' is still blank, I want it to be updated with the value from the column 'Assigned to'.
I was trying to create a view with a time stamp but I'm not sure how to specify the exact number of days and then update the value from another column.
You can do this in a view with something like this:
create view v_bugs as
select bugs, assigned_to,
coalesce(resolved_by,
(case when createdAt <= sysdate - interval '14' day then assigned_to end)
) as assigned_to
from bugs;
This assumes, of course, that you have a column that specifies when each row was inserted.
Unfortunately, Oracle does not allow sysdate in a virtual column, so you cannot use generated always as to define the column.

How to find record count of every single day for a given period in a single sql query?

I have the following table routes with their effective and expiry dates.
r_no name eff_date exp_date
1 test1 01-jun-2013 INF
2 test2 10-jun-2013 15-jun-2013
3 test3 01-jan-2013 25-jun-2013
4 test4 01-feb-2013 31-dec-2013
Input will be given by the user say for Eg: 01-Jun-2013 to 30-Jun-2013`
I need to get the count of the routes for each and every day in that entire period.
Date Routes_Count
01-Jun-2013 3
02-Jun-2013 3
.
.
10-Jun-2013 4
.
.
20-Jun-2013 3
.
.
30-Jun-2013 2
Could someone help me to get the above results using a single query in Oracle 11g?
You can do it this way,
WITH cte AS
(SELECT TO_DATE ('01-jun-2013', 'dd-mon-yyyy') + LEVEL - 1 dte
FROM DUAL
CONNECT BY LEVEL <=
TO_DATE ('30-jun-2013', 'dd-mon-yyyy') + 1
- TO_DATE ('01-jun-2013', 'dd-mon-yyyy'))
SELECT cte.dte, COUNT (1)
FROM cte, routes
WHERE cte.dte BETWEEN NVL (routes.eff_date, '01-jan-0001')
AND NVL (routes.exp_date, '31-dec-4000')
GROUP BY cte.dte
ORDER BY 1;
sql fiddle
Here cte is the temporary table that I have generated, which gives all the dates from 1st June to 30th June. Join this with your routes table, so that the date is within the effective and expiry date of the route. Note that, if there is no exp_date or eff_date, i substitute it with a date which is too far in the future or too far in the past. Now that you have got all the routes for each date, group by the date, to get the count.