prevent query from return duplicate results - sql

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

Related

Replace some variables by data of another table in sql oracle

I have a table with two columns
type
TXT
A
this is some text for %1 and %2
B
this is another step for %1
in a translation table I have the signification of the variables %X that looks like
Type
variable
descr
A
%1
#person1#
A
%2
#person2#
B
%1
#manager#
I want to replace in my first table all the variables by the description, so the result has to looks like this:
type
TXT
A
this is some text for #person1# and #person2#
B
this is another step for #manager#
I tried with a replace, but I didn't figured out how to make it work
To replace all variables you could use a recursive algorithm:
with data(typ, txt) as (
select 'A', 'this is some text for %1 and %2' from dual union all
select 'B', 'this is another step for %1' from dual
),
translations(typ, var, description) as (
select 'A', '%1', '#person1#' from dual union all
select 'A', '%2', '#person2#' from dual union all
select 'B', '%1', '#manager#' from dual -- union all
),
rtranslations(typ, var, description,rn) as (
select t.*, row_number() over(partition by typ order by var) as rn
from translations t
),
replacecte(typ, txt, replaced_txt, rn) as (
select d.typ, d.txt, replace(d.txt, t.var, t.description), t.rn
from data d
join rtranslations t on t.typ = d.typ
where t.rn = 1
union all
select r.typ, r.txt, replace(r.replaced_txt, t.var, t.description), t.rn
from replacecte r
join rtranslations t on t.typ = r.typ and t.rn = r.rn + 1
)
select r.typ, r.txt, replaced_txt from replacecte r
where rn = length(txt) - length(replace(txt,'%',''))
;
You can also do it this way without recursion. data and descr are of course just mock ups for your tables, you would not need any WITH clauses. This method uses the steps (1) break up the sentences into words, (2) outer join using those words to your description table, replacing any matches with the description values, (3) reassemble the words back into sentences using LISTAGG.
WITH data AS(SELECT 'A' type, 'this is some text for %1 and %2' txt FROM dual
UNION ALL
SELECT 'B' type, 'this is another step for %1' txt FROM dual
),
descr AS (SELECT 'A' type, '%1' variable,'#person1#' description FROM dual
UNION ALL
SELECT 'A' type, '%2' variable,'#person2#' description FROM dual
UNION ALL
SELECT 'B' type, '%1' variable,'#manager#' description FROM dual)
SELECT type,
LISTAGG(new_word,' ') WITHIN GROUP (ORDER BY seq) txt
FROM (SELECT x.type,
NVL(descr.description,x.word) new_word,
seq
FROM (SELECT type,SUBSTR(' '||txt,INSTR(' '||txt,' ',1,seq)+1,INSTR(' '||txt||' ',' ',1,seq+1) - (INSTR(' '||txt,' ',1,seq)+1)) word,seq
FROM data,
(SELECT ROWNUM seq FROM dual CONNECT BY LEVEL <= 50) x) x,
descr
WHERE x.type = descr.type(+)
AND x.word = descr.variable(+))
GROUP BY type
You could use PIVOT to get the var values from rows into columns (geting all vars in the same row with text) and then do multiple replaces depending on number of var values:
SELECT t.A_TYPE,
CASE WHEN d.V3 Is Not Null THEN REPLACE(REPLACE(REPLACE(t.TXT, '%1', d.V1), '%2', d.V2), '%3', d.V3)
WHEN d.V2 Is Not Null THEN REPLACE(REPLACE(t.TXT, '%1', d.V1), '%2', d.V2)
WHEN d.V1 Is Not Null THEN REPLACE(t.TXT, '%1', d.V1)
ELSE t.TXT
END "TXT"
FROM tbl t
INNER JOIN ( SELECT *
FROM ( Select A_TYPE, VAR, DESCRIPTION FROM descr )
PIVOT ( MAX(DESCRIPTION) For VAR IN('%1' "V1", '%2' "V2", '%' "V3") )
) d ON(d.A_TYPE = t.A_TYPE)
With sample data as:
WITH
tbl (A_TYPE, TXT) AS
(
Select 'A', 'this is some text for %1 and %2' From Dual Union All
Select 'B', 'this is another step for %1' From dual
),
descr (A_TYPE, VAR, DESCRIPTION) AS
(
Select 'A', '%1', '#person1#' From Dual UNION ALL
Select 'A', '%2', '#person2#' From Dual UNION ALL
Select 'B', '%1', '#manager#' From Dual
)
... the result should be
A_TYPE TXT
------ -----------------------------------------------
A this is some text for #person1# and #person2#
B this is another step for #manager#

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.

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

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.

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

Keep order from 'IN' clause

Is it possible to keep order from a 'IN' conditional clause?
I found this question on SO but in his example the OP have already a sorted 'IN' clause.
My case is different, 'IN' clause is in random order
Something like this :
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
I would like to retrieve results in (45,2,445,12,789) order. I'm using an Oracle database. Maybe there is an attribute in SQL I can use with the conditional clause to specify to keep order of the clause.
There will be no reliable ordering unless you use an ORDER BY clause ..
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
order by case TestResult.SomeField
when 45 then 1
when 2 then 2
when 445 then 3
...
end
You could split the query into 5 queries union all'd together though ...
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 4
union all
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField = 2
union all
...
I'd trust the former method more, and it would probably perform much better.
Decode function comes handy in this case instead of case expressions:
SELECT SomeField,OtherField
FROM TestResult
WHERE TestResult.SomeField IN (45,2,445,12,789)
ORDER BY DECODE(SomeField, 45,1, 2,2, 445,3, 12,4, 789,5)
Note that value,position pairs (e.g. 445,3) are kept together for readability reasons.
Try this:
SELECT T.SomeField,T.OtherField
FROM TestResult T
JOIN
(
SELECT 1 as Id, 45 as Val FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 445 FROM dual UNION ALL
SELECT 4, 12 FROM dual UNION ALL
SELECT 5, 789 FROM dual
) I
ON T.SomeField = I.Val
ORDER BY I.Id
There is an alternative that uses string functions:
with const as (select ',45,2,445,12,789,' as vals)
select tr.*
from TestResult tr cross join const
where instr(const.vals, ','||cast(tr.somefield as varchar(255))||',') > 0
order by instr(const.vals, ','||cast(tr.somefield as varchar(255))||',')
I offer this because you might find it easier to maintain a string of values rather than an intermediate table.
I was able to do this in my application using (using SQL Server 2016)
select ItemID, iName
from Items
where ItemID in (13,11,12,1)
order by CHARINDEX(' ' + Convert("varchar",ItemID) + ' ',' 13 , 11 , 12 , 1 ')
I used a code-side regex to replace \b (word boundary) with a space. Something like...
var mylist = "13,11,12,1";
var spacedlist = replace(mylist,/\b/," ");
Importantly, because I can in my scenario, I cache the result until the next time the related items are updated, so that the query is only run at item creation/modification, rather than with each item viewing, helping to minimize any performance hit.
Pass the values in via a collection (SYS.ODCINUMBERLIST is an example of a built-in collection) and then order the rows by the collection's order:
SELECT t.SomeField,
t.OtherField
FROM TestResult t
INNER JOIN (
SELECT ROWNUM AS rn,
COLUMN_VALUE AS value
FROM TABLE(SYS.ODCINUMBERLIST(45,2,445,12,789))
) i
ON t.somefield = i.value
ORDER BY rn
Then, for the sample data:
CREATE TABLE TestResult ( somefield, otherfield ) AS
SELECT 2, 'A' FROM DUAL UNION ALL
SELECT 5, 'B' FROM DUAL UNION ALL
SELECT 12, 'C' FROM DUAL UNION ALL
SELECT 37, 'D' FROM DUAL UNION ALL
SELECT 45, 'E' FROM DUAL UNION ALL
SELECT 100, 'F' FROM DUAL UNION ALL
SELECT 445, 'G' FROM DUAL UNION ALL
SELECT 789, 'H' FROM DUAL UNION ALL
SELECT 999, 'I' FROM DUAL;
The output is:
SOMEFIELD
OTHERFIELD
45
E
2
A
445
G
12
C
789
H
fiddle