Use interval to add on timestamp value from other table, in Oracle - sql

I'm actually beginner in SQL and i working on Oracle engine. I have a problem to do arithmetic manipulation using Interval, to add on timestamp column - integer value, that exist in other table and convert it to Minute.
To test my schemas i used in data generator. As a result, Some of the data produced, are not reliable and i need to check overlapping between two appointments, when the same patient invited for two treatments overlap.
I have treatments_appointments table that contains these attributes:
treatments_appointments(app_id NUMBER(38) NOT NULL,
[fk] care_id NUMBER(38) NOT NULL,
[fk] doctor_id NUMBER(38) NOT NULL,
[fk] room_id NUMBER(38) NOT NULL,
[fk] branch_id NUMBER(38) NOT NULL,
[fk] patient_id NUMBER(38) NOT NULL,
appointment_time TIMESTAMP NOT NULL)
Below is the code what i wrote and it's get an error message:
SELECT app1.app_id
FROM treatment_appointment app1
INNER JOIN treatment_appointment app2
ON app1.patient_id = app2.patient_id
WHERE app1.appointment_time >= app2.appointment_time AND
app1.appointment_time <=
app2.appointment_time + interval (to_char(select care_categories.care_duration where app2.care_id = care_categories.care_id)) minute
AND
app1.app_id != app2.app_id
The error message is:
ORA-00936: missing expression
Sorry about my English and thanks for answering my question!

You can only use a fixed string value for an INTERVAL literal, not a variable, an expression or a column value. But you can use the NUMTODSINTERVAL function to convert a number of minutes into an interval. Instead of:
interval (to_char(select care_categories.care_duration
where app2.care_id = care_categories.care_id)) minute
Use:
numtodsinterval(select care_categories.care_duration
where app2.care_id = care_categories.care_id, 'MINUTE')
Although you should join to that table in the main query rather than doing a subquery for every row:
SELECT app1.app_id
FROM treatment_appointment app1
INNER JOIN treatment_appointment app2
ON app1.patient_id = app2.patient_id
INNER JOIN care_categories cc
ON app2.care_id = cc.care_id
WHERE app1.appointment_time >= app2.appointment_time AND
app1.appointment_time <=
app2.appointment_time + numtodsinterval(cc.care_duration, 'MINUTE') AND
app1.app_id != app2.app_id

Related

How to delete row with past date and add this row to an other table?

Please help. I have 2 tables. First one is called Reservation and second one is called LastOrders.
There are the columns DATE_FROM in Date type and DATE:)_TO in date type too. And I want if the date in table Reservation in column DATE_TO = SYSDATE then delete this row and insert new row to table LastOrders I think a trigger can do that, but I dont know how. Any idea? Thank you very much and have a nice Christmas :)
TABLE RESERVATION
Name Null Type
-------------------------- -------- -------------
ID NOT NULL VARCHAR2(25)
DESCRIPTION NOT NULL VARCHAR2(100)
DATE_FROM NOT NULL DATE
DATE_TO NOT NULL DATE
TABLE LASTORDERS
Name Null Type
-------------------------- -------- -------------
ID NOT NULL VARCHAR2(25)
DESCRIPTION NOT NULL VARCHAR2(100)
DATE_FROM NOT NULL DATE
DATE_TO NOT NULL DATE
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE RESERVATION (
ID VARCHAR2(25) CONSTRAINT RESERVATION__ID__NN NOT NULL,
DESCRIPTION VARCHAR2(100) CONSTRAINT RESERVATION__DE__NN NOT NULL,
DATE_FROM DATE CONSTRAINT RESERVATION__DF__NN NOT NULL,
DATE_TO DATE CONSTRAINT RESERVATION__DT__NN NOT NULL
)
/
CREATE TABLE LASTORDERS (
ID VARCHAR2(25) CONSTRAINT LASTORDERS__ID__NN NOT NULL,
DESCRIPTION VARCHAR2(100) CONSTRAINT LASTORDERS__DE__NN NOT NULL,
DATE_FROM DATE CONSTRAINT LASTORDERS__DF__NN NOT NULL,
DATE_TO DATE CONSTRAINT LASTORDERS__DT__NN NOT NULL
)
/
CREATE PROCEDURE fulfilReservation(
I_ID IN RESERVATION.ID%TYPE,
O_SUCCESS OUT NUMBER
)
AS
r_reservation RESERVATION%ROWTYPE;
BEGIN
DELETE FROM RESERVATION
WHERE ID = I_ID
AND SYSDATE BETWEEN DATE_FROM AND DATE_TO
RETURNING ID, DESCRIPTION, DATE_FROM, DATE_TO INTO r_reservation;
INSERT INTO lastorders VALUES r_reservation;
o_success := 1;
EXCEPTION
WHEN NO_DATA_FOUND THEN
o_success := 0;
END;
/
INSERT INTO RESERVATION VALUES ( 1, 'Test', SYSDATE - 1, SYSDATE + 1 )
/
DECLARE
success NUMBER(1,0);
BEGIN
fulfilReservation( 1, success );
END;
/
Query 1:
SELECT * FROM RESERVATION
Results:
No Results
Query 2:
SELECT * FROM LASTORDERS
Results:
| ID | DESCRIPTION | DATE_FROM | DATE_TO |
|----|-------------|----------------------------|----------------------------|
| 1 | Test | December, 24 2015 18:59:07 | December, 26 2015 18:59:07 |
Trigger can do such job but it will fire only on some changes on table data. If you need to move data between tables in real-time you have to use jobs.
begin
dbms_scheduler.create_job(
job_name => 'my_job',
job_type => 'PLSQL_BLOCK',
enabled => true,
repeat_interval => 'FREQ=DAILY; INTERVAL=1',
start_date => round(sysdate,'DD'),
job_action => 'begin
insert into LastOrders
select *
from Reservation
where DATE_TO = round(sysdate,''DD'');
delete Reservation where DATE_TO = round(sysdate,''DD'');
commit;
end;');
end;
This job will move data from Reservation to LastOrders every day at 00:00.
Ooo, tricky one!
The main question is how "realtime" you need this to be. You need to consider that data changes on both sides of the equation - meaning, there is new data that enters the table and should be compared to SYSDATE (that's the easy part). The other side is SYSDATE, changing every second. So if you need your data to be as accurate as possible you basically need to check the entire table every second to see if any of the DATE_DO has "arrived" to SYSDATE. This is where the job #Artem suggested comes in handy.
BUT
Running the job once a day means that rows would be moved only once a day, and running it every second would mean querying the table every second (potential performance overhead).
SO, you need to be smart about it -
Add an index on DATE_TO
Make sure to use bind variables in repeating
queries you're making on the table. i.e, change the query in the
job.
Build the job in a smart way - Let's say you run it every 5
min (because you don't want your potentially heavy query to run
every sec), and you check for the lines that will reach SYSDATE in
the next 5 min. You can then move to a temporary table that holds
only these lines, and have another job run on that every second.
Sounds a bit complex, but would save you lot's of trouble if this data is planned to scale.
Good luck, Let me know if you have any questions.

Select date between two dates

I have this select statement to return number of rows between two dates. It only returns one while while there are foure rows.
SELECT count(*) as number
FROM PFServicesLogging
WHERE User_ID = '784198013531599'
AND ServiceType = 1
AND InsertDate between "2013-11-11" and "2013-11-19"
table structure is
CREATE TABLE `PFServicesLogging`
(
ID INTEGER NOT NULL,
ServiceType INTEGER NOT NULL,
Datetime DATETIME,
User_ID TEXT,
Frequency INTEGER, `InsertDate` Date,
PRIMARY KEY(ID)
)
If your date fields include the time of day, do not use between. Use
where YourDateField >= StartDate
and YourDateField < TheDayAfterTheEndDate
Edit Starts Here
Reading the comments, maybe the problem has nothing to do with the dates. Maybe it's the user_id or service type. To troubleshoot, replace your where clause with
where 1 = 1
/*
User_ID = '784198013531599'
AND ServiceType = 1
AND InsertDate between "2013-11-11" and "2013-11-19"
*/
and run your query. Take your filters out of the comment block one by one so that you can see which one causes the unexpected results.

Derby DB , How to find the date nearest to current Date in WHERE condition

I am working on a project that is using JDBC (Derby driver), The part of the table details are provided below.
Table Name : Example (
RID Char(5) Primary Key
RTime TIME NOT NULL,
RDate Date NOT NULL
)
I know how to find the closest date to current date (or time) using MAX, but in WHERE condition, MAX and MIN definitely do not work in most SQL.
How can I find the date/time nearest to current date/time in Derby db?
You could either do it with a subquery
SELECT * FROM T WHERE RTime = (SELECT MAX(RTime) FROM T) AND RDate = ...
or by sorting on the RDate and RTime columns and selecting the first.

How to select records using table relationships and timestamps?

Another newbie PostgreSQL question.
I have something like this:
CREATE TABLE user (
userID bigserial primary key,
name varchar(50) NOT NULL,
created timestamp NULL DEFAULT CURRENT_TIMESTAMP
)
CREATE TABLE session (
sessionID bigserial primary key,
userID int NOT NULL,
lastAction timestamp NULL DEFAULT NULL,
created timestamp NULL DEFAULT CURRENT_TIMESTAMP
)
CREATE TABLE action (
actionID bigserial primary key,
sessionID int NOT NULL,
lastAction timestamp NULL DEFAULT NULL,
created timestamp NULL DEFAULT CURRENT_TIMESTAMP
)
A user can have many sessions, each with multiple session actions.
Each user has sessions which expire, in which case a new one is inserted and any action they take is catalogued there.
My question is, how do I go about grabbing actions only for a select user, only from his sessions, and only if they happened 1 day ago, 2 days ago, a week ago, a month ago, or for all time.
I've looked at the docs and I think interval() is what I'm looking for but I only really know how to expire sessions:
(part of a join here) e.lastAction >= now() - interval '4 hours'
That one either returns me what I need or it doesn't. But how do I make it return all the records that have been created since 1 day ago, 2 days ago, etc. SQL syntax and logic is still a bit confusing.
So in an ideal world I'll want to ask a question like, how many actions has this user taken in 2 days? I have the relationships and timestamps created but I writing a query I've been met with failure.
I'm not sure which timestamp you want from the actions table -- the created or the last action timestamp. In any case, the query you want is a basic join, where you filter on the user id and the time stamp:
select a.*
from actions a join
sessions s
on a.sessionid = s.sessionid
where s.userid = v_userid and
a.created >= now() - interval '1 day';
If you want the number of transactions in the past two days, you would use aggregation:
select count(*)
from actions a join
sessions s
on a.sessionid = s.sessionid
where s.userid = v_userid and
a.created >= now() - interval '2 day';

How can I calculate the top % daily price changes using MySQL?

I have a table called prices which includes the closing price of stocks that I am tracking daily.
Here is the schema:
CREATE TABLE `prices` (
`id` int(21) NOT NULL auto_increment,
`ticker` varchar(21) NOT NULL,
`price` decimal(7,2) NOT NULL,
`date` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `ticker` (`ticker`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=2200 ;
I am trying to calculate the % price drop for anything that has a price value greater than 0 for today and yesterday. Over time, this table will be huge and I am worried about performance. I assume this will have to be done on the MySQL side rather than PHP because LIMIT will be needed here.
How do I take the last 2 dates and do the % drop calculation in MySQL though?
Any advice would be greatly appreciated.
One problem I see right off the bat is using a timestamp data type for the date, this will complicate your sql query for two reasons - you will have to use a range or convert to an actual date in your where clause, but, more importantly, since you state that you are interested in today's closing price and yesterday's closing price, you will have to keep track of the days when the market is open - so Monday's query is different than tue - fri, and any day the market is closed for a holiday will have to be accounted for as well.
I would add a column like mktDay and increment it each day the market is open for business. Another approach might be to include a 'previousClose' column which makes your calculation trivial. I realize this violates normal form, but it saves an expensive self join in your query.
If you cannot change the structure, then you will do a self join to get yesterday's close and you can calculate the % change and order by that % change if you wish.
Below is Eric's code, cleaned up a bit it executed on my server running mysql 5.0.27
select
p_today.`ticker`,
p_today.`date`,
p_yest.price as `open`,
p_today.price as `close`,
((p_today.price - p_yest.price)/p_yest.price) as `change`
from
prices p_today
inner join prices p_yest on
p_today.ticker = p_yest.ticker
and date(p_today.`date`) = date(p_yest.`date`) + INTERVAL 1 DAY
and p_today.price > 0
and p_yest.price > 0
and date(p_today.`date`) = CURRENT_DATE
order by `change` desc
limit 10
Note the back-ticks as some of your column names and Eric's aliases were reserved words.
Also note that using a where clause for the first table would be a less expensive query - the where get's executed first and only has to attempt to self join on the rows that are greater than zero and have today's date
select
p_today.`ticker`,
p_today.`date`,
p_yest.price as `open`,
p_today.price as `close`,
((p_today.price - p_yest.price)/p_yest.price) as `change`
from
prices p_today
inner join prices p_yest on
p_today.ticker = p_yest.ticker
and date(p_today.`date`) = date(p_yest.`date`) + INTERVAL 1 DAY
and p_yest.price > 0
where p_today.price > 0
and date(p_today.`date`) = CURRENT_DATE
order by `change` desc
limit 10
Scott brings up a great point about consecutive market days. I recommend handling this with a connector table like:
CREATE TABLE `market_days` (
`market_day` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`date` DATE NOT NULL DEFAULT '0000-00-00',
PRIMARY KEY USING BTREE (`market_day`),
UNIQUE KEY USING BTREE (`date`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0
;
As more market days elapse, just INSERT new date values in the table. market_day will increment accordingly.
When inserting prices data, lookup the LAST_INSERT_ID() or corresponding value to a given date for past values.
As for the prices table itself, you can make storage, SELECT and INSERT operations much more efficient with a useful PRIMARY KEY and no AUTO_INCREMENT column. In the schema below, your PRIMARY KEY contains intrinsically useful information and isn't just a convention to identify unique rows. Using MEDIUMINT (3 bytes) instead of INT (4 bytes) saves an extra byte per row and more importantly 2 bytes per row in the PRIMARY KEY - all while still affording over 16 million possible dates and ticker symbols (each).
CREATE TABLE `prices` (
`market_day` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`ticker_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`price` decimal (7,2) NOT NULL DEFAULT '00000.00',
PRIMARY KEY USING BTREE (`market_day`,`ticker_id`),
KEY `ticker_id` USING BTREE (`ticker_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
;
In this schema each row is unique across each pair of market_day and ticker_id. Here ticker_id corresponds to a list of ticker symbols in a tickers table with a similar schema to the market_days table:
CREATE TABLE `tickers` (
`ticker_id` MEDIUMINT(8) UNSIGNED NOT NULL AUTO_INCREMENT,
`ticker_symbol` VARCHAR(5),
`company_name` VARCHAR(50),
/* etc */
PRIMARY KEY USING BTREE (`ticker_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=0
;
This yields a similar query to others proposed, but with two important differences: 1) There's no functional transformation on the date column, which destroys MySQL's ability to use keys on the join; in the query below MySQL will use part of the PRIMARY KEY to join on market_day. 2) MySQL can only use one key per JOIN or WHERE clause. In this query MySQL will use the full width of the PRIMARY KEY (market_day and ticker_id) whereas in the previous query it could only use one (MySQL will usually pick the more selective of the two).
SELECT
`market_days`.`date`,
`tickers`.`ticker_symbol`,
`yesterday`.`price` AS `close_yesterday`,
`today`.`price` AS `close_today`,
(`today`.`price` - `yesterday`.`price`) / (`yesterday`.`price`) AS `pct_change`
FROM
`prices` AS `today`
LEFT JOIN
`prices` AS `yesterday`
ON /* uses PRIMARY KEY */
`yesterday`.`market_day` = `today`.`market_day` - 1 /* this will join NULL for `today`.`market_day` = 0 */
AND
`yesterday`.`ticker_id` = `today`.`ticker_id`
INNER JOIN
`market_days` /* uses first 3 bytes of PRIMARY KEY */
ON
`market_days`.`market_day` = `today`.`market_day`
INNER JOIN
`tickers` /* uses KEY (`ticker_id`) */
ON
`tickers`.`ticker_id` = `today`.`ticker_id`
WHERE
`today`.`price` > 0
AND
`yesterday`.`price` > 0
;
A finer point is the need to also join against tickers and market_days in order to display the actual ticker_symbol and date, but these operations are very fast since they make use of keys.
Essentially, you can just join the table to itself to find the given % change. Then, order by change descending to get the largest changers on top. You could even order by abs(change) if you want the largest swings.
select
p_today.ticker,
p_today.date,
p_yest.price as open,
p_today.price as close,
--Don't have to worry about 0 division here
(p_today.price - p_yest.price)/p_yest.price as change
from
prices p_today
inner join prices p_yest on
p_today.ticker = p_yest.ticker
and date(p_today.date) = date(date_add(p_yest.date interval 1 day))
and p_today.price > 0
and p_yest.price > 0
and date(p_today.date) = CURRENT_DATE
order by change desc
limit 10