Oracle SQL How can I write this Join with less code? - sql

Hello Oracle experts I have a question on how to join two tables correctly.
My first table describes an leave category, the minimum years of service time required for new max vacation leave rollover amount and the max rollover numbers.
PTRLVAC_LCAT_CODE PTRLVAC_YEAR PTRLVAC_ROLL_MAX_HRS
C1 0 80
C1 2 88
C1 5 128
P3 0 120
P3 2 128
P3 5 168
The next table details the employee id, hire date, and leave category
PEBEMPL_PIDM PEBEMPL_HIRE_DATE PEBEMPL_LCAT_CODE
1234 01/09/2017 P3
3214 02/01/2014 C1
The join that I have right now relies on a CTE and I'm not sure if it's the easiest solution.
**I've included the tables here as CTEs
with ptrlvac as(
select 'C1' ptrlvac_lcat_code
,0 ptrlvac_year
,80 ptrlvac_roll_max_hrs
from dual union all
select 'C1', 2, 88 from dual union all
select 'C1', 5, 128 from dual union all
select 'P3', 0, 120 from dual union all
select 'P3', 5, 128 from dual union all
select 'P3', 2, 168 from dual
) , pebempl as(
select 1234 pebempl_pidm
,to_date('09-JAN-2017', 'DD-MON-YYYY') pebempl_hire_date
,'P3' pebempl_lcat_code
from dual
UNION ALL
select 3214, to_date('01-FEB-2014','DD-MON-YYYY'), 'C1' from dual
) ,leave as(
select a.ptrlvac_lcat_code
,a.ptrlvac_year
,a.ptrlvac_roll_max_hrs
,row_number()
over(partition by a.ptrlvac_lcat_code
order by a.ptrlvac_year) rn
from ptrlvac a
)
,leave_rules as(
select a.ptrlvac_lcat_code
,a.ptrlvac_year year_start
,nvl(b.ptrlvac_year, 100)-1 year_end
,a.ptrlvac_roll_max_hrs
from leave a
left join leave b
on a.ptrlvac_lcat_code = b.ptrlvac_lcat_code
and a.rn = b.rn - 1
)
select distinct pebempl_pidm
,pebempl_hire_date
,floor(months_between(to_date(:seldate, 'DD-MON-YYYY'), pebempl_hire_date) / 12) as service_years
,pebempl_lcat_code as lcat
,b.ptrlvac_roll_max_hrs
from pebempl a
inner join leave_rules b
on a.pebempl_lcat_code = b.ptrlvac_lcat_code
and floor(months_between(to_date(:seldate, 'DD-MON-YYYY'), pebempl_hire_date) / 12) between b.year_start and b.year_end
Any help to save some keystrokes would be greatly appreciated.
Thanks in advance.

I'm not sure if this does what you want:
select
t2.PEBEMPL_PIDM,
t1.PTRLVAC_ROLL_MAX_HRS
from test1 t1, test2 t2
where
t1.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE and
t1.PTRLVAC_YEAR =
(select max(t1s.PTRLVAC_YEAR) from test1 t1s
where t1s.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE
and (sysdate-PEBEMPL_HIRE_DATE)/365 >= t1s.PTRLVAC_YEAR);
Here are the results I got based on your test data:
PEBEMPL_PIDM PTRLVAC_ROLL_MAX_HRS
------------ --------------------
3214 88
1234 120
Bobby

Had this thought over lunch, further reducing #BobbyDurret's answer:
select
t2.PEBEMPL_PIDM,
max(t1.PTRLVAC_ROLL_MAX_HRS)
from ptrlvac t1, pebempl t2
where
t1.PTRLVAC_LCAT_CODE = t2.PEBEMPL_LCAT_CODE and
(sysdate-PEBEMPL_HIRE_DATE)/365 >= t1.PTRLVAC_YEAR
group by t2.PEBEMPL_PIDM
Assumes the Max_Hrs always increases for more years of service.

Instead of using row_number and filtering in a separate CTE, use lead:
with leave_rules as
(
select a.ptrlvac_lcat_code
,a.ptrlvac_year as year_start
,a.ptrlvac_roll_max_hrs
,lead(ptrlvac_year,1,10000) over (partition by ptrlvac_lcat_code
order by ptrlvac_year)
as year_end
from ptrlvac a
)
select distinct pebempl_pidm
,pebempl_hire_date
,floor(months_between(sysdate, pebempl_hire_date) / 12) as service_years
,pebempl_lcat_code as lcat
,b.ptrlvac_roll_max_hrs
from pebempl a
inner join leave_rules b
on a.pebempl_lcat_code = b.ptrlvac_lcat_code
and floor(months_between(sysdate, pebempl_hire_date) / 12) between year_start and year_end
Combines your 2 CTE's to compute "leave_rules" into 1. I just put sysdate for the date variable so I could test easily - you probably want to use the bind variables as you originally had.

Related

how to write an oracle single query to get data from 2 tables

My system has two tables as below
I need to write a single query in oracle that gives me below result
i understand how to write them in a different query, but in a single query, I have no idea..
here the result is group by craft and planned cost=sum(hr*rate) similarly actual cost
For this sample data you can join the tables and aggregate:
select w.craft,
sum(w.planned * w.rate) "Total Planned (cost)",
sum(l.actual * l.rate) "Total Actual (cost)"
from wplabor w inner join labtrans l
on l.wonum = w.wonum
group by w.craft
Maybe the condition of the ON clause should include equality of the craft columns also:
on l.wonum = w.wonum and l.craft = w.craft
but for this sample data it does not seem to be needed.
One approach is to aggregate before joining:
select p.craft, p.planned, a.actual
from (select craft, sum(hrs * rate) as planned
from wplabor
group by craft
) p join
(select craft, sum(hrs * rate) as actual
from labtrans
group by craft
) a
on p.craft = a.craft;
In your example, all craft are in both tables. If some are missing from either table, you will want an outer join.
Also you may try this solution. This should work even when some craft are missing from either table
with
WPLABOR (WONUM, CRAFT, HRS, RATE) as (
select 50028507, 'AIRSYS', 0.5, 30 from dual union all
select 50028537, 'AIRSYS', 2, 30 from dual union all
select 50031118, 'AIRSYS', 8, 10 from dual union all
select 50031118, 'ELEC', 8, 30 from dual union all
select 50034485, 'ELEC', 0.5, 18 from dual
) ,
LABTRANS (WONUM, CRAFT, HRS, RATE) as (
select 50028507, 'AIRSYS', 0.5, 30 from dual union all
select 50028537, 'AIRSYS', 1, 36 from dual union all
select 50031118, 'AIRSYS', 6, 30 from dual union all
select 50031118, 'ELEC', 8, 30 from dual union all
select 50034485, 'ELEC', 1, 17 from dual
)
SELECT WPLABOR.CRAFT||LABTRANS.CRAFT CRAFT,
SUM(WPLABOR.HRS*WPLABOR.RATE) Total_Planned_cost ,
SUM(LABTRANS.HRS*LABTRANS.RATE) Total_actual_cost FROM WPLABOR
full join
LABTRANS
on 1 = 0
GROUP BY WPLABOR.CRAFT||LABTRANS.CRAFT
db<>fiddle db fiddle link
Therefore assuming your your table names are WPLABOR and LABTRANS respectively. and column names as i have used/assumed. then the query can do the job. you only need this part of the query.
SELECT WPLABOR.CRAFT||LABTRANS.CRAFT CRAFT,
SUM(WPLABOR.HRS*WPLABOR.RATE) Total_Planned_cost ,
SUM(LABTRANS.HRS*LABTRANS.RATE) Total_actual_cost FROM WPLABOR
full join
LABTRANS
on 1 = 0
GROUP BY WPLABOR.CRAFT||LABTRANS.CRAFT

Recursive/hierarchical query in BigQuery

I have a recursion/hierarchical problem that I'm trying to figure out in BigQuery.
I have a list of employees and each employee has a manager ID. I need to be able to enter a single Employee_ID and return an array of every person beneath them.
CREATE TABLE p_RLS.testHeirarchy
(
Employee_ID INT64,
Employee_Name STRING,
Position STRING,
Line_Manager_ID INT64
);
INSERT INTO p_RLS.testHeirarchy (Employee_ID, Employee_Name, Position, Line_Manager_ID)
VALUES(1,'Joe','Worker',11),
(2,'James','Worker',11),
(3,'Jack','Worker',11),
(4,'Jill','Worker',12),
(5,'Jan','Worker',12),
(6,'Jacquie','Worker',13),
(7,'Joaquin','Worker',14),
(8,'Jeremy','Worker',14),
(9,'Jade','Worker',15),
(10,'Jocelyn','Worker',15),
(11, 'Bob', 'Store Manager',16),
(12, 'Bill', 'Store Manager',16),
(13, 'Barb', 'Store Manager',16),
(14, 'Ben', 'Store Manager',17),
(15, 'Burt', 'Store Manager',17),
(16, 'Sally','Group Manager',18),
(17, 'Sam','Group Manager',19),
(18, 'Anna', 'Ops Manager',20),
(19, 'Amy', 'Ops Manager',20),
(20, 'Zoe', 'State Manager', NULL);
My desired output would resemble:
SELECT 20 as Employee_ID, [19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1] as Reports;
SELECT 11 as Employee_ID, [3,2,1] as Reports;
SELECT 1 as Employee_ID, [] as Reports;
I have got the following working but it seems very ugly/inconvenient and doesn't support unlimited levels:
WITH test as (
SELECT L0.Employee_ID, L0.Employee_Name, L0.Position, L0.Line_Manager_ID,
ARRAY_AGG(DISTINCT L1.Employee_ID IGNORE NULLS) as Lvl1,
ARRAY_AGG(DISTINCT L2.Employee_ID IGNORE NULLS) as Lvl2,
ARRAY_AGG(DISTINCT L3.Employee_ID IGNORE NULLS) as Lvl3,
ARRAY_AGG(DISTINCT L4.Employee_ID IGNORE NULLS) as Lvl4,
ARRAY_AGG(DISTINCT L5.Employee_ID IGNORE NULLS) as Lvl5,
ARRAY_AGG(DISTINCT L6.Employee_ID IGNORE NULLS) as Lvl6,
ARRAY_AGG(DISTINCT L7.Employee_ID IGNORE NULLS) as Lvl7
FROM p_RLS.testHeirarchy as L0
LEFT OUTER JOIN p_RLS.testHeirarchy L1 ON L0.Employee_ID = L1.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L2 ON L1.Employee_ID = L2.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L3 ON L2.Employee_ID = L3.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L4 ON L3.Employee_ID = L4.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L5 ON L4.Employee_ID = L5.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L6 ON L5.Employee_ID = L6.Line_Manager_ID
LEFT OUTER JOIN p_RLS.testHeirarchy L7 ON L6.Employee_ID = L7.Line_Manager_ID
WHERE L0.Employee_ID = 16
GROUP BY 1,2,3,4)
SELECT
Employee_ID, ARRAY_CONCAT(
IFNULL(Lvl1,[]),
IFNULL(Lvl2,[]),
IFNULL(Lvl3,[]),
IFNULL(Lvl4,[]),
IFNULL(Lvl5,[]),
IFNULL(Lvl6,[]),
IFNULL(Lvl7,[])) as All_reports
FROM test
Is there a better way to do this? Is a recursive approach possible in BigQuery?
Recursive CTE was recently introduced !
This makes things so much easier
with recursive iterations as (
select line_manager_id, employee_id, 1 pos from your_table
union all
select b.line_manager_id, a.employee_id, pos + 1
from your_table a join iterations b
on b.employee_id = a.line_manager_id
)
select line_manager_id, string_agg('' || employee_id order by pos, employee_id desc) as reports_as_list
from iterations
where not line_manager_id is null
group by line_manager_id
order by line_manager_id desc
If applied to sample data in question - output is
Below is for BigQuery Standard SQL
DECLARE rows_count, run_away_stop INT64 DEFAULT 0;
CREATE TEMP TABLE initialData AS WITH input AS (
SELECT 1 Employee_ID,'Joe' Employee_Name,'Worker' Position,11 Line_Manager_ID UNION ALL
SELECT 2,'James','Worker',11 UNION ALL
SELECT 3,'Jack','Worker',11 UNION ALL
SELECT 4,'Jill','Worker',12 UNION ALL
SELECT 5,'Jan','Worker',12 UNION ALL
SELECT 6,'Jacquie','Worker',13 UNION ALL
SELECT 7,'Joaquin','Worker',14 UNION ALL
SELECT 8,'Jeremy','Worker',14 UNION ALL
SELECT 9,'Jade','Worker',15 UNION ALL
SELECT 10,'Jocelyn','Worker',15 UNION ALL
SELECT 11, 'Bob', 'Store Manager',16 UNION ALL
SELECT 12, 'Bill', 'Store Manager',16 UNION ALL
SELECT 13, 'Barb', 'Store Manager',16 UNION ALL
SELECT 14, 'Ben', 'Store Manager',17 UNION ALL
SELECT 15, 'Burt', 'Store Manager',17 UNION ALL
SELECT 16, 'Sally','Group Manager',18 UNION ALL
SELECT 17, 'Sam','Group Manager',19 UNION ALL
SELECT 18, 'Anna', 'Ops Manager',20 UNION ALL
SELECT 19, 'Amy', 'Ops Manager',20 UNION ALL
SELECT 20, 'Zoe', 'State Manager', NULL
)
SELECT * FROM input;
CREATE TEMP TABLE ttt AS
SELECT Line_Manager_ID, ARRAY_AGG(Employee_ID) Reports FROM initialData WHERE NOT Line_Manager_ID IS NULL GROUP BY Line_Manager_ID;
LOOP
SET (run_away_stop, rows_count) = (SELECT AS STRUCT run_away_stop + 1, COUNT(1) FROM ttt);
CREATE OR REPLACE TEMP TABLE ttt1 AS
SELECT Line_Manager_ID, ARRAY(SELECT DISTINCT Employee_ID FROM UNNEST(Reports) Employee_ID ORDER BY Employee_ID DESC) Reports
FROM (
SELECT Line_Manager_ID, ARRAY_CONCAT_AGG(Reports) Reports
FROM (
SELECT t2.Line_Manager_ID, ARRAY_CONCAT(t1.Reports, t2.Reports) Reports
FROM ttt t1, ttt t2
WHERE (SELECT COUNTIF(t1.Line_Manager_ID = Employee_ID) FROM UNNEST(t2.Reports) Employee_ID) > 0
) GROUP BY Line_Manager_ID
);
CREATE OR REPLACE TEMP TABLE ttt AS
SELECT * FROM ttt1 UNION ALL
SELECT * FROM ttt WHERE NOT Line_Manager_ID IN (SELECT Line_Manager_ID FROM ttt1);
IF (rows_count = (SELECT COUNT(1) FROM ttt) AND run_away_stop > 1) OR run_away_stop > 10 THEN BREAK; END IF;
END LOOP;
SELECT Employee_ID,
(
SELECT STRING_AGG(CAST(Employee_ID AS STRING), ',' ORDER BY Employee_ID DESC)
FROM ttt.Reports Employee_ID
) Reports_as_list
FROM (SELECT DISTINCT Employee_ID FROM initialData) d
LEFT JOIN ttt ON Employee_ID = Line_Manager_ID
ORDER BY Employee_ID DESC;
with result
Row Employee_ID Reports_as_list
1 20 19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1
2 19 17,15,14,10,9,8,7
3 18 16,13,12,11,6,5,4,3,2,1
4 17 15,14,10,9,8,7
5 16 13,12,11,6,5,4,3,2,1
6 15 10,9
7 14 8,7
8 13 6
9 12 5,4
10 11 3,2,1
11 10 null
12 9 null
13 8 null
14 7 null
15 6 null
16 5 null
17 4 null
18 3 null
19 2 null
20 1 null
In case if you need Reports as array - replace last statement in above script with below
SELECT Employee_ID, Reports Reports_as_array
FROM (SELECT DISTINCT Employee_ID FROM initialData) d
LEFT JOIN ttt ON Employee_ID = Line_Manager_ID
ORDER BY Employee_ID DESC;
Note: depends on level of nesting in your hierarchy - you might need to adjust 10 in OR run_away_stop > 10
To the question: "Is a recursive approach possible in BigQuery?"
Yes!
Now that BigQuery supports scripting and loops I solved some recursive problems from the Advent of Code with BigQuery:
https://towardsdatascience.com/advent-of-code-sql-bigquery-31e6a04964d4
CREATE TEMP TABLE planets AS SELECT 'YOU' planet;
LOOP
SET steps = steps+1
;
CREATE OR REPLACE TEMP TABLE planets AS
SELECT DISTINCT planet
FROM (
SELECT origin planet FROM t1 WHERE dest IN (SELECT planet FROM planets)
UNION ALL
SELECT dest planet FROM t1 WHERE origin IN (SELECT planet FROM planets)
)
;
IF 'SAN' IN (SELECT * FROM planets )
THEN LEAVE;
END IF;
END LOOP
;
SELECT steps-2
I would use a similar approach to navigate the graph and annotate all parent relationships.
Soon: I'll write a blog post on the specifics of tree traversal to get everyone under x. But this code will help you in the meantime.

sql-to fetch reciprocal data

I have a table in Oracle db, which contains data like this, a+b, b+a, c+d, d+c.
My question is, when a+b = b+a, how to fetch distinct records.
PK CODE
1000 87DIA4+BAJI204
1001 87DIA4+BIJI939
1002 87DIA4+C3IDI02
1003 87DIA4+C3IZI419
1004 BAJI204+87DIA4
1005 BIJI939+87DIA4
1006 C3IDI02+87DIA4
1007 C3IZI419+87DIA4
Write a sub-query which splits out the two elements from the CODE column. Then use a self-join to find the reciprocal pairs:
SQL> with cte as ( select pk
2 , substr(code, 1, instr(code, '+')-1) as str1
3 , substr(code, instr(code, '+')+1) as str2
4 from your_table)
5 select a.pk as a_pk
6 , b.pk as b_pk
7 , a.str1
8 , a.str2
9 from cte a
10 join cte b
11 on a.str1 = b.str2
12 and a.str2 = b.str1
13 where a.pk < b.pk
14 order by a_pk;
A_PK B_PK STR1 STR2
---------- ---------- ------------ ------------
1000 1004 87DIA4 BAJI204
1001 1005 87DIA4 BIJI939
1002 1006 87DIA4 C3IDI02
1003 1007 87DIA4 C3IZI419
4 rows selected.
SQL>
The WHERE clause ensures the result set contains only one instance of each reciprocal pair.
If I understand your question, you may need something like this:
with yourTable(PK, CODE) as (
select '1000', '87DIA4+BAJI204' from dual union all
select '1001', '87DIA4+BIJI939' from dual union all
select '1002', '87DIA4+C3IDI02' from dual union all
select '1003', '87DIA4+C3IZI419' from dual union all
select '1004', 'BAJI204+87DIA4' from dual union all
select '1005', 'BIJI939+87DIA4' from dual union all
select '1006', 'C3IDI02+87DIA4' from dual union all
select '1007', 'C3IZI419+87DIA4' from dual
)
select least( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)) as A,
greatest( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)) as B
from yourTable
group by least( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1)),
greatest( substr(code, 1, instr(code, '+') -1), substr(code, instr(code, '+') +1))
that gives:
A B
--------------- ---------------
87DIA4 BAJI204
87DIA4 BIJI939
87DIA4 C3IDI02
87DIA4 C3IZI419
The idea is to separate the values in each row and, for each row, evaluate the "smallest" and the "greatest" value. This way, you can use these results to group and get only one row A+B if you have A+B and B+A.
As Aleksej and APC both pointed out, you need to split the code into components. And I am pleased to see they both offered the "right" solution (rather than the "lazy" solution) - to split code, use standard string functions substr() and instr(), not regular expressions.
The solution below also splits the code, in the same way. I use that for a GROUP BY clause, to group together the rows you consider "duplicates". Then from each group I just pick the one with the lowest PK value. NOTE: your problem statement is incomplete, you didn't state WHICH row should be kept from each pair. (Or, more generally, what to do with them... if the requirement is different, the solution below can be adapted.)
In each group I select MIN(PK) - that is self-explanatory. Then, to pick up the CODE value that belongs to that PK, I use the FIRST function; see the documentation if you are not familiar with it. (Some developers aren't.)
This solution reads the base data just once, and there are no joins.
with
test_data ( pk, code ) as (
select 1000, '87DIA4+BAJI204' from dual
union all select 1001, '87DIA4+BIJI939' from dual
union all select 1002, '87DIA4+C3IDI02' from dual
union all select 1003, '87DIA4+C3IZI419' from dual
union all select 1004, 'BAJI204+87DIA4' from dual
union all select 1005, 'BIJI939+87DIA4' from dual
union all select 1006, 'C3IDI02+87DIA4' from dual
union all select 1007, 'C3IZI419+87DIA4' from dual
)
-- End of test data (not part of the solution!) SQL query begins BELOW THIS LINE.
select min(pk) as pk, min(code) keep (dense_rank first order by pk) as code
from test_data
group by least(substr(code, 1, instr(code, '+')-1), substr(code, instr(code, '+')+1)),
greatest(substr(code, 1, instr(code, '+')-1), substr(code, instr(code, '+')+1))
;
PK CODE
---- ---------------
1000 87DIA4+BAJI204
1001 87DIA4+BIJI939
1002 87DIA4+C3IDI02
1003 87DIA4+C3IZI419

prevent query from return duplicate results

I have a database contains information for a telecommunication company with the following table:
SUBSCRIBERS (SUB_ID , F_NAME , L_NANE , DATE_OF_BIRTH , COUNTRY)
LINES (LINE_ID , LINE_NUMBER)
SUBSCRIBERS_LINES (SUB_LINE_ID , SUB_ID "foreign key", LINE_ID "foreign key", ACTIVATION_DATE)
CALLS (CALL_ID , LINE_FROM "foreign key", LINE_TO "foreign key" , START_DATE_CALL, END_DATE_CALL)
I want to retrieve the names of top 3 subscribers who make the highest count number of calls (with duration less than 60 seconds for each call) in specific given day.
So, I write the following query :
with TEMPRESULT AS
(
select * from
(
select CALLS.LINE_FROM , count(*) totalcount
from CALLS
where (((END_DATE_CALL-START_DATE_DATE)*24*60*60)<=60 and to_char(S_DATE,'YYYY-MM-DD')='2015-12-12')
group by CALLS.LINE_FROM
order by totalcount DESC
)
where rownum <= 3
)
select F_NAME,L_NAME
from TEMPRESULT inner join SUBSCRIBERS_LINES on TEMPRESULT.LINE_FROM=SUBSCRIBERS_LINES.line_id inner join SUBSCRIBERS on SUBSCRIBERS_LINES.SUB_ID=SUBSCRIBERS.SUB_ID;
But this query will not work if one of the subscribers has more than one line,
for example:
(X1 has L1 and L2 lines
X2 has L3
X3 has L4)
if X1 talks 20 calls from L1, and 19 calls from L2
X2 talks 15 calls from L3
X3 talks 10 calls from L4
my query will return the following output:
X1
X1
X2
it must return :
X1
X2
X3
how to modify the query to not return duplicate name ?
The subquery must GROUP BY on SUB_ID (not on LINE_FROM). This will provide the total calls of a subscriber and not the top line calls.
In other words move the join in the subquery and group and order by SUB_ID.
DISTINCT in the main query is too late, you will get no duplicates but less results.
Could you try adding the DISTINCT keyword to the SELECT query at the bottom?
Something like this:
with TEMPRESULT AS
(
select * from
(
select CALLS.LINE_FROM , count(*) totalcount
from CALLS
where (((END_DATE_CALL-START_DATE_DATE)*24*60*60)<=60 and to_char(S_DATE,'YYYY-MM-DD')='2015-12-12')
group by CALLS.LINE_FROM
order by totalcount DESC
)
where rownum <= 3
)
select DISTINCT F_NAME,L_NAME
from TEMPRESULT
inner join SUBSCRIBERS_LINES on TEMPRESULT.LINE_FROM = SUBSCRIBERS_LINES.line_id
inner join SUBSCRIBERS on SUBSCRIBERS_LINES.SUB_ID = SUBSCRIBERS.SUB_ID;
In theory (I haven't tested it by creating this database) this should show:
X1
X2
X3
how about something like this
(T represents the result from your query)
WITH t AS
(SELECT 1 id, 'x1' subscriber, 'l1' line FROM dual
UNION ALL
SELECT 2, 'x1', 'l1' FROM dual
UNION ALL
SELECT 3, 'x1', 'l1' FROM dual
UNION ALL
SELECT 4, 'x1', 'l2' FROM dual
UNION ALL
SELECT 5, 'x1', 'l2' FROM dual
UNION ALL
SELECT 6, 'x1', 'l2' FROM dual
UNION ALL
SELECT 6, 'x1', 'l2' FROM dual
UNION ALL
SELECT 7, 'x2', 'l3' FROM dual
UNION ALL
SELECT 8, 'x2', 'l3' FROM dual
UNION ALL
SELECT 9, 'x3', 'l4' FROM dual
),
t1 AS
(SELECT COUNT(subscriber) totalcount,
line,
MAX(subscriber) keep (dense_rank last
ORDER BY line ) subscribers
FROM t
GROUP BY line
ORDER BY 1 DESC
)
SELECT subscribers,
listagg(line
||' had '
|| totalcount
|| ' calls ', ',') within GROUP (
ORDER BY totalcount) AS lines
FROM t1
GROUP BY subscribers
the results
subscribers lines
x1 l1 had 3 calls, l2 had 4 calls
x2 l3 had 2 calls
x3 l4 had 1 calls

SQL hierarchy count totals report

I'm creating a report with SQL server 2012 and Report Builder which must show the total number of Risks at a high, medium and low level for each Parent Element.
Each Element contains a number of Risks which are rated at a certain level. I need the total for the Parent Elements. The total will include the number of all the Child Elements and also the number the Element itself may have.
I am using CTEs in my query- the code I have attached isn't working (there are no errors - it's just displaying the incorrect results) and I'm not sure that my logic is correct??
Hopefully someone can help. Thanks in advance.
My table structure is:
ElementTable
ElementTableId(PK) ElementName ElementParentId
RiskTable
RiskId(PK) RiskName RiskRating ElementId(FK)
My query:
WITH cte_Hierarchy(ElementId, ElementName, Generation, ParentElementId)
AS (SELECT ElementId,
NAME,
0,
ParentElementId
FROM Extract.Element AS FirtGeneration
WHERE ParentElementId IS NULL
UNION ALL
SELECT NextGeneration.ElementId,
NextGeneration.NAME,
Parent.Generation + 1,
Parent.ElementId
FROM Extract.Element AS NextGeneration
INNER JOIN cte_Hierarchy AS Parent
ON NextGeneration.ParentElementId = Parent.ElementId),
CTE_HighRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS HighRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'High'
GROUP BY r.ElementId),
CTE_LowRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS LowRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Low'
GROUP BY r.ElementId),
CTE_MedRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS MedRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Medium'
GROUP BY r.ElementId)
SELECT rd.ElementId,
rd.ElementName,
rd.ParentElementId,
Generation,
HighRisk,
MedRisk,
LowRisk
FROM cte_Hierarchy rd
LEFT OUTER JOIN CTE_HighRisk h
ON rd.ElementId = h.ElementId
LEFT OUTER JOIN CTE_MedRisk m
ON rd.ElementId = m.ElementId
LEFT OUTER JOIN CTE_LowRisk l
ON rd.ElementId = l.ElementId
WHERE Generation = 1
Edit:
Sample Data
ElementTableId(PK) -- ElementName -- ElementParentId
1 ------------------- Main --------------0
2 --------------------Element1-----------1
3 --------------------Element2 ----------1
4 --------------------SubElement1 -------2
RiskId(PK) RiskName RiskRating ElementId(FK)
a -------- Financial -- High ----- 2
b -------- HR --------- High ----- 3
c -------- Marketing -- Low ------- 2
d -------- Safety -----Medium ----- 4
Sample Output:
Element Name High Medium Low
Main ---------- 2 ---- 1 -------1
Here is your sample tables
SELECT * INTO #TABLE1
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
)TAB
SELECT * INTO #TABLE2
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
)TAB
We are finding the children of a parent, its count of High,Medium and Low and use cross join to show parent with all the combinations of its children's High,Medium and Low
UPDATE
The below variable can be used to access the records dynamically.
DECLARE #ElementTableId INT;
--SET #ElementTableId = 1
And use the above variable inside the query
;WITH CTE1 AS
(
SELECT *,0 [LEVEL] FROM #TABLE1 WHERE ElementTableId = #ElementTableId
UNION ALL
SELECT E.*,e2.[LEVEL]+1 FROM #TABLE1 e
INNER JOIN CTE1 e2 on e.ElementParentId = e2.ElementTableId
AND E.ElementTableId<>#ElementTableId
)
,CTE2 AS
(
SELECT E1.*,E2.*,COUNT(RiskRating) OVER(PARTITION BY RiskRating) CNT
from CTE1 E1
LEFT JOIN #TABLE2 E2 ON E1.ElementTableId=E2.ElementId
)
,CTE3 AS
(
SELECT DISTINCT T1.ElementName,C2.RiskRating,C2.CNT
FROM #TABLE1 T1
CROSS JOIN CTE2 C2
WHERE T1.ElementTableId = #ElementTableId
)
SELECT *
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE
RESULT
UPDATE 2
I am updating as per your new requirement
Here is sample table in which I have added extra data to test
SELECT * INTO #ElementTable
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
UNION ALL
SELECT 5, 'Main 2',0
UNION ALL
SELECT 6, 'Element21',5
UNION ALL
SELECT 7, 'SubElement21',6
UNION ALL
SELECT 8, 'SubElement22',7
UNION ALL
SELECT 9, 'SubElement23',7
)TAB
SELECT * INTO #RiskTable
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
UNION ALL
SELECT 'e' , 'Fincancial' ,'High' ,5
UNION ALL
SELECT 'f','HR','High',6
UNION ALL
SELECT 'g','HR','High',6
UNION ALL
SELECT 'h', 'Marketing','Low',7
UNION ALL
SELECT 'i', 'Safety','Medium',8
UNION ALL
SELECT 'j', 'Safety','High',8
)TAB
I have written the logic in query
;WITH CTE1 AS
(
-- Here you will find the level of every elements in the table
SELECT *,0 [LEVEL]
FROM #ElementTable WHERE ElementParentId = 0
UNION ALL
SELECT ET.*,CTE1.[LEVEL]+1
FROM #ElementTable ET
INNER JOIN CTE1 on ET.ElementParentId = CTE1.ElementTableId
)
,CTE2 AS
(
-- Filters the level and find the major parant of each child
-- ie, 100->150->200, here the main parent of 200 is 100
SELECT *,CTE1.ElementTableId MajorParentID,CTE1.ElementName MajorParentName
FROM CTE1 WHERE [LEVEL]=1
UNION ALL
SELECT CTE1.*,CTE2.MajorParentID,CTE2.MajorParentName
FROM CTE1
INNER JOIN CTE2 on CTE1.ElementParentId = CTE2.ElementTableId
)
,CTE3 AS
(
-- Since each child have columns for main parent id and name,
-- you will get the count of each element corresponding to the level you have selected directly
SELECT DISTINCT CTE2.MajorParentName,RT.RiskRating ,
COUNT(RiskRating) OVER(PARTITION BY MajorParentID,RiskRating) CNT
FROM CTE2
JOIN #RiskTable RT ON CTE2.ElementTableId=RT.ElementId
)
SELECT MajorParentName, ISNULL([High],0)[High], ISNULL([Medium],0)[Medium],ISNULL([Low],0)[Low]
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE