recursive query to replicate/imitate dense_rank - postgresql

BEGIN;
CREATE temp TABLE teacher (
name text,
salary numeric
);
INSERT INTO teacher
VALUES ('b1', 90000);
INSERT INTO teacher
VALUES ('f1', 87000);
INSERT INTO teacher
VALUES ('a', 65000),
('b', 90000),
('c', 40000),
('d', 95000),
('e', 60000),
('f', 87000);
COMMIT;
query
with recursive cte as(
(select name, salary, 1 as rn
from teacher order by salary desc limit 1)
union all
select l.* from cte c cross join lateral(
select name, salary, rn + 1 from teacher t
where t.salary < c.salary
order by salary desc
limit 1
) l
)
table cte order by salary desc;
If all salary are distinct,then above mentioned query can imitate as rank/row_number.
I am wondering how to use recursive query to replicate/imitate dense_rank.
related post: https://dba.stackexchange.com/questions/286627/get-top-two-rows-per-group-efficiently

Related

Delete duplicate rows with different values in columns

I didn't find my case on the Internet. Tell me how i can delete duplicates if the values are in different columns.
I have a table with a lot of values, for example:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
|89417978|89417980|
|89417979|89417980|
I need to exclude duplicates and leave in the answer only:
|Id1|Id2|
|89417980|89417978|
|89417980|89417979|
min/max does not work here, as the values may be different.
I tried to union/join tables on a table/exclude results with temporary tables, but in the end I come to the beginning.
Assuming id1 and id2 are primary keys columns you could try this
DECLARE #tbl table (id1 int, id2 int )
INSERT INTO #tbl
SELECT 89417980, 89417978
UNION SELECT 89417980, 89417979
UNION SELECT 89417978, 89417980
UNION SELECT 89417979, 89417980
SELECT * FROM #tbl
;WITH CTE AS (--Get comparable value as "cs"
SELECT
IIF(id1 > id2, CHECKSUM(id1, id2), CHECKSUM(id2,id1)) as cs
, id1
, id2
, ROW_NUMBER() OVER (order by id1, id2) as rn
FROM #tbl
)
, CTE2 AS ( --Get rows to keep
SELECT MAX (rn) as rn
FROM CTE
GROUP BY cs
HAVING COUNT(*) > 1
)
DELETE tbl -- Delete all except the rows to keep
FROM #tbl tbl
WHERE NOT EXISTS(SELECT 1
FROM CTE2
JOIN CTE ON CTE.rn = CTE2.rn
WHERE CTE.id1 = tbl.id1
AND CTE.id2 = tbl.id2
)
SELECT * FROM #tbl

What would be Postgres equivalent of this T-SQL

I want to run this either interactively from psql or from code.
create proc recent_orders_by_region
as
select top(3) * from view_orders where region = 'NA' order by order_date desc
select top(3) * from view_orders where region = 'WE' order by order_date desc
select top(3) * from view_orders where region = 'EE' order by order_date desc
You can use rank() over () but remember that order must be unique, otherwise RANK will assign the same number to multiple rows in group.
Query with example data
SELECT a.* FROM (
SELECT *,
rank() OVER (
PARTITION BY region
ORDER BY order_date DESC,ident DESC
)
FROM (
values
(1, current_date, 'NA'),(2, current_date-1, 'NA'),
(3, current_date-1, 'NA'),(4, current_date-4, 'NA'),
(5, current_date, 'NA1'),(6, current_date-1, 'NA1'),
(7, current_date-1, 'NA1'),(8, current_date-4, 'NA1')
) view_orders (ident, order_date, region)
) a WHERE RANK <=3
ident is only for demonstration purposes and should be replaced by real column from view_orders table

How to order by result set in union query in T-SQL

I want to use order by clause in my last sql query and I have more than 3 union queries. I do not want to order the top 2 union query but I want to use order by clause in my last sql statement.
Currently, getting error
ORDER BY items must appear in the select list if the statement contains a UNION, INTERSECT or EXCEPT operator.
select 'Total Number of pat' Name, convert(varchar(20), count(id)) Number from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number from table3
union
select x.usertype, count(distinct userid) cnt
from [dbo].table1 t
cross apply (values (
case when t.userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end,
t.userid
)) x(usertype, userid)
where t.date >= dateadd(day,-7, getdate())
group by x.usertype
order by usertype desc
order by is sorting the result of the unions all together however you can introduce a orderIndex column for imlementing the right ordering.
Here the sample:
I've tried to build sample data in the following code.
create table table1(
userid varchar(100),
usertype varchar(100),
date date
)
insert into table1(userid, date) values ('Einsmayr', '2020-10-27')
insert into table1(userid, date) values ('Eins123', '2020-10-27')
insert into table1(userid, date) values ('Einschmid', '2020-10-27')
insert into table1(userid, date) values ('Einshuber', '2020-10-27')
insert into table1(userid, date) values ('Einsreitmayr', '2020-10-27')
create table table2 (
Name varchar(100),
id int
)
insert into table2(Name, id) values('Zweirich', 5)
insert into table2(Name, id) values('Zweifel', 6)
create table table3 (
Name varchar(100),
id int
)
insert into table3(Name, id) values('Dreisinger', 17)
insert into table3(Name, id) values('Dreibert', 18)
This allows the following queries:
select usertype, Number
from (
select 'Total Number of pat' usertype, convert(varchar(20), count(id)) Number, 1 orderIndex from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number, 2 orderIndex from table3
union
select usertype, count(distinct userid) Number, 3 orderIndex
from (
select userid, case when userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end usertype
from table1
where date >= dateadd(day,-7, getdate())
) x
group by x.usertype
) y
order by y.orderIndex, y.usertype
Find the solution here: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ac396c48f5dbcb4a53ad40fac70e9236

SQL Server : group by with corresponding row values

I need to write a T-SQL group by query for a table with multiple dates and seq columns:
DROP TABLE #temp
CREATE TABLE #temp(
id char(1),
dt DateTime,
seq int)
Insert into #temp values('A','2015-03-31 10:00:00',1)
Insert into #temp values('A','2015-08-31 10:00:00',2)
Insert into #temp values('A','2015-03-31 10:00:00',5)
Insert into #temp values('B','2015-09-01 10:00:00',1)
Insert into #temp values('B','2015-09-01 10:00:00',2)
I want the results to contains only the items A,B with their latest date and the corresponding seq number, like:
id MaxDate CorrespondentSeq
A 2015-08-31 10:00:00.000 2
B 2015-09-01 10:00:00.000 2
I am trying with (the obviously wrong!):
select id, max(dt) as MaxDate, max(seq) as CorrespondentSeq
from #temp
group by id
which returns:
id MaxDate CorrespondentSeq
A 2015-08-31 10:00:00.000 5 <-- 5 is wrong
B 2015-09-01 10:00:00.000 2
How can I achieve that?
EDIT
The dt datetime column has duplicated values (exactly same date!)
I am using SQL Server 2005
You can use a ranking subselect to get only the highest ranked entries for an id:
select id, dt, seq
from (
select id, dt, seq, rank() over (partition by id order by dt desc, seq desc) as r
from #temp
) ranked
where r=1;
SELECT ID, DT, SEQ
FROM (
SELECT ID, DT, SEQ, Row_Number()
OVER (PARTITION BY id ORDER BY dt DESC, seq DESC) AS row_number
FROM temp
) cte
WHERE row_number = 1;
Demo : http://www.sqlfiddle.com/#!3/3e3d5/5
With trial and errors maybe I have found a solution, but I'm not completely sure this is correct:
select A.id, B.dt, max(B.seq)
from (select id, max(dt) as maxDt
from #temp
group by id) as A
inner join #temp as B on A.id = B.id AND A.maxDt = B.dt
group by A.id, B.dt
Select id, dt, seq
From #temp t
where dt = (Select Max(dt) from #temp
Where id = t.Id)
If there are duplicate rows, then you also need to specify what the query processor should use to determine which of the duplicates to return. Say you want the lowest value of seq,
Then you could write:
Select id, dt, seq
From #temp t
where dt = (Select Max(dt) from #temp
Where id = t.Id)
and seq = (Select Min(Seq) from #temp
where id = t.Id
and dt = t.dt)

postgres hierarchy - count of child levels and sort by date of children or grandchildren

I would like to know how to write a postgres subquery so that the following table example will output what I need.
id parent_id postdate
1   -1 2015-03-10
2     1 2015-03-11 (child level 1)
3     1 2015-03-12 (child level 1)
4     3 2015-03-13 (child level 2)
5    -1 2015-03-14
6    -1 2015-03-15
7     6 2015-03-16 (child level 1)
If I want to sort all the root ids by child level 1 with a count of children(s) from the parent, the output would be something like this
id count  date
6   2    2015-03-15
1   4    2015-03-10
5   1    2015-03-14
The output is sorted by postdate based on the root's child. The 'date' being outputted is the date of the root's postdate. Even though id#5 has a more recent postdate, the rootid#6's child (id#7) has the most recent postdate because it is being sorted by child's postdate. id#5 doesnt have any children so it just gets placed at the end, sorted by date. The 'count' is the number children(child level 1), grandchildren(child level 2) and itself (root). For instance, id #2,#3,#4 all belong to id#1 so for id#1, the count would be 4.
My current subquery thus far:
SELECT p1.id,count(p1.id),p1.postdate
FROM mytable p1
LEFT JOIN mytable c1 ON c1.parent_id = p1.id AND p1.parent_id = -1
LEFT JOIN mytable c2 ON c2.parent_id = c1.id AND p1.parent_id = -1
GROUP BY p1.id,c1.postdate,p1.postdate
ORDER by c1.postdate DESC,p1.postdate DESC
create table mytable ( id serial primary key, parent_id int references mytable, postdate date );
create index mytable_parent_id_idx on mytable (parent_id);
insert into mytable (id, parent_id, postdate) values (1, null, '2015-03-10');
insert into mytable (id, parent_id, postdate) values (2, 1, '2015-03-11');
insert into mytable (id, parent_id, postdate) values (3, 1, '2015-03-12');
insert into mytable (id, parent_id, postdate) values (4, 3, '2015-03-13');
insert into mytable (id, parent_id, postdate) values (5, null, '2015-03-14');
insert into mytable (id, parent_id, postdate) values (6, null, '2015-03-15');
insert into mytable (id, parent_id, postdate) values (7, 6, '2015-03-16');
with recursive recu as (
select id as parent, id as root, null::date as child_postdate
from mytable
where parent_id is null
union all
select r.parent, mytable.id, mytable.postdate
from recu r
join mytable
on parent_id = r.root
)
select m.id, c.cnt, m.postdate, c.max_child_date
from mytable m
join ( select parent, count(*) as cnt, max(child_postdate) as max_child_date
from recu
group by parent
) c on c.parent = m.id
order by c.max_child_date desc nulls last, m.postdate desc;
You'll need a recursive query to count the elements in the subtrees:
WITH RECURSIVE opa AS (
SELECT id AS par
, id AS moi
FROM the_tree
WHERE parent_id IS NULL
UNION ALL
SELECT o.par AS par
, t.id AS moi
FROM opa o
JOIN the_tree t ON t.parent_id = o.moi
)
SELECT t.id
, c.cnt
, t.postdate
FROM the_tree t
JOIN ( SELECT par, COUNT(*) AS cnt
FROM opa o
GROUP BY par
) c ON c.par = t.id
ORDER BY t.id
;
UPDATE (it appears the OP also wants the maxdate per tree)
-- The same, but also select the postdate
-- --------------------------------------
WITH RECURSIVE opa AS (
SELECT id AS par
, id AS moi
, postdate AS postdate
FROM the_tree
WHERE parent_id IS NULL
UNION ALL
SELECT o.par AS par
, t.id AS moi
-- , GREATEST(o.postdate,t.postdate) AS postdate
, t.postdate AS postdate
FROM opa o
JOIN the_tree t ON t.parent_id = o.moi
)
SELECT t.id
, c.cnt
, t.postdate
, c.maxdate
FROM the_tree t
JOIN ( SELECT par, COUNT(*) AS cnt
, MAX(o.postdate) AS maxdate -- and obtain the max()
FROM opa o
GROUP BY par
) c ON c.par = t.id
ORDER BY c.maxdate, t.id
;
After looking at everyone's code, I created the subquery I needed. I can use PHP to vary the 'case when' code depending on the user's sort selection. For instance, the code below will sort the root nodes based on child level 1's postdate.
with recursive cte as (
select id as parent, id as root, null::timestamp as child_postdate,0 as depth
from mytable
where parent_id = -1
union all
select r.parent, mytable.id, mytable.postdate,depth+1
from cte r
join mytable
on parent_id = r.root
)
select m.id, c.cnt, m.postdate
from ssf.dtb_021 m
join ( select parent, count(*) as cnt, max(child_postdate) as max_child_date,depth
from cte
group by parent,depth
) c on c.parent = m.id
order by
case
when depth=2 then 1
when depth=1 then 2
else 0
end DESC,
c.max_child_date desc nulls last, m.postdate desc;
select
p.id,
(1+c.n) as parent_post_plus_number_of_subposts,
p.postdate
from
table as p
inner join
(
select
parent_id, count(*) as n, max(postdate) as _postdate
from table
group by parent_id
) as c
on p.id = c.parent_id
where p.parent_id = -1
order by c._postdate desc