I have two tables, for instance "Employees" and "Projects". I need a list of all projects with the name of all the employes involved.
Problem now is, that the employe_ids are saved with commas, like in the example below:
employe | ID project | employe_id
-------------- -----------------------
Person A | 1 Project X | ,2,
Person B | 2 Project Y |
Person C | 3 Project Z | ,1,3,
select
p.project, e.employe
from
projects p
left join employees e on e.id = p.employe_id ???
How do I have to write the join to get the desired output:
project | employe
--------------------
Project X | Person B,
Project Y |
Project Z | Person A, Person C
You can try group by and listagg as following:
select
p.project,
listagg(e.employe,',') within group (order by e.id) as employee
from
projects p
left join employees e on p.employe_id like '%,' || e.id || ',%'
Group by p.project
Cheers!!
Here's a kind of fun way to do it:
WITH cteId_counts_by_project AS (SELECT PROJECT,
NVL(REGEXP_COUNT(EMPLOYE_ID, '[^,]'), 0) AS ID_COUNT
FROM PROJECTS),
cteMax_id_count AS (SELECT MAX(ID_COUNT) AS MAX_ID_COUNT
FROM cteId_counts_by_project),
cteProject_employee_ids AS (SELECT PROJECT,
EMPLOYE_ID,
REGEXP_SUBSTR(EMPLOYE_ID, '[^,]',1, LEVEL) AS ID
FROM PROJECTS
CROSS JOIN cteMax_id_count m
CONNECT BY LEVEL <= m.MAX_ID_COUNT),
cteProject_emps AS (SELECT DISTINCT PROJECT, ID
FROM cteProject_employee_ids
WHERE ID IS NOT NULL),
cteProject_empnames AS (SELECT pe.PROJECT, pe.ID, e.EMPLOYE
FROM cteProject_emps pe
LEFT OUTER JOIN EMPLOYEES e
ON e.ID = pe.ID
ORDER BY pe.PROJECT, e.EMPLOYE)
SELECT p.PROJECT,
LISTAGG(pe.EMPLOYE, ',') WITHIN GROUP (ORDER BY pe.EMPLOYE) AS EMPLOYEE_LIST
FROM PROJECTS p
LEFT OUTER JOIN cteProject_empnames pe
ON pe.PROJECT = p.PROJECT
GROUP BY p.PROJECT
ORDER BY p.PROJECT
You can certainly compress some of the CTE's together to save space, but I kept them separate so you can see how each little bit adds to the solution.
dbfiddle here
Yet another option; code you need (as you already have those tables) begins at line #12:
SQL> -- Your sample data
SQL> with employees (employee, id) as
2 (select 'Person A', 1 from dual union all
3 select 'Person B', 2 from dual union all
4 select 'Person C', 3 from dual
5 ),
6 projects (project, employee_id) as
7 (select 'Project X', ',2,' from dual union all
8 select 'Project Y', null from dual union all
9 select 'Project Z', ',1,3,' from dual
10 ),
11 -- Employees per project
12 emperpro as
13 (select project, regexp_substr(employee_id, '[^,]+', 1, column_value) id
14 from projects cross join table(cast(multiset(select level from dual
15 connect by level <= regexp_count(employee_id, ',') + 1
16 ) as sys.odcinumberlist))
17 )
18 -- Final result
19 select p.project, listagg(e.employee, ', ') within group (order by null) employee
20 from emperpro p left join employees e on e.id = p.id
21 group by p.project
22 /
PROJECT EMPLOYEE
--------- ----------------------------------------
Project X Person B
Project Y
Project Z Person A, Person C
SQL>
You can extract the numeric IDs from the employee_id column of the projects table by using regexp_substr() and rtrim() ( trimming the last extra comma ) functions together, and then concatenate by listagg() function :
with p2 as
(
select distinct p.*, regexp_substr(rtrim(p.employee_id,','),'[^,]',1,level) as p_eid,
level as rn
from projects p
connect by level <= regexp_count(rtrim(p.employee_id,','),',')
)
select p2.project, listagg(e.employee,', ') within group (order by p2.rn) as employee
from p2
left join employees e on e.id = p2.p_eid
group by p2.project
Demo
Related
So I have written a query to get the cumulative sum of children but I think partition sum has error as its totalling for the parent that is not part of the children.
My fiddle is http://sqlfiddle.com/#!15/88828/1
I have dont a running total but thats wrong. I want the siblings total to a child and child total back to its parent.So basically cumulative total of a child up the tree.
expected output
parent_child_tree id name dimensionvalueid level order_sequence volume cummulative_total
A1 1 A1 (null) 0 1 20 840
-----A1:1 2 A1:1 1 1 1_1 (null) 820
----------A1:1:1 3 A1:1:1 2 2 1_1_2 20 820
-----------A1:1:1:1 4 A1:1:1:1 3 3 1_1_2_3 300 800
-----------A1:1:1:2 5 A1:1:1:2. 3 3 1_1_2_3 500 500
B1 6 B1 (null) 0 6 200 300
-----B1:2 8 B1:2 6 1 6_6 (null) null
-----B1:1 7 B1:1 6 1 6_6 (null) 100
----------B1:2:1 9 B1:2:1 8 2 6_6_8 100 100
To get totals for tree nodes you need to generate hierarchy tree for every node in a subquery like this
SELECT
d.*,
v.volume,
(
WITH RECURSIVE cte AS (
SELECT
dd.id AS branch_id,
dd.id
FROM dimensionvalue dd
WHERE dd.id = d.id
UNION ALL
SELECT
cte.branch_id,
dd.id
FROM dimensionvalue dd
JOIN cte ON dd.dimensionvalueid = cte.id
)
SELECT SUM(v.volume)
FROM cte
JOIN valuation v ON v.dimensionvalueid = cte.id
GROUP BY cte.branch_id
) AS totals
FROM dimensionvalue d
LEFT JOIN valuation v ON v.dimensionvalueid = d.id
ORDER BY d.name;
If you really need all those "decoration" columns that you generate in your query for each tree node than you can combine your recursive CTE hierarchy with subquery for totals calculation like this
WITH RECURSIVE hierarchy AS (
SELECT
d.id,
d.name,
d.dimensionvalueid,
0 AS level,
CAST(d.id AS varchar(50)) AS order_sequence
FROM dimensionvalue d
WHERE d.dimensionvalueid IS NULL
UNION ALL
SELECT
e.id,
e.name,
e.dimensionvalueid,
hierarchy.level + 1 AS level,
CAST(hierarchy.order_sequence || '_' || CAST(hierarchy.id AS VARCHAR(50)) AS VARCHAR(50)) AS order_sequence
FROM hierarchy
JOIN dimensionvalue e ON e.dimensionvalueid = hierarchy.id
)
SELECT
RIGHT('-----------', h.level * 5) || h.name || ' ' AS parent_child_tree,
h.*,
v.volume,
(
WITH RECURSIVE cte AS (
SELECT
dd.id AS branch_id,
dd.id
FROM dimensionvalue dd
WHERE dd.id = h.id
UNION ALL
SELECT
cte.branch_id,
dd.id
FROM dimensionvalue dd
JOIN cte ON dd.dimensionvalueid = cte.id
)
SELECT SUM(v.volume)
FROM cte
JOIN valuation v ON v.dimensionvalueid = cte.id
GROUP BY cte.branch_id
) AS totals
FROM hierarchy h
LEFT JOIN valuation v ON v.dimensionvalueid = h.id
ORDER BY h.name
You can check a working demo here
I have a query to find the youngest generation in a family tree.
This is my database
ID NAME PARENT_ID
1 A 0
2 B 1
3 C 1
4 D 2
5 E 3
6 F 3
7 G 6
I need an output like below if result has more than one row
NAME GENERATIONS
E 4
F 4
or if the result returns single row
NAME GENERATIONS
G 5
This is my query:
WITH RECURSIVE children AS
(
SELECT id, name, parent_id
FROM family
UNION ALL
SELECT f.id, f.name, f.parent_id
FROM family f
INNER JOIN children c ON c.id = f.parent_id
)
SELECT
(
SELECT name
FROM family
WHERE id IS NOT NULL
AND parent_id =
(
SELECT MAX(parent_id) as maxpi
FROM family
)
) AS name,
COUNT(DISTINCT main.parent_id) AS generations
FROM family main
ORDER BY name
How to fix this? And another question is there a way to get result using window funtion?
Calculate the generation as you recurse, and then get the rows that match the max(generation).
WITH RECURSIVE children AS (
SELECT id, name, parent_id, 1 as generation
FROM family
WHERE parent_id not in (SELECT id FROM family)
UNION ALL
SELECT f.id, f.name, f.parent_id, c.generation + 1 as generation
FROM family f
INNER JOIN children c ON c.id = f.parent_id
)
SELECT *
FROM children
WHERE generation = (select max(generation) from children);
This question already has answers here:
LISTAGG in Oracle to return distinct values
(24 answers)
Closed 2 years ago.
hello I need to select a listagg column that only contains distinct values. Unfortunately I am using oracle 18.c and it doesnt support straight distinct option so I probably have to make it with a nested select (not sure?) I have the following SQL query, I need to listagg the "adrml.email" column with distinct values.
select distinct
c.trader_transact
, t.trader_descr
, d.third
, d.f_name
, d.def_phone
, d.def_mail
, d.f_city
, d.country_descr
, d.f_street
, con.first_name
, con.last_name
, adrph.formated_phone_nr
, link.adr
, link.contact
,adrdet.dt
,adrdet.street Contact_Street
,adrdet.post_code
,adrdet.city
,adrdet.country
,adrml.email
,LISTAGG(
adrml.email,
' / '
) WITHIN GROUP(
ORDER BY
d.third
)
from thr_v_third d
join tra_contract c on d.third = c.customer or d.third = c.supplier
join tra_trader t on t.trader = c.trader_transact
join thr_v_adr_lnk_contact link on link.third = d.third --and link.type = 1 and link.default_contact = 1
join adr_contact con on con.adr = link.adr and con.contact = link.contact
join adr_address_det adrdet on adrdet.adr = link.adr and adrdet.last = 1
left join adr_mail adrml on link.adr = adrml.adr and con.contact = adrml.contact and adrml.deflt = 1
left join adr_v_phones adrph on adrph.adr = link.adr and adrph.contact = link.contact and adrph.deflt = 1 and adrph.type = 1
where t.trader = 32
group by
c.trader_transact
, t.trader_descr
,d.third
, d.f_name
, d.def_phone
, d.def_mail
, d.f_city
, d.country_descr
, d.f_street
, link.adr
, link.contact
, con.first_name
, con.last_name
,adrdet.dt
,adrdet.street
,adrdet.post_code
,adrdet.city
,adrdet.country
,adrml.email
, adrph.formated_phone_nr
order by d.third
so far I am getting duplicate emails in the listagg column. How can I clear this out?
This is what you have:
SQL> select d.dname,
2 listagg(e.job, ', ') within group (order by e.job) jobs
3 from dept d join emp e on e.deptno = d.deptno
4 group by d.dname;
DNAME JOBS
-------------- ------------------------------------------------------------
ACCOUNTING CLERK, MANAGER, PRESIDENT
RESEARCH ANALYST, ANALYST, CLERK, CLERK, MANAGER
SALES CLERK, MANAGER, SALESMAN, SALESMAN, SALESMAN, SALESMAN
This is what you want:
SQL> select x.dname,
2 listagg(x.job, ', ') within group (order by x.job) jobs
3 from (select distinct d.dname,
4 e.job
5 from dept d join emp e on e.deptno = d.deptno
6 ) x
7 group by x.dname;
DNAME JOBS
-------------- ------------------------------------------------------------
ACCOUNTING CLERK, MANAGER, PRESIDENT
RESEARCH ANALYST, CLERK, MANAGER
SALES CLERK, MANAGER, SALESMAN
SQL>
So, yes - first find distinct values, then listagg them.
You can use REGEXP_REPLACE and XMLAGG as following to remove the duplicates:
SQL> with table1(req) as
2 (SELECT 'TEJASH' FROM DUAL UNION ALL
3 SELECT 'RIDDHI' FROM DUAL UNION ALL
4 SELECT 'TEJASH' FROM DUAL UNION ALL
5 SELECT 'REKHA' FROM DUAL)
6 SELECT
7 REGEXP_REPLACE(RTRIM(XMLAGG(XMLELEMENT(E, REQ, ',').EXTRACT('//text()')
8 ORDER BY
9 REQ
10 ).GETCLOBVAL(), ','), '([^,]+)(,\1)+', '\1') AS LIST
11 FROM
12 TABLE1;
LIST
--------------------------------------------------------------------------------
REKHA,RIDDHI,TEJASH
SQL>
The reason for using XMLAGG instead of the LISTAGG is to avoid any 4000 characters limit issue.
Using LISTAGG also you can achieve it with the use of REGEXP_REPLACE as following:
SQL> with table1(req) as
2 (SELECT 'TEJASH' FROM DUAL UNION ALL
3 SELECT 'RIDDHI' FROM DUAL UNION ALL
4 SELECT 'TEJASH' FROM DUAL UNION ALL
5 SELECT 'REKHA' FROM DUAL)
6 SELECT
7 REGEXP_REPLACE(
8 LISTAGG(REQ, ',') WITHIN GROUP(
9 ORDER BY
10 REQ
11 ), '([^,]+)(,\1)+', '\1') AS LIST
12 FROM
13 TABLE1;
LIST
--------------------------------------------------------------------------------
REKHA,RIDDHI,TEJASH
SQL>
Cheers!!
i have two tables one Job Category with the structure | id | name | and the other one jobs with the structure | id | job_name | job_category |
How to count how many jobs are in each category?
select c.name, count(j.id)
from job_category c
left join jobs j on j.job_category = c.name
group by c.name
You can do it with a left join (see other answers) or with a subquery:
SELECT
c.Name
, (SELECT COUNT(*) FROM jobs j WHERE j.category_id=c.id) AS Count
FROM job_category c
Why do you need join at all? Is it not
select job_category, count(*)
from jobs
group by job_category
If not, do post your own query (and, possibly, some sample data which might help us help you).
[EDIT, after reading comments]
It appears that my (over)simplified "solution" lacks in some details. True, it shows categories with no jobs, while the OP asked that those should also be displayed having "0" as a result.
Outer join with appropriate COUNT function would fix that; here's an example.
SQL> with job_category(id, name) as
2 (select 1, 'categ 1' from dual union
3 select 2, 'categ 2' from dual
4 ),
5 job (id, job_name, job_category) as
6 (select 100, 'job 1', 1 from dual union
7 select 200, 'job 2', 1 from dual
8 )
9 select c.name, count(j.id)
10 from job_category c left join job j on j.job_category = c.id
11 group by c.name
12 order by c.name;
NAME COUNT(J.ID)
------- -----------
categ 1 2
categ 2 0
SQL>
I have two tables with the below schema:
Table 1
-------
empID
empName
Table 2
-------
empID
department
salary
Assuming the tables are:
Table1:
empID|empName
1 A
2 B
3 C
4 D
5 E
6 F
7 G
8 H
9 I
10 J
Table 2:
empID|department|salary
1 X 10
2 X 10
3 X 10
4 Y 5
5 Y 5
6 Y 5
7 Y 5
8 Y 5
9 Z 3
10 Z 3
I need to find the department name with the highest average salary and display them along with the employee names.
The output I am expecting is:
empName|department|salary
A 10
B X 10
C 10
This was an interview question, and I am recreating this from memory so it might not be perfect. I am also picking up SQL after a gap of more than 2 years. Please suggest if I am missing something.
The query that I have formed is:
SELECT
table1.empName,
TOP(1) AVG(table2.salary),
table2.department
FROM
table1
INNER JOIN
table2
ON table1.empID = table2.empID
GROUP BY
table2.department
Your syntax looks like SQL Server (the "TOP 1"). In that database, I would do something like this:
SELECT TOP (1) WITH TIES t1.empName, t2.salary, t2.department
FROM table1 t1 INNER JOIN
table2 t2
ON t1.empID = t2.empID
ORDER BY AVG(t2.salary) OVER (PARTITION BY t2.department) DESC;
A more generic solution:
SELECT empName, salary, department
FROM (SELECT t.*,
DENSE_RANK() OVER (ORDER BY avg_salary) as seqnum
FROM (SELECT t1.empName, t2.salary, t2.department,
AVG(t2.salary) OVER (PARTITION BY t2.department) as avg_salary
FROM table1 t1 INNER JOIN
table2 t2
ON t1.empID = t2.empID
) t
) t
WHERE seqnum = 1;
Based on what I draw from your question, I would approach it this way.
WITH department_rank AS
(
SELECT
department,
RANK() OVER(ORDER BY avg_salary DESC) AS avg_salary_rank
FROM
(
SELECT
department,
AVG(salary) AS avg_salary
FROM
table2
GROUP BY
department
) tbl
)
SELECT
dept.department,
emp.empID,
emp.empName,
dept.salary
FROM
table2 dept
JOIN
table1 emp
ON (emp.empID = dept.empID)
JOIN
department_rank drnk
ON (drnk.department = dept.department)
AND (drnk.avg_salary_rank = 1) --Top ranked department based on average salary
OUTPUT: