Postgres: "Recursive" query with Array - sql

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);

Related

Is there any query to find this id information?

Her is a sample of my table:
+--------+-------+-----+
| name | value | id |
+--------+-------+-----+
| value1 | 1 | 100 |
| value2 | 2 | 100 |
| value1 | 1 | 200 |
| value2 | 3 | 200 |
| value1 | 1 | 300 |
| value2 | 4 | 300 |
| | | |
+--------+-------+-----+
How to setup an SQL query to retrieve the id value 100 for given value1 = 1 and value2 = 2?
If I understand correctly, aggregation with a having clause does what you want:
select id
from t
group by id
having count(*) filter (where name = 'value1' and value = 1) = 1 and
count(*) filter (where name = 'value2' and value = 2) = 1 ;
Group by id and set the condition in the having clause:
select id
from tablename
where (name, value) in (('value1', 1), ('value2', 2))
group by id
having count(distinct value) = 2
See the demo.
A case of relational-division. See:
How to filter SQL results in a has-many-through relation
This query typically more efficient than what has been suggested so far:
SELECT id
FROM (SELECT id FROM tbl WHERE name = 'value1' AND value = 1) t1
JOIN (SELECT id FROM tbl WHERE name = 'value2' AND value = 2) t2 USING (id);
Or equivalent (results in identical query plan):
SELECT id
FROM tbl t1
JOIN tbl t2 USING (id)
WHERE t1.name = 'value1' AND t1.value = 1
AND t2.name = 'value2' AND t2.value = 2;
db<>fiddle here
Not enforcing uniqueness in the query (like other answers do) as I assume one or more constraints in the table already doing so.
Have a "covering" index on (name, value, id). Optimized index variations are possible, depending on your undisclosed table definition.
But first reconsider your relational design if you are in a position to change it. EAV models are typically sub-optimal. See:
Is there a name for this database schema of key values?
SELECT id from TABLE_NAME WHERE value IN (1, 2);
This is will retrieve the id column from your table when value column is either 1 or 2

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)

I want to assign the name of table1 in table 2 thanks

UPDATE table1, (select top 1 [ID],chef,formateur,techni from table1 order by Num desc)
AS x SET table2.ID = x.[ID], table2.Nom = x.[chef], table2.Nom = x.[formateur],
table2.Nom = x.[techni]
WHERE table2.mission="chef" and table2.mission="Formateur" and table2.mission="techni" ;
returns
table2 table 1
ID | mission |Nom| ID |chef|Formateur|techni|
-------------------------------- ----------------------------
1 | chef | | 1 |nom1| nom2 | nom3|
2 | Formateur | | **Result**
3 | techni | | ID | mission | Nom|
--------------------------
1 | chef | nom1|
2 | Formateur| nom2|
3 | techni | nom3|
I want to assign the name of table1 in table 2.
Please i need your help :)
Data structure does not make sense. Table2 has only 3 records?
Your attempted example is updating Table1 when it appears you really want to update Table2. Why do you want to pull from just last record of Table1?
But try:
UPDATE Table2,
(SELECT TOP 1 Table1.ID, Table1.chef, Table1.formateur, Table1.techni
FROM Table1 ORDER BY Table1.ID DESC) AS Q
SET Table2.nom = Switch([mission]="chef",[chef],[mission]="formateur",[formateur],[mission]="techni",[techni]);

Delete subtable from a table, 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
)

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