Reputation: 6892
I have this data in my Oracle table:
In this table, I have multiple products with having product_id
and price at a particular time.
I want to write a SQL query to get latest price of each product based on the TIME
column (Oracle - Long Type).
But here is the problem, for our table price monitoring for the product starts at 23:00 previous date.
Output required: when I passed 2-Dec-2019 as parameter into the SQL query
How can I do this with a SQL query? Or do I need to write a stored procedure for it, based on if and else condition?
Thanks in advance.
Upvotes: 0
Views: 1421
Reputation: 95052
First of all you are storing date and time separately, which makes working with the date and time more complicated than needed.
One way to combine the two:
local_date + interval '1' second * (to_number(substr(time, 5, 2)) +
to_number(substr(time, 3, 2)) * 60 +
to_number(substr(time, 1, 2)) * 3600)
Another:
to_date(to_char(local_date, 'yyyymmdd') || time, 'yyyymmddhh24miss')
With this datetime you want something along the lines of
where the_time >= timestamp '2019-12-01 23:00:00' and the_time < date '2019-12-03'
For some date yet unknown that will be provided when running the query:
where the_time >= :date - interval '1' hour and the_time < :date + interval '1' day
For the current day, supposing there is no future data in the table:
where the_time >= trunc(sysdate) - interval '1' hour
As to preferring a more current datetime over an older one, use a window function, e.g. MAX OVER
.
with rows_with_datetime as
(
select
mytable.*,
to_date(to_char(local_date, 'yyyymmdd') || time, 'yyyymmddhh24miss') as dt
from mytable
)
, two_days_with_maxdatetime as
(
select
rows_with_datetime.*,
max(dt) over (partition by product_id order by dt) as max_dt
from rows_with_datetime
where dt >= trunc(sysdate) - interval '1' hour
)
select *
from two_days_with_maxdatetime
where dt = max_dt
order by product_id;
As the datetime gets calculated, no index will be used here and the query may be quite slow accordingly. So, you should better change the data model to have a datetime you can easily index and work with instead of separate date and time. If you can't, you may want to apply an additional redundant condition on the date alone:
where local_date >= trunc(sysdate) - interval '1' day
Upvotes: 1
Reputation: 1270523
Oh, I see. You can use two parameters in the ORDER BY
:
SELECT local_date, system_date, currency, product_id, time, current_price
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY product_id
ORDER BY local_date DESC, time DESC
) as seqnum
FROM t
WHERE system_date = @parameter
) t
WHERE rn = 1;
I'm not sure if you really want the filtering on date like that. You might want:
WHERE system_date <= @parameter
or:
WHERE system_date < @parameter + interval '1' day
These would allow you to get data from earlier days, if there is no data on the date specified by the parameter. The second version also works if system_date
has a time component (which is allowed for date
data types in Oracle).
Upvotes: 1
Reputation: 21095
You may extend the idea from other answer (using ROW_NUMBER
) to define a view providing only the records with the last price per day.
You must provide you dimensions (product_id and day) in the PARTITION BY
clause, which will work for any product and any day - selecting only the row with the highest time per day and product.
create view last_price_per_day as
with last_price as (
select
SYSTEM_DATE, PRODUCT_ID, TIME, CURRENT_PRICE,
row_number() over (partition by PRODUCT_ID, SYSTEM_DATE order by TIME desc) as rn
from tab)
select
SYSTEM_DATE, PRODUCT_ID, TIME, CURRENT_PRICE
from last_price
where rn = 1;
Note that this approach is superior to altervatives using MAX(TIME)
in correlated subquery, as it works fine even if ties occurres (more rows with the same highest time).
If this is a relevant topic, you may add further columns to the ORDER BY
clause, e.g. by adding CURRENT_PRICE DESC
you will get the highest price from the tied rows.
A good check for such last row views is to verify if the columns in PARTITIEN BY
and ORDER BY
are unique in the table.
In your case PRODUCT_ID, SYSTEM_DATE, TIME
should be unique, otherwise the view is not deterministic and will return on repeated queries different results - which may be not what you expect.
Upvotes: 0
Reputation: 522181
We can try using ROW_NUMBER
for this requirement:
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY time DESC) rn
FROM yourTable t
WHERE system_date = date '2019-12-19'
)
SELECT local_date, system_date, currency, product_id, time, current_price
FROM cte
WHERE rn = 1;
Upvotes: 2