display team name and parent name using sql query - sql

i have two tables
Teams table
id name
1 A
2 B
3 B1
4 B2
Team_mapping table
id team_id parentid
1 1 0
2 2 0
3 3 2
4 4 2
display should be like this
Team Name Parent Name
A -
B -
B1 B
B2 B
Please help me to write a sql query output same as above display

This is a bad idea. There are two ways to do this,
ltree
hierarchical single table
Using the hierarchical single table is the best fit for you. It's a minor re-organization but it's much more semantic.
CREATE TABLE teams (
id serial PRIMARY KEY,
parent int REFERENCES teams,
name text
);
INSERT INTO teams (id, parent, name) VALUES
( 1, null, 'A' ),
( 2, null, 'B' ),
( 3, 2, 'B1' ),
( 4, 2, 'B2' );
For an example of a recursive query for this..
WITH RECURSIVE t(id,name,parent) AS (
SELECT t1.id, t1.name, ARRAY[]::text[]
FROM teams AS t1
WHERE parent IS NULL
UNION ALL
SELECT t2.id, t2.name, t1.parent || ARRAY[t1.name]
FROM t AS t1
JOIN teams AS t2
ON t2.parent = t1.id
)
SELECT *
FROM t;
id | name | parent
----+------+--------
1 | A | {}
2 | B | {}
3 | B1 | {B}
4 | B2 | {B}
(4 rows)
This allows arbitrarily deep hierarchy.
INSERT INTO teams (id, parent, name) VALUES
( 5, 4, 'Deep' );
Running the same query as above,
id | name | parent
----+------+--------
1 | A | {}
2 | B | {}
3 | B1 | {B}
4 | B2 | {B}
5 | Deep | {B,B2}
(5 rows)

One way is use left join, and join teams twice:
select
coalesce(t1.name, '-') "Team Name", coalesce(t2.name, '-') "Parent Name"
from team_mapping tm
left join teams t1 on tm.team_id = t1.id
left join teams t2 on tm.parentid = t2.id
then you can use subquery in select statement:
select
coalesce((select t.name from teams t where t.id = tm.team_id), '-') "Team Name",
coalesce((select t.name from teams t where t.id = tm.parentid), '-') "Parent Name"
from team_mapping tm
Edit: Previous first answer's join is incorrect, it should be left join in case of there is no parentid in table teams. Besides, for null s, use coalesce to convert to -.
Demo in sqlfiddle.

Related

SQL referrals view

I have a table with users:
id referred_by_user_id
-----------------------------------
1 null
2 1
3 1
4 2
I need to write request to get number of people referred by user in two levels.
First - direct referral (example: user 1 referred users 2 3. count for level 1 = 2)
Second - user 1 referred to users 2 and 3, user 2 referred to user 4. So count for level 2 should be 1
Result of query should be:
id referred_user_tier_one_total referred_user_tier_two_total
------------------------------------------------------------------------------
1 2 1
2 1 null
3 null null
4 null null
I figured out how to count referred_user_tier_one_total:
select
"id", referred_user_tier_one_total
from
"user"
inner join
(select
count(*) as referred_user_tier_one_total, referred_by_user_id
from
"user"
where
"user".referred_by_user_id is not null
group by
"user".referred_by_user_id) ur on "user".id = ur.referred_by_user_id
But I don't understand how to calculate referred_user_tier_two_total. Please, help
UPD:
Thanks #Stoff for SQL Server solution.
Here is the script rewritten for Postgres
WITH RECURSIVE agg AS
(
SELECT
a.ID, a.referred_by_user_id,
COUNT(b.referred_by_user_id) AS "count"
FROM
"user" a
LEFT JOIN
"user" b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.referred_by_user_id
)
SELECT
a.ID,
a.Count AS referred_user_tier_one_total,
CASE
WHEN SUM(b.count) IS NULL
THEN 0
ELSE SUM(b.count)
END AS referred_user_tier_two_total
FROM
agg a
LEFT JOIN
agg b ON a.ID = b.referred_by_user_id
GROUP BY
a.ID, a.Count
ORDER BY
a.ID
Here is a solution which works in Postgres.
We could carry on writing more levels in the same way.
create table referals(
id int,
referred_by_user_id int);
insert into referals values
(1 , null),
(2 , 1),
(3 , 1),
(4 , 2 );
select
t0.id,
count(t1.id) tier1,
count(t2.id) tier2
from
referals t0
left join referals t1
on t0.id = t1.referred_by_user_id
left join referals t2
on t1.id = t2.referred_by_user_id
group by t0.id
id | tier1 | tier2
-: | ----: | ----:
1 | 2 | 1
2 | 1 | 0
3 | 0 | 0
4 | 0 | 0
db<>fiddle here

How can I get full results in SQL query using 3 tables, where 1 of them keeps relation of 2 another?

I need help writing a query to display results I want.
"Table 3 - relations" keeps all relations between table 1 and 2.Often, relation between table 1 and 2 will not exist in table 3 so I want to see missing relation in the results for all Table 1 rows - see expected Results below.
I can't modify these tables - I have only SELECT privilege.
Data and expected result below:
Table 1 - a:
a_id, a_name
e.g.:
1 A
2 B
Table 2 - b:
b_id, b_name
e.g.:
1 X
2 Y
Table 3 - relation:
asset1_id (it's always id from Table 1), asset2_id (it's always id from Table 2), relation_type
e.g.:
1 1 covers
1 2 covers
Expected result:
Table1_name, Table2_name, Table3_relation_type (including NULL for b_name and relation_type when such relation does not exist in Table 3 - relation)
e.g.
A X covers
A Y covers
B NULL NULL
I can't get the 3rd expected line with NULLs.
I think that this query will produce those results.
select a.name as a_name,b.name as b_name, r.relation_type from relation r
join a on a.id=r.asset1_id
join b on b.id=r.asset2_id
union
select a.name as a_name,b.name as b_name,r.relation_type from relation r
full outer join a on a.id=r.asset1_id
full outer join b on b.id=r.asset2_id
where a.id is null or b.id is null
With your data sample you could try this one.
It should work both hive or impala.
SELECT t1.name ,t2.name ,r.relation_type
FROM relation r
FULL OUTER JOIN table1 t1 ON(t1.id = r.id1)
FULL OUTER JOIN table2 t2 ON(t2.id = r.id2);
+------+------+---------------+
| name | name | relation_type |
+------+------+---------------+
| A | X | covers |
| A | Y | covers |
| B | NULL | NULL |
+------+------+---------------+
WITH
cte_A AS (
SELECT id as a_id, name as a_name
FROM a
),
cte_C AS (
SELECT c.asset_id1 as a_id, b.name, c.relation
FROM c
LEFT JOIN b ON c.id=b.asset_id2
)
SELECT cte_A.a_name, cte_C.name as c_name, cte_C.relation
FROM cte_A
LEFT JOIN cte_C ON cte_A.a_id=cte_C.a_id

How to do selection in PostgreSQL with join when more than one row satisfies requirements?

How to do selection to get JSON array in one cell when doing INNER JOIN when there are more than 1 values to join?
ex Tables:
T1:
id | name
1 Tom
2 Dom
T2:
user_id | product
1 Milk
2 Cookies
2 Banana
Naturally I do SELECT * FROM T1 INNER JOIN T2 ON T1.id = T2.user_id.
But then I get:
id | Name | product
1 Tom Milk
2 Dom Cookies
2 Dom Banana
But I want to get:
id | Name | product
1 Tom [{"product":"Milk}]
2 Dom [{"product":"Cookies"}, {"product":"Banana"}]
If I do something with agg functions, then I need to put everything else in GROUP BY, where I have at least 10 arguments. And whole query takes more than 5 minutes.
My T1 is around 4000 rows and T2 around 300 000 rows, each associated with some row in T1.
Is there a better way?
Using LATERAL you can solve it as given example below:
-- The query
SELECT *
FROM table1 t1,
LATERAL ( SELECT jsonb_agg(
jsonb_build_object( 'product', product )
)
FROM table2
WHERE user_id = t1.id
) t2( product );
-- Result
id | name | product
----+------+-------------------------------------------------
1 | Tom | [{"product": "Milk"}]
2 | Dom | [{"product": "Cookies"}, {"product": "Banana"}]
(2 rows)
-- Test data
CREATE TABLE IF NOT EXISTS table1 (
id int,
"name" text
);
INSERT INTO table1
VALUES ( 1, 'Tom' ),
( 2, 'Dom' );
CREATE TABLE IF NOT EXISTS table2 (
user_id int,
product text
);
INSERT INTO table2
VALUES ( 1, 'Milk' ),
( 2, 'Cookies' ),
( 2, 'Banana' );

How to use recursive logic to return only the Root row in a sql table (SQL Server 2008 R2)

This is for SQL Server 2008 R2, I'm a novice at SQL so please be as specific as you can.
Table1 has some recursive structure built into it, where the ParentId is either Null meaning it's the root, or ParentId is the Id of another row in Table1 which denotes it as a child.
Example data set:
Table1Id ParentId
--------------------------------------------
1 NULL
2 1
3 1
4 2
5 NULL
6 2
7 6
8 NULL
9 8
With the above example the table then has the following tree structure with 3 root nodes:
Root 1 5 8
Child(teir1) 2 3 9
Child(teir2) 4 6
Child(tier3) 7
....
Is there a way to return only the Root row given any of the row Ids? For example:
InputId ReturnedRowId
----------------------------
1 1
2 1
3 1
4 1
5 5
6 1
7 1
8 8
9 8
Any help would be appreciated.
You can use a CTE and traverse the hierarchy
IF OBJECT_ID('tempdb..#testData') IS NOT NULL
DROP TABLE #testData
CREATE TABLE #testData (
Table1Id INT
,ParentId INT NULL
)
INSERT INTO #testData ( Table1Id, ParentId )
VALUES
(1, NULL )
,(2, 1 )
,(3, 1 )
,(4, 2 )
,(5, NULL )
,(6, 2 )
,(7, 6 )
,(8, NULL )
,(9, 8 )
DECLARE #InputId INT
SET #InputId = 2 --<<--Change this as appropriate
;WITH cteTraverse
AS
(
SELECT
T.Table1Id, T.ParentId
FROM
#testData T
WHERE
Table1Id = #InputId
UNION ALL
SELECT
T1.Table1Id, T1.ParentId
FROM
#testData T1
INNER JOIN
cteTraverse T2 ON T1.Table1Id = T2.ParentId
)
SELECT
#InputId '#InputId', Table1Id 'ReturnedRowId'
FROM
cteTraverse
WHERE
ParentId IS NULL
This query do the job.
with CTE as
(
Select Table1ID as ID, Table1ID as Ancestor, 0 as level
from Table1
UNION ALL
Select ID, ParentID, level + 1
from Table1
inner join CTE on CTE.Ancestor = Table1.Table1ID
where ParentID is not NULL
)
,
R_only as
(
Select ID as ID, MAX(level) as max_level
from CTE
group by ID
)
select CTE.ID, Ancestor
from CTE inner join R_only on CTE.ID = R_only.ID and CTE.level = R_only.max_level
order by CTE.ID
Here's a script that finds the root nodes for the nodes table you have. What it does:
In the first CTE, recurses the nodes until the parent is found. Recursion keeps track of the depth in column level.
In a second CTE, determines the maximum depth for each node: max_level. This is the depth where the parent was determined.
Select the nodes with maximum depth.
CREATE TABLE #tree(table1_id INT PRIMARY KEY,parent_id INT);
INSERT INTO #tree(table1_id,parent_id)VALUES
(1,NULL),(2,1),(3,1),(4,2),(5,NULL),(6,2),(7,6),(8,NULL),(9,8);
;WITH cte_tr AS (
SELECT table1_id, parent_id, level=0
FROM #tree
UNION ALL
SELECT t_c.table1_id, t_p.parent_id, level=t_c.level+1
FROM cte_tr AS t_c
INNER JOIN #tree AS t_p ON
t_p.table1_id=t_c.parent_id
WHERE t_p.parent_id IS NOT NULL
),
cte_ml AS (
SELECT table1_id, max_level=MAX(level)
FROM cte_tr
GROUP BY table1_id
)
SELECT cte_tr.table1_id, root_node=ISNULL(cte_tr.parent_id,cte_tr.table1_id)
FROM cte_tr
INNER JOIN cte_ml ON
cte_ml.table1_id=cte_tr.table1_id AND
cte_ml.max_level=cte_tr.level
ORDER BY cte_tr.table1_id
DROP TABLE #tree;
Result:
+-----------+-----------+
| table1_id | root_node |
+-----------+-----------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 5 |
| 6 | 1 |
| 7 | 1 |
| 8 | 8 |
| 9 | 8 |
+-----------+-----------+
Use start with and connect by prior. Check this documentation here
http://psoug.org/reference/connectby.html
I am not sure if "connect by" is available in other databases other than Oracle. Check it out.
You could also try with clause. Someone has tried here.
Simulation of CONNECT BY PRIOR of ORACLE in SQL SERVER
With works somewhat like this
with query1 as (select .... from .... where ....),
query2 as (select .... from .... where ....)
select ...
from query1 q1,
query2 q2
where q1.xxxxx = q2.xxxx

How do I print out 'NULL' or '0' values for column values when an element isn't found?

I need to loop through a set of values (less than 10) and see if they are in a table. If so, I need to print out all of the record values, but if the item doesn't exist, I still want it to be included in the printed result, although with NULL or 0 values. So, for example, the following query returns:
select *
from ACTOR
where ID in (4, 5, 15);
+----+-----------------------------+-------------+----------+------+
| ID | NAME | DESCRIPTION | ORDER_ID | TYPE |
+----+-----------------------------+-------------+----------+------+
| 4 | [TEST-1] | | 3 | NULL |
| 5 | [TEST-2] | | 4 | NULL |
+----+-----------------------------+-------------+----------+------+
But I want it to return
+----+-----------------------------+-------------+----------+------+
| ID | NAME | DESCRIPTION | ORDER_ID | TYPE |
+----+-----------------------------+-------------+----------+------+
| 4 | [TEST-1] | | 3 | NULL |
| 5 | [TEST-2] | | 4 | NULL |
| 15| NULL | | 0 | NULL |
+----+-----------------------------+-------------+----------+------+
Is this possible?
To get the output you want, you first have to construct a derived table containing the ACTOR.id values you desire. UNION ALL works for small data sets:
SELECT *
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
With that, you can OUTER JOIN to the actual table to get the results you want:
SELECT x.actor_id,
a.name,
a.description,
a.orderid,
a.type
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
LEFT JOIN ACTOR a ON a.id = x.actor_id
If there's no match between x and a, the a columns will be null. So if you want orderid to be zero when there's no match for id 15:
SELECT x.actor_id,
a.name,
a.description,
COALESCE(a.orderid, 0) AS orderid,
a.type
FROM (SELECT 4 AS actor_id
FROM DUAL
UNION ALL
SELECT 5
FROM DUAL
UNION ALL
SELECT 15
FROM DUAL) x
LEFT JOIN ACTOR a ON a.id = x.actor_id
Well, for that few values, you could do something ugly like this, I suppose:
SELECT
*
FROM
(
SELECT 4 AS id UNION
SELECT 5 UNION
SELECT 15
) ids
LEFT JOIN ACTOR ON ids.id = ACTOR.ID
(That should work in MySQL, I think; for Oracle you'd need to use DUAL, e.g. SELECT 4 as id FROM DUAL...)
That is only possible using a temporary table.
CREATE TABLE actor_temp (id INTEGER);
INSERT INTO actor_temp VALUES(4);
INSERT INTO actor_temp VALUES(5);
INSERT INTO actor_temp VALUES(15);
select actor_temp.id, ACTOR.* from ACTOR RIGHT JOIN actor_temp on ACTOR.id = actor_temp.id;
DROP TABLE actor_temp;
If you know the upper and lower limits on the ID, it's not too bad. Set up a view with all possible ids - the connect by trick is the simplest way - and do an outer join with your real table. Here, I've limited it to values from 1-1000.
select * from (
select ids.id, a.name, a.description, nvl(a.order_id,0), a.type
from Actor a,
(SELECT level as id from dual CONNECT BY LEVEL <= 1000) ids
where ids.id = a.id (+)
)
where id in (4,5,15);
Can you make a table that contains expected actor ids?
If so you can left join from it.
SELECT * FROM expected_actors LEFT JOIN actors USING (ID)