Recursive Self-Join in postgres - sql

I have a table of profiles that have data related to the managers of a user.
There can multiple levels of managers for a user.
I need to write a query to get all the managers of a user.
Official manager_id is stored in column text = A.
Table Name profiles
id
text
manager_id
user_id
1
A
20
50
2
B
20
50
3
A
21
20
4
B
NULL
20
5
C
NULL
20
6
A
22
21
7
B
NULL
21
9
A
NULL
22
For example,
If user_id=50 then,
50 manager's is 20
20 manager's is 21
21 manager's is 22
21 manager's is NULL
So, the output should be 20,21,22
Similarly, if user_id=20 the output should be 21,22
I tried a couple of queries but it doesn't return the expected output.

The recursive query you're looking for should feature:
base step, which begins with distinct couples of user_id and manager_id from the input table
recursive step, which self joins the table with the current recursive result, matching manager of the second to be users of the first
Once you get all matches, you can aggregate your values by "user_id" with ARRAY_AGG.
WITH RECURSIVE cte AS (
SELECT DISTINCT user_id, manager_id
FROM tab
WHERE manager_id IS NOT NULL
UNION ALL
SELECT usr.user_id,
mgr.manager_id
FROM cte usr
INNER JOIN tab mgr
ON usr.manager_id = mgr.user_id
WHERE mgr.manager_id IS NOT NULL
)
SELECT user_id,
ARRAY_AGG(manager_id) AS managers
FROM cte
GROUP BY user_id
Check the demo here.

Related

PostgreSQL: how to delete duplicated rows grouped by the value of a column?

Given the following table, I need to delete every row corresponding to a certain "id" whenever all these rows are duplicated in a successive "id". Note that the deletion all rows for a specific "id" should happen only in case that every row between the two ids match (with the exception of the different "id" column).
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
2
Ann
Maths
9
2
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
For this specific input, the updated output table should be:
id
name
subject
score
1
Ann
Maths
9
1
Ann
History
8
3
Ann
Maths
9
3
Ann
History
7
4
Bob
Maths
8
4
Bob
History
8
This because all records between id 1 and 2 are the exactly the same. This doesn't apply for "id" 1 and 3, as long as there's at least one row not in common between the two (id 1 has 8 in History while id 3 has 7 in the same subject).
So it is not as simple as deleting duplicated rows. Here's my attempt:
DELETE FROM table a
USING table b
WHERE a.name = b.name
AND a.subject = b.subject
AND a.score = b.score
AND a.ID < b.ID;
Can you help me?
You can first get all ids that shouldn't be deleted and then exclude them in the WHERE clause of the DELETE statement.
Step 1. In order to match unique ids that are not repeated for all rows, you can use PostgreSQL DISTINCT ON construct, that will allows you to get every row that is not duplicated on the fields "name", "subject", "score". Then retrieve these ids only once with a simple DISTINCT.
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
Step 2. Hence you can build the DELETE statement using the NOT IN operator inside the WHERE clause:
DELETE FROM tab
WHERE id NOT IN (
SELECT DISTINCT id
FROM (SELECT DISTINCT ON (name, subject, score) id
FROM tab
ORDER BY name, subject, score, id) ids_to_keep
);
Check the demo here.

Need to find the count of user who belongs to different depts

I have table with dept,user and so on, I need to find the number of count of user that belongs to different combinations of the dept.
Lets consider I've a table like this:
dept user
1 33
1 33
1 45
2 11
2 12
3 33
3 15
Then I've to find the uniq user and dept combination: something like this:
select distinct dept,user from x;
Which will give me result like :
Dept user
1 33
1 45
2 11
2 12
3 33
3 15
which actually removes the duplicates of the combination:
And here's the thing which i need to do :
My output should look like this:
dep_1_1 dep_1_2 dep_1_3 dep_2_2 dep_2_1 dep_2_3 Dep_3_1 Dep_3_2 Dep_3_3
2 0 1 2 0 0 1 0 2
So, Basically I need to find the count of common users between all the combinations of departments
Thanks for the help
You can get a row for each department combination using a self-join of your Distinct Select:
with cte as
(
select distinct dept,user from x
)
select t1.dept, t2.dept, count(*)
from cte a st1 join cte as t2
on t1.user = t2.user -- same user
and t1.dept < t2.dept -- different department
group by t1.dept, t2.dept
order by t1.dept, t2.dept

Return Two Related Results Based On Nested SELECT

I have the following data in a User Table in a SQL Server Database
ID Name Manager Employee
_________________________________
1 Greg 17 50
2 Bob 50 54
3 Jim 54 65
I'm trying to pull only two records at a time with a single query - here's what I have:
SELECT * FROM USERS WHERE MANAGER ID = 50 AND EXISTS (SELECT * FROM USERS WHERE EMPLOYEE = 50)
However this pulls only the first record. The desired results are records 1 & 2 (or 2 & 3, 3 & 4, etc.). How can I modify this query to return the correct results?
I think you want OR:
SELECT u.*
FROM USERS u
WHERE MANAGER_ID = 50 OR EMPLOYEE = 50;
This can also be written as:
SELECT u.*
FROM USERS u
WHERE 50 IN (MANAGER_ID, EMPLOYEE);

sql join problem

hy!
I have 2 tables. The structure is the above:
students
idStudent name age job code
1 john 18 student 1
2 john 19 programmer 1
3 john 18 developer 2
4 mark 18 student 3
5 mark 19 programmer 1
infos
id address tel code
1 fdsf 00232 1
2 gffdfd 322 1
3 dsdd 1833 2
4 gffg 43333 3
5 fff 1933 1
I want to obtain a table with the structure:
idStudent name age address tel code
1 john 18 1
3 john 18 2
4 mark 18 3
So what I want to do is to obtain a table with unique code and all the other fields to be filled with the first or last (it has no importance for me) data which appear in the table. I don`t want to have duplicated code fields, this is important for me.
select S.IdStudent, MIN(Name) Name, MIN(Age) Age, MIN(Address) Address, MIN(Tel) Tel, MIN(Code) Code
FROM Students S Inner Join Info I ON S.IDStudent = I.Id
group by S.IdStudent
If all you want is to obtain the info in each table for each student, this is a simple JOIN Query:
SELECT e.idStudent, e.name, e.age, i.address, i.tel, e.code
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
If you have duplicated information, you can use a GROUP BY clausule to obtain the info grouped and MIN or MAX to obtain the values of each field you don't group.
EDITED to take in account the comment from user599977:
From your comment I imagine you have duplicated rows in info?, if that's the case you can do something like:
SELECT e.idStudent, max(e.name), max(e.age), max(i.address), max(i.tel), max(e.code)
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
GROUP BY e.idStudent
But I will recommend you to filter for some more predictable field, like last introduced record or something similar.
For example:
SELECT e.idStudent, e.name, e.age, i.address, i.tel, e.code
FROM students e
INNER JOIN infos i ON e.idEstudent=i.id
WHERE i.loadDate= (
SELECT max(loadDate)
FROM infos
WHERE id=e.idStudent
)
Assuming you have some column with a timestamp or some data that allows you to determine which row is the last that the user entered.

Database Query Help required in MySQL

I am looking for help in writing a query of retrieving the values from 2 tables in MySQL.
The scenario is
Table A
ID Name Marks
===================
23 John 67
45 Mark 45
12 Ram 87
Table B has the following Structure
ID Name Evaluation Marks
==============================
45 Mark 34
78 Chris 09
98 Nancy 10
23 John 12
I am trying to write a query, where if I execute the following query
Select "SOMETHING" from Table A where Id=45
I should get Marks Column as 45+34=79, which should fetch and sum from the both the Tables A and Table B.
If I execute the query with the Id=12.
Since the Id=12, does not exists in the Table B, I should get the Marks as 87.
What would a query for the above?
I assume that the id occurs only once in your tables table a, but could be missing in both. If it always exists in table a, you can use a LEFT JOIN instead of the UNION.
SELECT COALESCE(SUM(marks), 0)
FROM
(
SELECT marks FROM a WHERE id = 45
UNION ALL
SELECT SUM(evaluation_marks) AS marks FROM b WHERE id = 45
) x
Edit
If you have all users in table a, then use
SELECT a.marks + COALESCE( SUM( b.evaluation_marks ), 0 )
FROM a
LEFT OUTER JOIN b ON ( b.id = a.id )
WHERE a.id = 45
GROUP BY a.id, a.marks
You should consider changing your table model though. Why do you store name and id twice? Can't you do it like that:
id name marks evaluation marks
=======================================
12 Ram 87 0
23 John 67 12
45 Mark 45 34
78 Chris 0 9
98 Nancy 0 10