Ignoring a primary key in a SQL delete where exists statement - sql

Last CTE based SQL question for today, I promise!
I have a CTE and it returns a couple of rows:
WITH myCte AS
( ... )
SELECT 123 AS PersonId, DeviceId
FROM myCte
would return:
PersonId | DeviceId
---------+---------
123 4
123 8
I tried to use the results in a delete statement so I could get rid of the respective rows in another table PersonHasDevice
pk | PersonId | DeviceId
---+----------+---------
1 123 4 - to delete
2 123 5
3 123 8 - to delete
4 991 8
So I appended the delete bit to the CTE select:
WITH myCte AS
( ... )
DELETE FROM PersonHasDevice
WHERE EXISTS (SELECT 123 AS PersonId, DeviceId FROM myCte)
But this just deletes everything from the PersonHasDevice table.
I'm not sure if it's because I'm not taking into account the PK on the table - but then if that were the case surely it shouldn't delete anything?!

You need to "connect" the CTE to your table. You can do this using exists or using a join:
with myCte as ( ... )
delete phd
from PersonHasDevice phd join
myCte
on phd.PersonId = myCte.PersonId and
phd.DeviceId = myCte.DeviceId
Edit by OP
Just reiterating that the hardcoded value 123 in my original query is easily substituted here:
with myCte as ( ... )
delete phd
from PersonHasDevice phd join
myCte
on phd.PersonId = 123 and
phd.DeviceId = myCte.DeviceId

This is the correct syntax if you want to use EXISTS:
WITH myCte AS (.....)
DELETE p FROM PersonHasDevice p
WHERE p.PersonId = 123
AND EXISTS (
SELECT 1
FROM myCte m
WHERE p.DeviceId = m.DeviceId
);

Related

Not able to generate SQL Join query for Postgres

I have a table structure like this and I am using Postgres
ContactPhoneRelation
id
ContactId
PhoneId
1
123
999
2
123
998
I have another table GroupTable
id
groupId
ContactId
PhoneId
1
1
123
999
2
2
123
999
3
2
123
998
I am trying to fetch the data from ContactPhoneRelation which does not exist in groupId 1 and ContactId is 123, So I want to query such that where groupId is 1 and ContactId is 123 and ContactId phoneId both does not exists in groupId 1
So in return, it should give me this result
id
contactId
PhoneId
2
123
998
If I query for groupId 2, It should give me 0 rows in return.
I tried this query but it gave me the opposite data.
select * from ContactPhoneRelation cp
left join GroupTable gt on gt.ContactId = cp.ContactId
where cp.contactId = '123' and gt.groupId = 1
In all honesty, I don't understand what you are trying to achive. But maybe this is because of language issues.
For better understanding I added an example on db<>fiddle: Link
Edit (29.09.2021; 16:49):
SELECT
c.*
FROM
(
SELECT
b.groupID
,a.ContactID
,a.PhoneID
FROM
(SELECT DISTINCT
ContactId
,PhoneID
FROM
ContactPhoneRelation
) a
FULL JOIN
(SELECT DISTINCT
groupID
FROM
GroupTable
) b ON (1=1)
ORDER BY groupID, ContactID, phoneID
) c
LEFT OUTER JOIN GroupTable d ON (
c.groupID = d.groupID
AND c.ContactID = d.ContactID
AND c.PhoneID = d.PhoneID
)
WHERE
d.groupID IS NULL

Get the only the last row in a sequence. SQL Server

I have a table like this:
ID Seq Prod
-----------------
1 001 1
2 002 1
3 001 2
4 002 2
5 003 2
I want to make a query that only gets the last "Seq" of each product, so the expected output will be something like this:
ID Seq Prod
-----------------
2 002 1
5 003 2
Any help?
A simple way is a correlated subquery:
select t.*
from t
where t.seq = (select max(t2.seq) from t t2 where t2.prod = t.prod);
For performance, you want an index on (prod, seq).
The above often has the best performance. But another way to write the query is to use window fucntions:
select t.*
from (select t.*, row_number() over (partition by prod order by seq desc) as seqnum
from t
) t
where seqnum = 1;
Yet another option is using WITH TIES
Select top 1 * with ties
From YourTable
Order By row_number() over (partition by prod order by seq desc)
Full Disclosure:
Gordon's answer is a nudge more performant (+1), but WITH TIES does not generate an extra column.
You could use a sub-query that finds the maximum ID by Prod. In the following example, replace 'myTable' with your table name:
SELECT t.*
FROM myTable t
INNER JOIN (
SELECT MAX(ID) AS ID,
Prod
FROM myTable
GROUP BY Prod
) a ON a.ID = t.ID
Output:
ID Seq Prod
2 002 1
5 003 2
Here is a quick, working fiddle.
You can write a correlated subquery as:
select T.ID,T.Seq,T.Prod
from #T1 T
where T.ID = (select max(T_Inner.ID)
from #T1 T_Inner
where T_Inner.Prod = T.Prod
group by T_Inner.Prod
)

find all rows after the recent update using oracle

I tried below query to bring all rows after last Action="UNLOCKED", but ORDER BY is not allowed in subquery it seems.
SELECT *
FROM TABLE
WHERE id >= (SELECT MAX(id)
FROM TABLE
WHERE ACTION='UNLOCKED' AND action_id=123
ORDER BY CREATE_DATE DESC);
Sample data
Id action_id Action ... CREATE_DATE
1 123 ADD 03/18/2018
2 123 Unlocked 03/19/2018
3 123 Updated1 03/19/2018
4 123 Updated2 03/19/2018
5 123 Unlocked 03/20/2018
6 123 Updated3 03/20/2018
7 123 Updated4 03/20/2018
Output should be rows with id 5,6,7. What should i use to get this output
you could use an inner join on subselect for max create_date
select * from TABLE
INNER JOIN (
select max(CREATE_DATE) max_date
from TABLE
where Action = 'Unlocked' ) T on t.max_date = TABLE.CREATE_DATE
You need not order the inner query because it will return only one value. You can do it as follows
SELECT * FROM TABLE WHERE id >= (select max(id) from TABLE where ACTION='UNLOCKED' and action_id=123);

Find next row with specific value in a given row

The table I have now looks something like this. Each row has a time value (on which the table is sorted in ascending order), and two values which can be replicated across rows:
Key TimeCall R_ID S_ID
-------------------------------------------
1 100 40 A
2 101 50 B
3 102 40 C
4 103 50 D
5 104 60 A
6 105 40 B
I would like to return something like this, wherein for each row, a JOIN is applied such that the S_ID and Time_Call of the next row that shares that row's R_ID is displayed (or is NULL if that row is the last instance of a given R_ID). Example:
Key TimeCall R_ID S_ID NextTimeCall NextS_ID
----------------------------------------------------------------------
1 100 40 A 102 C
2 101 50 B 103 D
3 102 40 C 105 B
4 103 50 D NULL NULL
5 104 60 A NULL NULL
6 105 40 B NULL NULL
Any advice on how to do this would be much appreciated. Right now I'm joining the table on itself and staggering the key on which I'm joining, but I know this won't work for the instance that I've outlined above:
SELECT TOP 10 Table.*, Table2.TimeCall AS NextTimeCall, Table2.S_ID AS NextS_ID
FROM tempdb..#Table AS Table
INNER JOIN tempdb..#Table AS Table2
ON Table.TimeCall + 1 = Table2.TimeCall
So if anyone could show me how to do this such that it can call rows that aren't just consecutive, much obliged!
Use LEAD() function:
SELECT *
, LEAD(TimeCall) OVER (PARTITiON BY R_ID ORDER BY [Key]) AS NextTimeCall
, LEAD(S_ID) OVER (PARTITiON BY R_ID ORDER BY [Key]) AS NextS_ID
FROM Table2
ORDER BY [Key]
SQLFiddle DEMO
This is only test example I had close by ... but i think it could help you out, just adapt it to your case, it uses Lag and Lead ... and it's for SQL Server
if object_id('tempdb..#Test') IS NOT NULL drop table #Test
create table #Test (id int, value int)
insert into #Test (id, value)
values
(1, 1),
(1, 2),
(1, 3)
select id,
value,
lag(value, 1, 0) over (order by id) as [PreviusValue],
lead(Value, 1, 0) over (order by id) as [NextValue]
from #Test
Results are
id value PreviusValue NextValue
1 1 0 2
1 2 1 3
1 3 2 0
Use an OUTER APPLY to select the top 1 value that has the same R_ID as the first Query and has a higher Key field
Just change the TableName to the actual name of your table in both parts of the query
SELECT a.*, b.TimeCall as NextTimeCall, b.S_ID as NextS_ID FROM
(
SELECT * FROM TableName as a
) as a
OUTER APPLY
(
SELECT TOP 1 FROM TableName as b
WHERE a.R_ID = b.R_ID
AND a.Key > B.Key
ORDER BY Key ASC
) as b
Hope this helps! :)
For older versions, here is one trick using Outer Apply
SELECT a.*,
nexttimecall,
nexts_id
FROM table1 a
OUTER apply (SELECT TOP 1 timecall,s_id
FROM table1 b
WHERE a.r_id = b.r_id
AND a.[key] < b.[key]
ORDER BY [key] ASC) oa (nexttimecall, nexts_id)
LIVE DEMO
Note : It is better to avoid reserved keywords(Key) as column/table names.

SQL Select only rows where foreign key has an attribute

I have this table:
| id | Reader id | Book id | Taken date | Return date |
And, for example, 3 rows
id Reader_id Book_id Taken_date Return_date
1 1 1 1999-01-08 NULL
2 2 2 2015-03-09 2015-04-10
3 1 3 2013-01-01 2014-01-01
I need to get the id's of the readers who have returned books, so all the rows with that id in this table need to have Return_date != NULL. In the case above the query is supposed to return only the second row and not return the last row, because the reader from the last row has not returned a book. How to achieve that?
First identify the Reader_id who has to return books
SELECT Reader_id
FROM yourtable
WHERE Return_date IS NULL
Then select the readers from which is not present in above query result
Use NOT IN
SELECT *
FROM yourtable
WHERE Reader_id NOT IN (SELECT Reader_id
FROM yourtable
WHERE Return_date IS NULL)
Or use Not Exists which can handle NULL values from Sub-Query
SELECT *
FROM yourtable a
WHERE NOT EXISTS (SELECT 1
FROM yourtable b
WHERE b.Return_date IS NULL
AND a.Reader_id = b.Reader_id)
You can do this with a left join :
SELECT * FROM YourTable t
LEFT OUTER JOIN YourTable s
ON(t.reader_id = s.reader_id
and s.id <> t.id
and s.Return_date is null)
WHERE s.id is null
AND t.Return_date is not null
Try this...
select
reader_id
from
tablename
where id not in (SELECT id
FROM tablename
WHERE Return_date IS NULL)