Postgres - Combining these two queries - sql

My first query returns the first 10 message ids:
SELECT * from message WHERE $1 IN (creator_id, recipient_id)
AND parent_id is null
ORDER BY date_posted
DESC FETCH FIRST 10 ROW ONLY
[1, 2, 4, 6, 10, 14, 17, 18, 19, 23]
Using each message_id, my second query gets the message_id with the MAX value of each of their Linear trees:
With RECURSIVE recursetree AS
(
SELECT * FROM message WHERE message_id = $1
UNION ALL
SELECT t.*
From message t
JOIN recursetree rt ON rt.message_id = t.parent_id
)
SELECT * from recursetree where parent_id is not distinct
from (select max(parent_id) from recursetree)
Combining these two queries only gets one row which is the max value of the linear tree of the last message_id of 23. How would I get all rows?
with RECURSIVE cte as
(
(
SELECT * from message WHERE $1 IN (creator_id, recipient_id)
AND parent_id is null ORDER BY date_posted DESC
FETCH FIRST 10 ROW ONLY
)
UNION
SELECT t.*
From message t
JOIN cte rt ON rt.message_id = t.parent_id
)
SELECT * FROM cte where parent_id is not distinct
from (select max(parent_id) from cte)

If you want to get the last message of each,
I think you should extract data without children.
select m.*
from message m
left join message child on m.message_id = child.parent_id
where child.message_id is null

Related

PostgreSQL: Combining these two queries

My first query returns the first 10 comments, whose parent_id's are null
SELECT comment_id FROM comments WHERE thread_id = $1
AND parent_id is NULL
ORDER BY upvoted DESC FETCH FIRST 10 ROW ONLY
How would I use each comment id, to perform a recursive query?
My current method is by storing the returned comment_id's in an array
[1, 2, 3, 4, 10, 14, 15, 18, 19, 20]
and then using a for loop to perform a recursive query on each id
var query =
`With RECURSIVE cte AS
(
SELECT * FROM comments WHERE comment_id = $1
UNION
SELECT t.*
From comments t
JOIN cte rt ON rt.comment_id = t.parent_id
)
SELECT * FROM cte`;
for(int i = 0; i < array.size(); i++){
client.query(query, array[i])
. . .
}
So I am wondering how I could do this in a single query instead of storing the ids in an array and then recursive querying each individual id?
You can use your first statement as the non-recursive part of your recursive query. But due to the order by that you need, you need to put that query between parentheses:
With RECURSIVE cte AS
(
(
SELECT *
FROM comments
WHERE thread_id = $1
AND parent_id is NULL
ORDER BY upvoted DESC
FETCH FIRST 10 ROW ONLY
)
UNION
SELECT t.*
From comments t
JOIN cte rt ON rt.comment_id = t.parent_id
)
SELECT * FROM cte`
Alternatively you can do that in a separate CTE:
With RECURSIVE root_nodes AS
(
SELECT *
FROM comments
WHERE thread_id = $1
AND parent_id is NULL
ORDER BY upvoted DESC
FETCH FIRST 10 ROW ONLY
), cte as (
select *
from root_nodes
UNION
SELECT t.*
From comments t
JOIN cte rt ON rt.comment_id = t.parent_id
)
SELECT *
FROM cte;
Note the recursive keyword belongs to the WITH even when the first CTE is not the recursive one.

ORACLE join two table with comma separated ids

I have two tables
Table 1
ID NAME
1 Person1
2 Person2
3 Person3
Table 2
ID GROUP_ID
1 1
2 2,3
The IDs in all the columns above refer to the same ID (Example - a Department)
My Expected output (by joining both the tables)
GROUP_ID NAME
1 Person1
2,3 Person2,Person3
Is there a query with which I can achieve this.
It can be done. You shouldn't do it, but perhaps you don't have the power to change the world. (If you have a say in it, you should normalize your table design - in your case, both the input and the output fail the first normal form).
Answering more as good practice for myself... This solution guarantees that the names will be listed in the same order as the id's. It is not the most efficient, and it doesn't deal with id's in the list that are not found in the first table (it simply discards them instead of leaving a marker of some sort).
with
table_1 ( id, name ) as (
select 1, 'Person1' from dual union all
select 2, 'Person2' from dual union all
select 3, 'Person3' from dual
),
table_2 ( id, group_id ) as (
select 1, '1' from dual union all
select 2, '2,3' from dual
),
prep ( id, lvl, token ) as (
select id, level, regexp_substr(group_id, '[^,]', 1, level)
from table_2
connect by level <= regexp_count(group_id, ',') + 1
and prior id = id
and prior sys_guid() is not null
)
select p.id, listagg(t1.name, ',') within group (order by p.lvl) as group_names
from table_1 t1 inner join prep p on t1.id = p.token
group by p.id;
ID GROUP_NAMES
---- --------------------
1 Person1
2 Person2,Person3
select t2.group_id, listagg(t1.name,',') WITHIN GROUP (ORDER BY 1)
from table2 t2, table1 t1
where ','||t2.group_id||',' like '%,'||t1.id||',%'
group by t2.id, t2.group_id
Normalize you data model, this perversion !!! Сomma separated list should not exist in database. Only individual rows per data unit.

Retrieve values In the same order of values given in IN caluse

I have a query like (MS ACCESS).
The number values in the IN caluse is a variant.In the given example I given only 4.somethimes its more than 30 numbers.
SELECT ID
FROM TableName
WHERE (ID IN (16, 18, 15, 20 ))
Result : 15 ,16,18,20
Is there any way to retrieve values In the same order of values given in IN caluse.
Result should like this Result : 16,18,15,20
You can do this:
SELECT DISTINCT t1.ID
FROM TableName t1
INNER JOIN
(
SELECT 1 sortorder, 16 ID
UNION ALL
SELECT 2, 18
UNION ALL
SELECT 3, 15
UNION ALL
SELECT 4, 20
) t2 ON t1.ID = t2.ID
ORDER BY t2.sortorder
There is no way to tell the db engine to examine the order of values in the IN() list and apply that same order to the ORDER BY.
You could apply the same ordering by using a Switch() expression, but then you would have to keep Switch() and In() synchronized.
SELECT id
FROM YourTable
WHERE id In (16,18,15,20)
ORDER BY
Switch(
id=16,1,
id=18,2,
id=15,3,
id=20,4
);

How do I get records before and after given one?

I have the following table structure:
Id, Message
1, John Doe
2, Jane Smith
3, Error
4, Jane Smith
Is there a way to get the error record and the surrounding records? i.e. find all Errors and the record before and after them.
;WITH numberedlogtable AS
(
SELECT Id,Message,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM logtable
)
SELECT Id,Message
FROM numberedlogtable
WHERE RN IN (SELECT RN+i
FROM numberedlogtable
CROSS JOIN (SELECT -1 AS i UNION ALL SELECT 0 UNION ALL SELECT 1) n
WHERE Message='Error')
WITH err AS
(
SELECT TOP 1 *
FROM log
WHERE message = 'Error'
ORDER BY
id
),
p AS
(
SELECT TOP 1 l.*
FROM log
WHERE id <
(
SELECT id
FROM err
)
ORDER BY
id DESC
)
SELECT TOP 3 *
FROM log
WHERE id >
(
SELECT id
FROM p
)
ORDER BY
id
Adapt this routine to pick out your target.
DECLARE #TargetId int
SET #TargetId = 3
select *
from LogTable
where Id in (-- "before"
select max(Id)
from LogTable
where Id < #TargetId
-- target
union all select #TargetId
-- "after"
union all select min(Id)
from LogTable
where Id > #TargetId)
select id,messag from
(Select (Row_Number() over (order by ID)) as RNO, * from #Temp) as A,
(select SubRNO-1 as A,
SubRNO as B,
SubRNO+1 as C
from (Select (Row_Number() over (order by ID)) as SubRNO, * from #Temp) as C
where messag = 'Error') as B
where A.RNO = B.A or A.RNO = B.B or A.RNO = B.C
;WITH Logs AS
(
SELECT ROW_NUMBER() OVER (ORDER BY id), id, message as rownum FROM LogTable lt
)
SELECT curr.id, prev.id, next.id
FROM Logs curr
LEFT OUTER JOIN Logs prev ON curr.rownum+1=prev.rownum
RIGHT OUTER JOIN Logs next ON curr.rownum-1=next.rownum
WHERE curr.message = 'Error'
select id, message from tbl where id in (
select id from tbl where message = "error"
union
select id-1 from tbl where message = "error"
union
select id+1 from tbl where message = "error"
)
Get fixed number of rows before & after target
Using UNION for a simple, high performance query (I found selected answer WITH query above to be extremely slow)
Here is a high performance alternative to the WITH top selected answer, when you know an ID or specific identifier for a given record, and you want to select a fixed number of records BEFORE and AFTER that record. Requires a number field for ID, or something like date that can be sorted ascending / descending.
Example: You want to select the 10 records before and after a specific error was recorded, you know the error ID, and can sort by date or ID.
The following query gets (inclusive) the 1 result above, the identified record itself, and the 1 record below. After the UNION, the results are sorted again in descending order.
SELECT q.*
FROM(
SELECT TOP 2
id, content
FROM
the_table
WHERE
id >= [ID]
ORDER BY id ASC
UNION
SELECT TOP 1
id, content
FROM
the_table
WHERE
id < [ID]
ORDER BY id DESC
) q
ORDER BY q.id DESC

PostgreSQL recursive with

I need help with a recursive query. Assuming the following table:
CREATE TEMPORARY TABLE tree (
id integer PRIMARY KEY,
parent_id integer NOT NULL,
name varchar(50)
);
INSERT INTO tree (id, parent_id, name) VALUES (3, 0, 'Peter'), (2,0, 'Thomas'), (5,2, 'David'), (1, 0, 'Rob'), (8, 0, 'Brian');
I can retrieve a list of all people and their children with the following query:
WITH RECURSIVE recursetree(id, parent_id) AS (
SELECT id, parent_id FROM tree WHERE parent_id = 0
UNION
SELECT t.id, t.parent_id
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree;
How can I list them in order, and also sort the first level items by name? For example, the desired output would be:
id, parent_id, name
8, 0, "Brian"
3, 0, "Peter"
1, 0; "Rob"
2, 0, "Thomas"
5, 2, " David"
Thanks,
**EDIT. Please note that adding an ORDER BY won't work: **
WITH RECURSIVE recursetree(id, parent_id, path, name) AS (
SELECT
id,
parent_id,
array[id] AS path,
name
FROM tree WHERE parent_id = 0
UNION ALL
SELECT t.id, t.parent_id, rt.path || t.id, t.name
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree ORDER BY path;
The above will retain the parent child relationship (children follow their parents), but applying any other ORDER BY clause (ie: name - like some have suggested) will cause the result to lose it's parent-child relationships.
See also this (translated) article about CTE's in PostgreSQL: wiki.phpfreakz.nl
Edit: Try this one, using an array:
WITH RECURSIVE recursetree(id, parent_ids, firstname) AS (
SELECT id, NULL::int[] || parent_id, name FROM tree WHERE parent_id = 0
UNION ALL
SELECT
t.id,
rt.parent_ids || t.parent_id,
name
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree ORDER BY parent_ids;
You can add a path to your query and order by it at the end:
WITH RECURSIVE recursetree(id, parent_id,path) AS (
SELECT id, parent_id,id||'' as path FROM tree WHERE parent_id = 0
UNION
SELECT t.id, t.parent_id,concat(rt.path,'_',t.id)
FROM tree t
JOIN recursetree rt ON rt.id = t.parent_id
)
SELECT * FROM recursetree
ORDER BY rt.path;