Oracle SQL to delete table entries using having clause - sql

I have a SQL query which finds userids in a table based on last login date
select USER_ID, max(login_time) maxd from MyTable group by User_Id having max(login_time) < (sysdate - 90)
I need to delete the entries that this query finds
I have tried
DELETE a
FROM MyTable a
JOIN
(
select USER_ID, max(login_time) maxd from MyTable group by User_Id having max(login_time) < (sysdate - 90)
) b ON a.USER_ID = b.USER_ID
but this results in
SQL Error: ORA-00933: SQL command not properly ended
Can anyone help ?

Oracle supports delete join syntax, but it is a bit different than other databases.
DELETE FROM (
SELECT a.*
FROM MyTable a
INNER JOIN
(
SELECT USER_ID
FROM MyTable
GROUP BY User_Id
HAVING MAX(login_time) < (sysdate - 90)
) b ON a.USER_ID = b.USER_ID
);

Use your select as a subquery:
DELETE FROM MyTable
WHERE (user_id, login_time) IN
-- this is your query:
( SELECT USER_ID, MAX (login_time) maxd
FROM MyTable
GROUP BY User_Id
HAVING MAX (login_time) < (SYSDATE - 90));

You can correlate on the ROWID pseudo-column and use an analytic function:
DELETE FROM mytable
WHERE ROWID IN (
SELECT ROWID
FROM (
SELECT login_time,
MAX(login_time) OVER (PARTITION BY user_id) AS max_login_time
FROM mytable
)
WHERE max_login_time < SYSDATE - INTERVAL '90' DAY
AND login_time = max_login_time
)

Related

Windows functions orderen by date when some dates doesn't exist

Suppose this example query:
select
id
, date
, sum(var) over (partition by id order by date rows 30 preceding) as roll_sum
from tab
When some dates are not present on date column the window will not consider the unexistent dates. How could i make this windowns aggregation including these unexistent dates?
Many thanks!
You can join a sequence containing all dates from a desired interval.
select
*
from (
select
d.date,
q.id,
q.roll_sum
from unnest(sequence(date '2000-01-01', date '2030-12-31')) d
left join ( your_query ) q on q.date = d.date
) v
where v.date > (select min(my_date) from tab2)
and v.date < (select max(my_date) from tab2)
In standard SQL, you would typically use a window range specification, like:
select
id,
date,
sum(var) over (
partition by id
order by date
range interval '30' day preceding
) as roll_sum
from tab
However I am unsure that Presto supports this syntax. You can resort a correlated subquery instead:
select
id,
date,
(
select sum(var)
from tab t1
where
t1.id = t.id
and t1.date >= t.date - interval '30' day
and t1.date <= t.date
) roll_sum
from tab t
I don't think Presto support window functions with interval ranges. Alas. There is an old fashioned way to doing this, by counting "ins" and "outs" of values:
with t as (
select id, date, var, 1 as is_orig
from t
union all
select id, date + interval '30 day', -var, 0
from t
)
select id.*
from (select id, date, sum(var) over (partition by id order by date) as running_30,
sum(is_org) as is_orig
from t
group by id, date
) id
where is_orig > 0

add a column showing number of records for that ID before a date

I have a table that has a column called START_DATE which is of type datetime, and the id is represented by ID. I want to find the number of records in the same table, for that person before that START_DATE.
This is my attempt
select ID,count(*) from dbo.MyTable
where START_DATE < (select START_DATE from dbo.MyTable)
group by ID
But it does not work.
This is what you want:
SELECT t.ID,t.start_date,max(t.OTHER_COLUMN),max(T.OTHER_COLUMN2)...,count(*) from dbo.MyTable t
INNER JOIN dbo.MyTable s
ON(t.id = s.id and t.start_date > s.start_date)
GROUP BY t.ID,t.start_date
Or with a correlated query:
SELECT t.ID,
t.start_date,
t.OTHER_COLUMN,
t.OTHER_COLUMN2,
......
(SELECT COUNT(*) FROM dbo.MyTable s
WHERE t.id = s.id and t.start_date > s.start_date)
FROM dbo.MyTable t

The right SQL or ActiveRecord formulation for complex query in Rails

I have an ActiveRecord model SentimentRecords with table sentiment_records where each record has attributes: user_id, sentiment_id,lecture_id, and timestamp.
My Query: Given a LECTURE_ID, and a DATETIME, for each sentiment_id, I want a count of how many user_id's (users) recorded each sentiment_id; for each user_id only counting the latest timestamp <= the given datetime.
Here is a formulation that isn't working:
SentimentRecords.find_by_sql
("SELECT sentiment_id,count(*)
FROM sentiment_records WHERE id
IN (SELECT max(id) FROM sentiment_records
WHERE lecture_id=LECTURE_ID GROUP BY user_id
WHERE timestamp <= DATETIME) GROUP BY sentiment_id")
I'm not sure I understand the requirements, but I think you need SQL that's something like this.
select s1.sentiment_id, count(s1.user_id)
from sentiment_records s1
inner join (
select user_id, max(timestamp) as timestamp
from sentiment_records
where timestamp <= ?
and lecture_id = ?
group by user_id
) s2 on s1.user_id = s2.user_id and s1.timestamp = s2.timestamp
where lecture_id = ?
group by s1.sentiment_id;
Here is the correct query, thanks to Nick Marden:
SentimentRecords.connection.select_all("SELECT sentiment_id, COUNT(*) FROM sentiment_records WHERE id IN (SELECT MAX(id) FROM sentiment_records WHERE lecture_id = LECTURE_ID AND timestamp < DATETIME GROUP BY user_id) GROUP BY sentiment_id")

Filter rows by those created within a close timeframe

I have a application where users create orders that are stored in a Oracle database. I'm trying to find a bug that only happens when a user creates orders within 30 seconds of the last order they created.
Here is the structure of the order table:
order_id | user_id | creation_date
I would like to write a query that can give me a list of orders where the creation_date is within 30 seconds of the last order for the same user. The results will hopefully help me find the bug.
I tried using the Oracle LAG() function but it doesn't seem to with the WHERE clause.
Any thoughts?
SELECT O.*
FROM YourTable O
WHERE EXISTS (
SELECT *
FROM YourTable O2
WHERE
O.creation_date > O2.creation_date
AND O.user_id = O2.user_id
AND O.creation_date - (30 / 86400) <= O2.creation_date
);
See this in action in a Sql Fiddle.
You can use the LAG function if you want, you would just have to wrap the query into a derived table and then put your WHERE condition in the outer query.
SELECT distinct
t1.order_id, t1.user_id, t1.creation_date
FROM
YourTable t1
join YourTable t2
on t2.user_id = t1.user_id
and t2.creation_date between t1.creation_date - 30/86400 and t1.creation_date
and t2.rowid <> t1.rowid
order by 3 desc
Example of using LAG():
SELECT id, (pss - css) time_diff_in_seconds
, creation_date, prev_date
FROM
(
SELECT id, creation_date, prev_date
, EXTRACT(SECOND From creation_date) css
, EXTRACT(SECOND From prev_date) pss
FROM
(
SELECT id, creation_date
, LAG(creation_date, 1, creation_date) OVER (ORDER BY creation_date) prev_date
FROM
( -- Table/data --
SELECT 1 id, timestamp '2013-03-20 13:56:58' creation_date FROM dual
UNION ALL
SELECT 2, timestamp '2013-03-20 13:57:27' FROM dual
UNION ALL
SELECT 3, timestamp '2013-03-20 13:59:16' FROM dual
)))
--WHERE (pss - css) <= 30
/
ID TIME_DIFF_IN_SECONDS
--------------------------
1 0 <<-- if uncomment where
2 31
3 11 <<-- if uncomment where

Get another column from sum-sub-select

I'm selecting something from a sub-select, which in turn gives me a list of sums. Now I want to select the base_unit column, which contains the unit of measurement. I can't seem to add base_unit to the sub-select because then it doesn't work with the GROUP BY statement.
SELECT to_char(a.pow * f_unit_converter(base_unit, '[W]'), '000.00')
FROM (
SELECT sum (time_value) AS pow
FROM v_value_quarter_hour
WHERE
mp_id IN (SELECT mp_id FROM t_mp WHERE mp_name = 'AC') AND
(now() - time_stamp < '5 day')
GROUP BY time_stamp
ORDER BY time_stamp DESC
) a
LIMIT 1
Where/how can I additionally select the base_unit from the t_mp Table for each of those sums, so that I can pass it to the f_unit_converter function?
Thanks a lot,
MrB
SELECT to_char(a.pow * f_unit_converter(a.base_unit, '[W]'), '000.00')
FROM (
SELECT sum (time_value) AS pow, t_mp.base_unit
FROM v_value_quarter_hour
inner join t_mp on (v_value_quarter_hour.mp_id = t_mp.mp_id)
WHERE
t_mp.mp_name = 'AC' AND
(now() - time_stamp < '5 day')
GROUP BY time_stamp, base_unit
ORDER BY time_stamp DESC
) a
LIMIT 1
Assuming that all your selected rows have the same base_unit, you should be able to add it both to the SELECT and the GROUP BY of your sub-query.
Use an INNER JOIN instead of an IN. Something like this
SELECT to_char(a.pow * f_unit_converter(base_unit, '[W]'), '000.00') FROM (
SELECT sum (time_value), base_unit AS pow
FROM v_value_quarter_hour
INNER JOIN t_mp ON v_value_quarter_hour.mp_id = t_mp.mp_id
WHERE mp_name = 'AC' AND
now() - time_stamp < '5 day'
GROUP BY time_stamp, base_unit
ORDER BY time_stamp DESC ) a LIMIT 1