Delete subtable from a table, SQL - sql

What is the most clever way to delete tuples from table1, that are in the second table,
if the second table is not a part of initial database, but a result of some really big query?
table1 *this table is a result of some query
------------- -------------
| id1 | id2 | | id1 | id2 |
------------- -------------
| 1 2 | | 5 6 |
| 3 4 | | 1 2 |
| 5 6 | | 11 12 |
| 7 8 | -------------
| 9 10 |
| 11 12 |
| 13 14 |
-------------
I came up with
delete from table1
where id1 in (select id1 from ( really long query to get a second table))
and id2 in (select id2 from (the same really long query to get a second table));
It works, but I feel like I'm doing it way too wrong, and not keeping the query DRY.
And would the way you suggest work the same if table1 had an additional column, for example "somecol"?

IMO, You can use EXISTS statement like this:
DELETE FROM table1
WHERE EXISTS (
SELECT 1
FROM (<your long query>) AS dt
WHERE table1.id1 = dt.id1
AND table1.id2 = dt.id2);
[SQL Fiddle Sample]

One method is to use with, delete and exists:
with secondtable as (
<your query here>
)
delete from table1
where exists (select 1
from secondtable st
where table1.id1 = st.id1 and table1.id2 = st.id2
);

A Correlated Subquery using EXISTS allows matching multiple columns:
delete
from table1
where exists
( select * from
(
"really long query"
) as t2
where table1.id1 = t2.id1 -- correlating inner and outer table
and table1.id2 = t2.id2 -- similar to a join-condition
)

Related

How to do an outer join with full result between two tables

I have two tables:
TABLE1
id_attr
-------
1
2
3
TABLE2
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
As a result I want a table that show:
RESULT
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
So I want the row with id=10 and id_attr=3 also when id_Attr=3 is missing in TABLE2 (and I know that because I have a NULL value (or something else) in the val column of RESULT.
NB: I could have others ids in table2. For example, after insert this row on table2: {11,1,A}, as RESULT I want:
id | id_attr | val
----------------------
10 | 1 | A
10 | 2 | B
10 | 3 | NULL
11 | 1 | A
11 | 2 | NULL
11 | 3 | NULL
So, for every id, I want always the match with all id_attr.
Your specific example only has one id, so you can use the following:
select t2.id, t2.id_attr, t2.val
from table2 t2
union all
select 10, t1.id_attr, NULL
from table1 t1
where not exists (select 1 from table2 t2 where t2.id_attr = t1.id_attr);
EDIT:
You can get all combinations of attributes and ids in the following way. Use a cross join to create all the rows you want and then a left join to bring in the data you want:
select i.id, t1.id_attr, t2.val
from (select distinct id from table2) i cross join
table1 t1 left join
table2 t2
on t2.id = i.id and t2.id_attr = t1.id_attr;
It sounds like you want to do just an outer join on id_attr instead of id.
select * from table2 t2
left outer join table1 t1 on t2.id_attr = t1.id_attr;

Retrieve data from the same table if subId is an id of other item

I have a table that contains some records, and I would like to get only these records that have subID to a record with the id of the subID value. If there is no row with the id then do not take this row to the table. Also do not duplicate values if already in the table and do not look at rows that have subId 0 because they are as parents we can say so they do not have childs
----------------------------
ID | SUBID | NAME | ENABLED |
30 | 0 | EXP1 | TRUE |
55 | 30 | EXP2 | TRUE |
70 | 30 | EXP3 | FALSE |
99 | 42 | EXP4 | FALSE |
232| 0 | EXP5 | TRUE |
65 | 232 | EXP6 | TRUE |
-----------------------------
Expected result:
----------------------------
ID | SUBID | NAME | ENABLED |
30 | 0 | EXP1 | TRUE |
55 | 30 | EXP2 | TRUE |
70 | 30 | EXP3 | FALSE |
232| 0 | EXP5 | TRUE |
65 | 232 | EXP6 | TRUE |
-----------------------------
If someone could help me how to write this SQL statement in a good way I will be grateful.
You can use 'Exists':
SELECT T1.* FROM TEST T1
WHERE EXISTS (SELECT T2.ID FROM TEST T2 WHERE T2.ID = T1.SUBID)
OR EXISTS (SELECT T3.SUBID FROM TEST T3 WHERE T3.SUBID = T1.ID)
Test Result:
DB<>Fiddle
How about a union
select a.*
from have a
inner join have b
on a.subid=b.id
union
select b.*
from have a
inner join have b
on a.subid=b.id;
This can be actually pretty complicated, as you've evidently found out. I'd suggest a CTE and a UNION with your JOINs and aliases. It also looks like it'll need all that in a subquery to do a DISTINCT, too.
Without testing this, I'd image it looks something like this:
WITH MAIN AS (
SELECT ID, SUBID, NAME
FROM TABLE t
WHERE ENABLED = TRUE
)
SELECT DISTINCT ID, NAME
FROM (
SELECT ID, NAME
FROM MAIN
UNION
SELECT t.ID, t.NAME
FROM MAIN
LEFT JOIN TABLE t on MAIN.SUBID = t.ID
WHERE MAIN.SUBID <> 0
)
The outer select might not be needed if you do a distinct on each of the inner queries, but without testing it, I can't say for sure. I'd guess it would only DISTINCT the two lists separately, which isn't your intended result.
I'm kind of hoping someone else can come up with a less complicated version. I'd also suggest you do some more research on CTEs, UNIONs, aliases, and see if you can make this simpler on your own. But this should get you in the right direction.
BTW, I used a CTE (WITH MAIN AS) so that the query wouldn't be duplicated.
Try this script-
SELECT YT.*
FROM your_table YT
INNER JOIN (
SELECT DISTINCT(B.ID)
FROM your_table A
LEFT JOIN your_table B
ON A.SUBID = B.ID
WHERE B.ID IS NOT NULL
) C ON YT.ID = C.ID OR YT.SUBID = C.ID
By my understanding of what you are trying to do, you simply want:
SELECT * FROM myTable t1
WHERE SubID = 0
OR EXISTS (SELECT NULL FROM myTable t2 WHERE t2.id = t1.SubID)

What is the correct way from performance perspective to match(replace) every value in every row in temp table using SQL Server 2016 or 2017?

I am wondering what should I use in SQL Server 2016 or 2017 (CTE, LOOP, JOINS, CURSOR, REPLACE, etc) to match (replace) every value in every row in temp table? What is the best solution from performance perspective?
Source Table
|id |id2|
| 1 | 2 |
| 2 | 1 |
| 1 | 1 |
| 2 | 2 |
Mapping Table
|id |newid|
| 1 | 3 |
| 2 | 4 |
Expected result
|id |id2|
| 3 | 4 |
| 4 | 3 |
| 3 | 3 |
| 4 | 4 |
You may join the second table to the first table twice:
WITH cte AS (
SELECT
t1.id AS id_old,
t1.id2 AS id2_old,
t2a.newid AS id_new,
t2b.newid AS id2_new
FROM table1 t1
LEFT JOIN table2 t2a
ON t1.id = t2a.id
LEFT JOIN table2 t2b
ON t1.id2 = t2b.id
)
UPDATE cte
SET
id_old = id_new,
id2_old = id2_new;
Demo
Not sure if you want just a select here, or maybe an update, or an insert into another table. In any case, the core logic I gave above should work for all these cases.
You'd need to apply joins on update query. Something like this:
Update tblA set column1 = 'something', column2 = 'something'
from actualName tblA
inner join MappingTable tblB
on tblA.ID = tblB.ID
this query will compare eachrow with ids and if matched then it will update/replace the value of the column as you desire. :)
Do the self join only
SELECT t1.id2 as id, t2.id2
FROM table1 t
INNER JOIN table2 t1 on t1.id = t.id
INNER JOIN table2 t2 on t2.id = t.id2
This may have best performance from solutions posted here if you have indexes set appropriately:
select (select [newid] from MappingTable where id = [ST].[id]) [id],
(select [newid] from MappingTable where id = [ST].[id2]) [id2]
from SourecTable [ST]

How to get a single result with columns from multiple records in a single table?

Platform: Oracle 10g
I have a table (let's call it t1) like this:
ID | FK_ID | SOME_VALUE | SOME_DATE
----+-------+------------+-----------
1 | 101 | 10 | 1-JAN-2013
2 | 101 | 20 | 1-JAN-2014
3 | 101 | 30 | 1-JAN-2015
4 | 102 | 150 | 1-JAN-2013
5 | 102 | 250 | 1-JAN-2014
6 | 102 | 350 | 1-JAN-2015
For each FK_ID I wish to show a single result showing the two most recent SOME_VALUEs. That is:
FK_ID | CURRENT | PREVIOUS
------+---------+---------
101 | 30 | 20
102 | 350 | 250
There is another table (lets call it t2) for the FK_ID, and it is here that there is a reference
saying which is the 'CURRENT' record. So a table like:
ID | FK_CURRENT | OTHER_FIELDS
----+------------+-------------
101 | 3 | ...
102 | 6 | ...
I was attempting this with a flawed sub query join along the lines of:
SELECT id, curr.some_value as current, prev.some_value as previous FROM t2
JOIN t1 curr ON t2.fk_current = t1.id
JOIN t1 prev ON t1.id = (
SELECT * FROM (
SELECT id FROM (
SELECT id, ROW_NUMBER() OVER (ORDER BY SOME_DATE DESC) as rno FROM t1
WHERE t1.fk_id = t2.id
) WHERE rno = 2
)
)
However the t1.fk_id = t2.id is flawed (i.e. wont run), as (I now know) you can't pass a parent
field value into a sub query more than one level deep.
Then I started wondering if Common Table Expressions (CTE) are the tool for this, but then I've no
experience using these (so would like to know I'm not going down the wrong track attempting to use them - if that is the tool).
So I guess the key complexity that is tripping me up is:
Determining the previous value by ordering, but while limiting it to the first record (and not the whole table). (Hence the somewhat convoluted sub query attempt.)
Otherwise, I can just write some code to first execute a query to get the 'current' value, and then
execute a second query to get the 'previous' - but I'd love to know how to solve this with a single
SQL query as it seems this would be a common enough thing to do (sure is with the DB I need to work
with).
Thanks!
Try an approach with LAG function:
SELECT FK_ID ,
SOME_VALUE as "CURRENT",
PREV_VALUE as Previous
FROM (
SELECT t1.*,
lag( some_value ) over (partition by fk_id order by some_date ) prev_value
FROM t1
) x
JOIN t2 on t2.id = x.fk_id
and t2.fk_current = x.id
Demo: http://sqlfiddle.com/#!4/d3e640/15
Try out this:
select t1.FK_ID ,t1.SOME_VALUE as CURRENT,
(select SOME_VALUE from t1 where p1.id2=t1.id and t1.fk_id=p1.fk_id) as PREVIOUS
from t1 inner join
(
select t1.fk_id, max(t1.id) as id1,max(t1.id)-1 as id2 from t1 group by t1.FK_ID
) as p1 on t1.id=p1.id1

Postgres: "Recursive" query with Array

I have a database (that I can't change) one table looks like that:
| ID:integer | fk:integer | next:[integer array] |
--------------------------------------------------
| 1 | 711 | {4} |
| 2 | 712 | {6} |
| 3 | 788 | |
| 4 | 799 | {7} |
--------------------------------------------------
Now I try to define one Query that as first row the data with ID = 1 and as next rows, all data with the ID that are in the integer array next ({4}) so that my query returns:
| ID:integer | fk:integer | next:[integer array] |
--------------------------------------------------
| 1 | 711 | {4} |
| 4 | 799 | {7} |
--------------------------------------------------
But then stops, so it only results the element with the specified ID and it's next elements.
I tried sth. like this, but I can't get it to work:
SELECT * FROM tablenm WHERE ID = ANY(SELECT next FROM tablenm WHERE ID = 1) AND ID = 1
The current Workaround I use is to first use this query:
SELECT * FROM tablenm WHERE ID = 1
And then for each element in the Array I run the same query with the IDs in a loop programmatically, but this looks like a dirty hack and I hope there is a solutions for this with 1 SQL statement.
You can use = ANY(array) in the JOIN condition:
SELECT t2.*
FROM tbl t1
JOIN tbl t2 ON t2.id = ANY(t1.next)
OR t2.id = t1.id -- add first row
WHERE t1.id = 1 -- enter id to filter on once
ORDER BY (t2.id <> t1.id); -- "first row the data with ID = ?"
Should be fastest.
As #Daniel explained, this form (like your query) includes the first row only once.
If you want a "shorter query":
SELECT t2.*
FROM tbl t1
JOIN tbl t2 ON t2.id = ANY(t1.next || t1.id) -- append id to array
WHERE t1.id = 1; -- not sure if you need ORDER BY
Shorter, but not faster than the first form, because this will be expanded to an equivalent of the first form internally. Test performance with EXPLAIN ANALYZE.
It should be noted that next can even be NULL, because:
SELECT NULL::int[] || 5 --> '{5}'
This doesn't require recursion, just array un-nesting.
This should work:
select * from tablename where id=1
UNION ALL
select * from tablename where id
in (select unnest(next) from tablename where id=1);