Join Many to One without a match - sql

I'm not sure if the wording of my question is accurate but in short, I have 4 tables I need to access data from. DEPTH, DEPTHSET, DEPTHENTRY & DEPTHSETMM. I need my output to show 1 row per DEPTHSET, per DEPTH and I'm rolling up the data from DEPTHENTRY with a LISTAGG function. My problem is that I don't get a row for every DEPTH if there is not a valid DEPTHENTRY tied to it through the DEPTHSETMM many mapping table.
I've provided sample data through with clauses and then my actual code below that along with the current output and my desired output.
WITH
DEPTH AS (
(SELECT 1 AS "DEPTHID", 'Group' AS "NAME" FROM DUAL) UNION
(SELECT 2 AS "DEPTHID", 'Branch' AS "NAME" FROM DUAL) UNION
(SELECT 3 AS "DEPTHID", 'Area' AS "NAME" FROM DUAL) UNION
(SELECT 4 AS "DEPTHID", 'Dept' AS "NAME" FROM DUAL) UNION
(SELECT 5 AS "DEPTHID", 'Shift' AS "NAME" FROM DUAL) UNION
(SELECT 6 AS "DEPTHID", 'Rpt' AS "NAME" FROM DUAL) UNION
(SELECT 7 AS "DEPTHID", 'Code' AS "NAME" FROM DUAL)
),
DEPTHSET AS (
(SELECT 3705 AS "DEPTHSETID", 'Idaho Set' AS "NAME" FROM DUAL)
),
DEPTHSETMM AS (
(SELECT 3705 AS "DEPTHSETID", 1410 AS "ENTRYID" FROM DUAL) UNION
(SELECT 3705 AS "DEPTHSETID", 1420 AS "ENTRYID" FROM DUAL) UNION
(SELECT 3705 AS "DEPTHSETID", 1421 AS "ENTRYID" FROM DUAL) UNION
(SELECT 3705 AS "DEPTHSETID", 1430 AS "ENTRYID" FROM DUAL)
),
DEPTHENTRY AS (
(SELECT 1410 AS "ENTRYID", 'North West' AS "NAME", 1 AS "DEPTHID" FROM DUAL) UNION
(SELECT 1420 AS "ENTRYID", 'Zone 1' AS "NAME", 3 AS "DEPTHID" FROM DUAL) UNION
(SELECT 1421 AS "ENTRYID", 'Zone 2' AS "NAME", 3 AS "DEPTHID" FROM DUAL) UNION
(SELECT 1430 AS "ENTRYID", 'A' AS "NAME", 7 AS "DEPTHID" FROM DUAL)
)
SELECT
DST.name AS "DEPTH_SET_NAME",
DEP.depthid AS "DEPTHID",
DEP.name AS "DEPTH_NAME",
LISTAGG(CAST(DEE.name AS varchar2(2000)), '; ') WITHIN GROUP (ORDER BY DEE.name DESC) AS "ENTRY_NAME"
FROM DEPTHSETMM DMM
LEFT OUTER JOIN DEPTHENTRY DEE ON (DMM.entryid = DEE.entryid)
LEFT OUTER JOIN DEPTH DEP ON (DEE.depthid = DEP.depthid)
LEFT OUTER JOIN DEPTHSET DST ON (DMM.depthsetid = DST.depthsetid)
GROUP BY DST.name, DEP.depthid, DEP.name
ORDER BY DST.name, DEP.depthid
Current Output
DEPTH_SET_NAME DEPTHID DEPTH_NAME ENTRY_NAME
Idaho Set 1 Group North West
Idaho Set 3 Area Zone 2; Zone 1
Idaho Set 7 Code A
Desired Output
DEPTH_SET_NAME DEPTHID DEPTH_NAME ENTRY_NAME
Idaho Set 1 Group North West
Idaho Set 2 Branch NULL
Idaho Set 3 Area Zone 2; Zone 1
Idaho Set 4 Dept NULL
Idaho Set 5 Shift NULL
Idaho Set 6 Rpt NULL
Idaho Set 7 Code A

You can cross-join between DEPTHSET and DEPTH, and then outer-join to the remaining tables:
WITH ...
SELECT
DST.name AS "DEPTH_SET_NAME",
DEP.depthid AS "DEPTHID",
DEP.name AS "DEPTH_NAME",
LISTAGG(CAST(DEE.name AS varchar2(2000)), '; ') WITHIN GROUP (ORDER BY DEE.name DESC) AS "ENTRY_NAME"
FROM DEPTHSET DST
CROSS JOIN DEPTH DEP
LEFT OUTER JOIN DEPTHENTRY DEE ON (DEE.depthid = DEP.depthid)
LEFT OUTER JOIN DEPTHSETMM DMM ON (DMM.entryid = DEE.entryid)
AND (DMM.depthsetid = DST.depthsetid)
GROUP BY DST.name, DEP.depthid, DEP.name
ORDER BY DST.name, DEP.depthid;
DEPTH_SET DEPTHID DEPTH_ ENTRY_NAME
--------- ---------- ------ ------------------------------
Idaho Set 1 Group North West
Idaho Set 2 Branch
Idaho Set 3 Area Zone 2; Zone 1
Idaho Set 4 Dept
Idaho Set 5 Shift
Idaho Set 6 Rpt
Idaho Set 7 Code A
Notice the AND (DMM.depthsetid = DST.depthsetid) in the second outer join, replacing what used to be an indirect condition.

Related

2 independent left join queries won't work together

Ultimately I want my output to be a pivot query similar to below which I am comfortable doing
e.g.
Date CO RU ER AB
1/1/18 5 20 0 0
2/1/18 0 5 0 0
3/1/18 0 0 0 0
4/1/18 1 0 0 0
However, to get to that point I want to fill my data set with zero where no data exists
The table holds data similar to the following
Date/time Process_type Status
1/1/18 10:05 150 RU
2/1/18 14:00 150 CO
4/1/18 18:00 100 ER
On any given day there could be no processes.
I have written 2 queries whose purpose was to fill the gaps in the data. ie. fill days and statuses with zero counts where that combination does not exist.
This is a date range query that ensures zeroes are returned if the count is zero for dates where there is no info. This would give something similar to below
Date Count
1/1/18 25
2/1/18 5
3/1/18 0
4/1/18 1
This is a status query that ensures zeroes are returned if that zero is not status is not present
status count
AB 0
RU 2
CO 25
ER 0
I want to join the 2 queries so that I will get zeroes for both dates and status if the count is zero.
Date Status Count
1/1/18 AB 0
1/1/18 CO 0
1/1/18 ER 0
1/1/18 RU 0
2/1/18 AB 0
2/1/18 CO 6
3/1/18 ER 0
4/1/18 RU 1
When I join them up in Query 3 it wont run and gets the following error. I have tried a few different ways with no joy.
Error report -
SQL Error: ORA-00904: "TD"."TMP_DATE": invalid identifier
00000 - "%s: invalid identifier"
Query 1
--
-- Working out dates with nulls if zero count
--
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
)
select
count(pi.crtd_tstmp),
td.tmp_date
from
tmp_dates td
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
group by
td.tmp_date
order by
tmp_date;
Query 2
--
-- Working with Categories with zero if no category
--
with status_table as (
select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual
)
select
count(pi.crtd_tstmp),
st.instanceid
from
status_table st
left join procedure_instance pi
on (st.instanceid = pi.stat and proc_oid = 150)
group by
st.instanceid
order by
st.instanceid;
Query 3
--
-- join together
--
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
),
status_table as (
select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual
)
select
count(pi.crtd_tstmp),
td.tmp_date,
st.instanceid
from
tmp_dates td,
status_table st
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
left join procedure_instance pi
on (st.instanceid = pi.stat and proc_oid = 150)
group by
td.tmp_date,
st.instanceid
order by
tmp_date;
Maybe something like this with only one table in the from clause?
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 5
),status_table as (select 'CO' as instanceid from dual union
select 'RU' as instanceid from dual union
select 'ER' as instanceid from dual union
select 'AB' as instanceid from dual)
select count(pi.crtd_tstmp), td.tmp_date, st.instanceid
from tmp_dates td
left join procedure_instance pi on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
right join status_table st on (st.instanceid = pi.stat and proc_oid = 150)
group by td.tmp_date,st.instanceid
order by tmp_date;
I decided to get round this a different way, I used the pivot to fill the process statutes instead of using query 2 and the second join. Works exactly as wanted now.
select Process_created,
nvl(Complete, 0) as Complete,
nvl(Error, 0) as Error,
nvl(Running, 0) as Running,
nvl(Abort, 0) as Abort
from(
with tmp_dates as (
select trunc(sysdate) - level + 1 as tmp_date
from dual
connect by level <= 30
)
select
count(pi.crtd_tstmp) as number_of,
td.tmp_date as Process_created,
pi.stat as status
from
tmp_dates td
left join procedure_instance pi
on (td.tmp_date = trunc(pi.crtd_tstmp) and proc_oid = 150)
group by
td.tmp_date,pi.stat
order by
tmp_date)src
pivot(
sum(number_of)
for status in ('CO' as Complete, 'ER' as Error, 'RU' as Running, 'AB' as Abort )) piv order by process_created;
Try this and let me know what errors you get
--
-- join together
--
WITH tmp_dates
AS (
SELECT trunc(sysdate) - LEVEL + 1 AS tmp_date
FROM dual connect BY LEVEL <= 5
)
,status_table
AS (
SELECT 'CO' AS instanceid FROM dual
UNION
SELECT 'RU' AS instanceid FROM dual
UNION
SELECT 'ER' AS instanceid FROM dual
UNION
SELECT 'AB' AS instanceid FROM dual
)
SELECT count(pi.crtd_tstmp)
,td.tmp_date
,st.instanceid
FROM tmp_dates td
,status_table st
LEFT JOIN procedure_instance pi ON (
td.tmp_date = trunc(pi.crtd_tstmp)
AND proc_oid = 150
)
LEFT JOIN procedure_instance pi2 ON (
st.instanceid = pi2.stat
AND proc_oid = 150
)
GROUP BY td.tmp_date
,st.instanceid
ORDER BY td.tmp_date;

SQL row values to columns

I need help with something similar to PIVOT, but it isn't that easy.
I have table with this structure, and i need to get structure something like in the bottom of the picture:
Can anyone help me?
Thank you guys!
Here is a solution using a pivot:
with testdata as
(select 'first' casekey, 1 custom_field_id, 'value1' custom_field_value from dual union all
select 'first' casekey, 2 custom_field_id, 'value2' custom_field_value from dual union all
select 'first' casekey, 3 custom_field_id, 'value3' custom_field_value from dual union all
select 'second' casekey, 6 custom_field_id, 'value1' custom_field_value from dual union all
select 'second' casekey, 9 custom_field_id, 'value1' custom_field_value from dual)
select *
from(
select casekey, custom_field_id, custom_field_value,
rank() over(partition by casekey order by custom_field_id) pivotRank
from testdata)
pivot(
max(custom_field_id || ':' || custom_field_value)
for pivotRank in (1 as CF1, 2 as CF2, 3 as CF3, 4 as CF4, 5 as CF5)
)
First I use a windowing function to rank the custom_field_id column partitioned by casekey. Then all you have to do is take the max of the concatenated fields you wanted and pivot 1 through 5 on the pivotRank.
My output for the above query looks like this:
casekey CF1 CF2 CF3 CF4 CF5
first 1:value1 2:value2 3:value3 (null) (null)
second 6:value1 9:value1 (null) (null) (null)
try this:
with y as (
select cas, cfi || ':' || cfv cf, ROW_NUMBER() over (partition by cas order by cfi) n
from x)
, z AS (
select DISTINCT cas
FROM x
)
select z.cas, cf1.cf, cf2.cf, cf3.cf, cf4.cf, cf5.cf
from z
left join y as cf1 on z.cas = cf1.cas and cf1.n = 1
left join y as cf2 on z.cas = cf2.cas and cf1.n = 2
left join y as cf3 on z.cas = cf3.cas and cf1.n = 3
left join y as cf4 on z.cas = cf4.cas and cf1.n = 4
left join y as cf5 on z.cas = cf5.cas and cf1.n = 5

RECURSIVE CTE SQL - Find next available Parent

I have parent child relation SQL table
LOCATIONDETAIL Table
OID NAME PARENTOID
1 HeadSite 0
2 Subsite1 1
3 subsite2 1
4 subsubsite1 2
5 subsubsite2 2
6 subsubsite3 3
RULESETCONFIG
OID LOCATIONDETAILOID VALUE
1 1 30
2 4 15
If i provide Input as LOCATIONDETAIL 6, i should get RULESETCONFIG value as 30
because for
LOCATIONDETAIL 6, parentid is 3 and for LOCATIONDETAIL 3 there is no value in RULESETCONFIG,
LOCATIONDETAIL 3 has parent 1 which has value in RULESETCONFIG
if i provide Input as LOCATIONDETAIL 4, i should get RULESETCONFIG value 15
i have code to populate the tree, but don't know how to find the next available Parent
;WITH GLOBALHIERARCHY AS
(
SELECT A.OID,A.PARENTOID,A.NAME
FROM LOCATIONDETAIL A
WHERE OID = #LOCATIONDETAILOID
UNION ALL
SELECT A.OID,A.PARENTOID,A.NAME
FROM LOCATIONDETAIL A INNER JOIN GLOBALHIERARCHY GH ON A.PARENTOID = GH.OID
)
SELECT * FROM GLOBALHIERARCHY
This will return the next parent with a value. If you want to see all, remove the top 1 from the final select.
dbFiddle
Example
Declare #Fetch int = 4
;with cteHB as (
Select OID
,PARENTOID
,Lvl=1
,NAME
From LOCATIONDETAIL
Where OID=#Fetch
Union All
Select R.OID
,R.PARENTOID
,P.Lvl+1
,R.NAME
From LOCATIONDETAIL R
Join cteHB P on P.PARENTOID = R.OID)
Select Top 1
Lvl = Row_Number() over (Order By A.Lvl Desc )
,A.OID
,A.PARENTOID
,A.NAME
,B.Value
From cteHB A
Left Join RULESETCONFIG B on A.OID=B.OID
Where B.VALUE is not null
and A.OID <> #Fetch
Order By 1 Desc
Returns when #Fetch=4
Lvl OID PARENTOID NAME Value
2 2 1 Subsite1 15
Returns when #Fetch=6
Lvl OID PARENTOID NAME Value
1 1 0 HeadSite 30
This should do the job:
;with LV as (
select OID ID,PARENTOID PID,NAME NAM, VALUE VAL FROM LOCATIONDETAIL
left join RULESETCONFIG ON LOCATIONDETAILOID=OID
), GH as (
select ID gID,PID gPID,NAM gNAM,VAL gVAL from LV where ID=#OID
union all
select ID,PID,NAM,VAL FROM LV INNER JOIN GH ON gVAL is NULL AND gPID=ID
)
select * from GH WHERE gVAL>0
See here for e little demo: http://rextester.com/OXD40496

select unic ctn and group them by region

I have 3 tables
sub
ctn region
1 a
1 a
2 b
3 c
8 n
mta
ctn
1
1
2
3
4
rcr
ctn
1
1
2
3
4
5
I need to find the number of distinct users in every region. In this case the result would be
res
a 1
b 1
c 1
n 1
null 2
if the user isn't in any region then I need to know how many of users like him there are.
What I have so far.
WITH com as(
SELECT DISTINCT ctn
FROM (
SELECT ctn
FROM mta
UNION ALL
SELECT ctn
FROM rcr
) c
)
, distinct_ctn as(
SELECT DISTINCT sub.ctn, com.ctn, sub.region
FROM sub
FULL JOIN com
ON sub.ctn = com.ctn
)
SELECT region, count(*)
FROM distinct_ctn
GROUP BY region;
Have a sub-query where you use UNION to get the distinct cnt values from mta and rcr tables.
RIGHT JOIN table sub with that result, and finally GROUP BY the join result.
select s.region, count(distinct u.ctn)
from sub s
right join (select ctn from mta
union
select ctn from rcr) u
on s.ctn = u.ctn
group by s.region

Oracle SQL Return at least one row

I have an Oracle SQL query where I would like to return all rows where a certain column has a value, but if none of the rows have a value for that column then I would like to return one of the blank rows. How can this be accomplished?
My scenario is a learning path with courses that are to completed, but I want to show the percentage completion. Obviously if there are no courses completed I would like to show the learning path with a zero percentage completion.
The query is very complicated so I rather provide a dummy scenario below:
learning_paths
--------------
learning_path_id learning_path_name
1 Oracle Developer
2 Python Developer
courses
-------
course_id course_name
--------- -----------
1 Oracle SQL
2 Oracle PL/SQL
3 Python
4 Django
5 NLTK
learning_path_items
-------------------
learning_path_id course_id
---------------- ---------
1 1
1 2
2 3
2 4
2 5
learning_path_enrollments
-------------------------
employee_id learning_path_id
----------- ----------------
1 1
2 2
3 2
course_enrollments
------------------
employee_id course_id
----------- ---------
2 3
2 4
So my results should be:
employee_id learning_path_id course_id completion
----------- ---------------- --------- ----------
1 1 0 out of 2
2 2 3 2 out of 3
2 2 4 2 out of 3
3 2 0 out of 3
Below is the actual query, which is very complicated and still a work in progress.
select sub2.director,
sub2.cost_centre,
sub1.employee_number,
sub1.employee_name,
sub2.job_title,
sub1.course_name,
sub1.learning_path_name,
sub2.course_start_date,
sub2.course_end_date,
max(sub2.course_end_date) over (partition by sub1.employee_number, sub1.learning_path_name) max_course_end_date,
sub1.original_date_of_hire,
sub2.job_start_date promotion_date,
nvl(sub1.completion_target_days, 9999999999999999) completion_target_days,
sub1.completion_target_date,
sub1.no_of_completed_courses,
sub1.no_of_mandatory_courses,
round(least(sub1.no_of_completed_courses, sub1.no_of_mandatory_courses) / sub1.no_of_mandatory_courses * 100) completion_percentage
--round() time_percentage
from (
select papf.employee_number,
nvl(papf.known_as, papf.first_name) || ' ' || papf.last_name employee_name,
papf.original_date_of_hire,
oav.version_name course_name,
olpt.name learning_path_name,
ole.completion_target_date completion_target_date,
ole.no_of_completed_courses no_of_completed_courses,
ole.no_of_mandatory_courses no_of_mandatory_courses,
olp.duration completion_target_days,
papf.person_id,
oav.activity_version_id
from ota_lp_enrollments ole,
per_all_people_f papf,
ota_learning_paths olp,
ota_learning_paths_tl olpt,
ota_lp_sections ols,
ota_lp_sections_tl olst,
ota_learning_path_members olpm,
ota_activity_versions oav -- aka Courses
where papf.person_id = ole.person_id
and xxpay_bi_util.get_effective_date(ole.person_id, trunc(sysdate), 'all') between papf.effective_start_date
and papf.effective_end_date
and olp.learning_path_id = ole.learning_path_id
and olpt.learning_path_id = ole.learning_path_id
and olpt.language = userenv('LANG')
and ols.learning_path_id = ole.learning_path_id
and olst.learning_path_section_id = ols.learning_path_section_id
and olst.language = userenv('LANG')
and ols.learning_path_id = ole.learning_path_id
and olpm.learning_path_id = ole.learning_path_id
and olpm.learning_path_section_id = ols.learning_path_section_id
and oav.activity_version_id = olpm.activity_version_id
group by papf.employee_number,
nvl(papf.known_as, papf.first_name) || ' ' || papf.last_name,
papf.original_date_of_hire,
oav.version_name,
olpt.name,
olst.name,
ole.completion_target_date,
ole.no_of_completed_courses,
ole.no_of_mandatory_courses,
olp.duration,
papf.person_id,
oav.activity_version_id
) sub1,
(
select erm.*,
paaf.assignment_id,
paaf.person_id,
pj.name job_title,
haou.name cost_centre,
paafm.job_start_date,
xxpay_util.get_lookup_value(trunc(sysdate), 'TRUW_HR_DIRECTORS', hoi.org_information1) director -- 'Derek Kohler (Stores)'
from (
select odb.booking_id,
odb.delegate_person_id,
odb.date_booking_placed,
oe.course_start_date,
oe.course_end_date,
oe.course_start_time,
oe.course_end_time,
oe.enrolment_start_date,
oe.public_event_flag,
oe.title class_title,
oe.activity_version_id
from ota_delegate_bookings odb,
ota_events oe -- aka Classes
where oe.event_id = odb.event_id
) erm,
per_all_assignments_f paaf,
per_jobs pj,
hr_all_organization_units haou,
hr_organization_information hoi,
(
select paaf.person_id,
paaf.assignment_id,
paaf.job_id,
min(paaf.effective_start_date) job_start_date
from per_all_assignments_f paaf
where paaf.assignment_type in ('E', 'C')
and paaf.primary_flag = 'Y'
group by paaf.person_id,
paaf.assignment_id,
paaf.job_id
) paafm
where paaf.person_id (+) = erm.delegate_person_id
and erm.course_start_date between nvl(paaf.effective_start_date (+), to_date('01/01/1000', 'dd/mm/yyyy'))
and nvl(paaf.effective_end_date (+), to_date('31/12/4712', 'dd/mm/yyyy'))
and paafm.person_id (+) = paaf.person_id
and paafm.assignment_id (+) = paaf.assignment_id
and paafm.job_id (+) = paaf.job_id
and pj.job_id (+) = paaf.job_id
and haou.organization_id (+) = paaf.organization_id
and hoi.organization_id (+) = paaf.organization_id
and hoi.org_information_context (+) = 'TRU_ADD_ORG'
) sub2
where sub2.activity_version_id (+) = sub1.activity_version_id
and sub2.person_id (+) = sub1.person_id
and sub1.employee_number in ('2006591', '2005681', '2004118', '2004212')
order by 3, 1, 2, 4, 5, 7
If you have tables like this:
CREATE TABLE students (
student_id NUMBER PRIMARY KEY
);
CREATE TABLE courses (
course_id NUMBER PRIMARY KEY
);
CREATE TABLE student_courses (
student_id NUMBER REFERENCES Students( student_id ),
course_id NUMBER REFERENCES Courses( course_id ),
completed NUMBER(1,0),
score NUMBER(3,0),
CONSTRAINT student_courses_pk PRIMARY KEY ( student_id, course_id )
);
Then you can do:
SELECT s.student_id,
COUNT( CASE c.completed WHEN 1 THEN 1 END ) / COUNT( c.course_id ) * 100 AS percent_enrolled_completed
FROM student s
LEFT OUTER JOIN
student_courses c
ON ( s.student_id = c.student_id )
GROUP BY s.student_id;
Edited: to answer updated question
SQL Fiddle
Oracle 11g R2 Schema Setup:
create table learning_path_items AS
SELECT 1 AS learning_path_id, 1 AS course_id FROM DUAL
UNION ALL SELECT 1, 2 FROM DUAL
UNION ALL SELECT 2, 3 FROM DUAL
UNION ALL SELECT 2, 4 FROM DUAL
UNION ALL SELECT 2, 5 FROM DUAL;
create table learning_path_enrollments AS
SELECT 1 AS employee_id, 1 AS learning_path_id FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
UNION ALL SELECT 3, 2 FROM DUAL;
create table course_enrollments AS
SELECT 2 AS employee_id, 3 AS course_id FROM DUAL
UNION ALL SELECT 2, 4 FROM DUAL;
Query 1:
WITH num_completed AS (
SELECT e.learning_path_id,
e.employee_id,
c.course_id,
COUNT( c.course_id ) OVER ( PARTITION BY e.learning_path_id, e.employee_id ) AS num_completed
FROM learning_path_items i
INNER JOIN
learning_path_enrollments e
ON (e.learning_path_id = i.learning_path_id)
INNER JOIN
course_enrollments c
ON (i.course_id = c.course_id
AND e.employee_id = c.employee_id)
),
num_courses AS (
SELECT e.learning_path_id,
e.employee_id,
COUNT( 1 ) AS num_courses
FROM learning_path_items i
INNER JOIN
learning_path_enrollments e
ON (e.learning_path_id = i.learning_path_id)
GROUP BY
e.learning_path_id,
e.employee_id
)
SELECT c.learning_path_id,
c.employee_id,
x.course_id,
COALESCE( x.num_completed, 0 ) || ' out of ' || c.num_courses AS completion
FROM num_courses c
LEFT OUTER JOIN
num_completed x
ON ( c.employee_id = x.employee_id
AND c.learning_path_id = x.learning_path_id )
ORDER BY 1, 2, 3
Results:
| LEARNING_PATH_ID | EMPLOYEE_ID | COURSE_ID | COMPLETION |
|------------------|-------------|-----------|------------|
| 1 | 1 | (null) | 0 out of 2 |
| 2 | 2 | 3 | 2 out of 3 |
| 2 | 2 | 4 | 2 out of 3 |
| 2 | 3 | (null) | 0 out of 3 |
SELECT whatever FROM wherever UNION ALL SELECT 0 FROM DUAL LIMIT 1;
If you get a result from your first query you would get the 1 row with the percentage you are trying to get
second select returns a row that has value 0 but is stored in a VARCHAR2(1) from the dummy table DUAL so you will either get the result from your first query (assuming you only get 1 result because otherwise it will only show the first row it returns)
if the first select returns no rows, the second select will return a single column single row result with a varchar2(1) '0'
ALTERNATIVELY
SELECT
something
FROM sometable
WHERE somefield = somevalue
UNION ALL
SELECT
0
WHERE NOT EXISTS (SELECT something FROM sometable WHERE somefield = somevalue)
This will do the same except only when your first select query returns no rows, I guess this would be the better answer for your question as the first query might be returning multiple rows