Oracle SQL Return at least one row - sql

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

Related

sql pivot multiple and partially similar row values into multiple unknown number of cols

To SELECT multiple CASE-WHEN expressions into a single row per ID I have used aggregation MAX and GROUPBY.
SELECT table1.IDvar,
MAX(CASE WHEN table2.var1 = 'foo' THEN table2.var2 END) AS condition1,
MAX(CASE WHEN table2.var1 = 'bar' THEN table2.var2 END) AS condition2
FROM table1
FULL JOIN table2 ON table1.IDvar = table2.table1_IDvar
GROUP BY table1.IDvar
However, I have observed that empirical criteria such as foo entered in the CASE-WHEN-THEN-END expression may occur multiple times, that is, in multiple rows each of which corresponds to different values on columns of interest (THEN column-of-interest END) in the db schema. This implies that taking the MAX or MIN drops data that may be of interest. It is not known in advance how many rows there are for each value in criteria_col and thus in the cols_of_interest.
Sample data e.g.:
IDvar_foreign_key
criteria_col
col_of_interest1
col_of_interest2
x1
foo
01-01-2021
100
x1
foo
01-06-2021
2000
x1
foo
01-08-2021
0
x1
bar
01-08-2021
300
Note: the actual table does contain a unique identifier or primary key.
Q: Are there ways to pivot certain columns/tables in a db schema without possibly dropping some values?
An ouput something like this:
IDvar_foreign_key
foo_1_col_of_interest1
foo_1_col_of_interest2
foo_2_col_of_interest1
foo_2_col_of_interest2
foo_3_col_of_interest1
foo_3_col_of_interest2
x1
01-01-2021
100
01-06-2021
2000
01-08-2021
0
Edit
#lemon and #MTO suggests dynamic queries are necesarry, otherwise I was considering whether not using aggregation would do
Dynamic Pivot in Oracle's SQL
Pivot rows to columns without aggregate
TSQL Pivot without aggregate function
You can use the MIN and MAX aggregation functions and to get the correlated minimums and maximums for col_of_interest2 you can use KEEP (DENSE_RANK ...):
SELECT t1.IDvar,
MIN(CASE WHEN t2.criteria_col = 'foo' THEN t2.col_of_interest1 END)
AS foo_1_col_of_interest1,
MIN(col_of_interest2) KEEP (
DENSE_RANK FIRST ORDER BY
CASE WHEN t2.criteria_col = 'foo' THEN t2.col_of_interest1 END
ASC NULLS LAST
) AS foo_1_col_of_interest2,
MAX(CASE WHEN t2.criteria_col = 'foo' THEN t2.col_of_interest1 END)
AS foo_2_col_of_interest1,
MAX(col_of_interest2) KEEP (
DENSE_RANK FIRST ORDER BY
CASE WHEN t2.criteria_col = 'foo' THEN t2.col_of_interest1 END
DESC NULLS LAST
) AS foo_2_col_of_interest2
FROM table1 t1
FULL JOIN table2 t2
ON t1.IDvar = t2.table1_IDvar
GROUP BY t1.IDvar
Which, for the sample data:
CREATE TABLE table1 ( idvar ) AS
SELECT 1 FROM DUAL UNION ALL
SELECT 2 FROM DUAL UNION ALL
SELECT 3 FROM DUAL;
CREATE TABLE table2 ( table1_idvar, criteria_col, col_of_interest1, col_of_interest2 ) AS
SELECT 1, 'foo', DATE '2021-01-01', 100 FROM DUAL UNION ALL
SELECT 1, 'foo', DATE '2021-03-01', 500 FROM DUAL UNION ALL
SELECT 1, 'foo', DATE '2021-06-01', 2000 FROM DUAL UNION ALL
SELECT 1, 'bar', DATE '2021-06-01', 2000 FROM DUAL UNION ALL
SELECT 2, 'foo', DATE '2021-01-02', 200 FROM DUAL UNION ALL
SELECT 2, 'foo', DATE '2021-03-02', 300 FROM DUAL UNION ALL
SELECT 2, 'bar', DATE '2021-06-02', 400 FROM DUAL UNION ALL
SELECT 3, 'foo', DATE '2021-01-03', 700 FROM DUAL;
Outputs:
IDVAR
FOO_1_COL_OF_INTEREST1
FOO_1_COL_OF_INTEREST2
FOO_2_COL_OF_INTEREST1
FOO_2_COL_OF_INTEREST2
1
2021-01-01 00:00:00
100
2021-06-01 00:00:00
2000
2
2021-01-02 00:00:00
200
2021-03-02 00:00:00
300
3
2021-01-03 00:00:00
700
2021-01-03 00:00:00
700
SQL (not just Oracle) requires each query to have a known, fixed number of columns; if you want a dynamic number of columns then you should perform the pivot in whatever third-party application (Java, C#, PHP, etc.) that you are using to talk to the database.
If you want to pivot a fixed maximum number of columns then you can use the ROW_NUMBER analytic function. For example, if you want the 3 minimum values for col_of_interest1 then you can use:
SELECT idvar,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 1 THEN col_of_interest1 END)
AS foo_1_col_of_interest1,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 1 THEN col_of_interest2 END)
AS foo_1_col_of_interest2,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 2 THEN col_of_interest1 END)
AS foo_2_col_of_interest1,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 2 THEN col_of_interest2 END)
AS foo_2_col_of_interest2,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 3 THEN col_of_interest1 END)
AS foo_3_col_of_interest1,
MAX(CASE WHEN criteria_col = 'foo' AND rn = 3 THEN col_of_interest2 END)
AS foo_3_col_of_interest2
FROM (
SELECT t1.IDvar,
criteria_col,
col_of_interest1,
col_of_interest2,
ROW_NUMBER() OVER (
PARTITION BY t1.IDvar, criteria_col
ORDER BY col_of_interest1, col_of_interest2
) AS rn
FROM table1 t1
FULL JOIN table2 t2
ON t1.IDvar = t2.table1_IDvar
WHERE criteria_col IN ('foo' /*, 'bar', 'etc'*/)
)
GROUP BY idvar
Which outputs:
IDVAR
FOO_1_COL_OF_INTEREST1
FOO_1_COL_OF_INTEREST2
FOO_2_COL_OF_INTEREST1
FOO_2_COL_OF_INTEREST2
FOO_3_COL_OF_INTEREST1
FOO_3_COL_OF_INTEREST2
1
2021-01-01 00:00:00
100
2021-03-01 00:00:00
500
2021-06-01 00:00:00
2000
2
2021-01-02 00:00:00
200
2021-03-02 00:00:00
300
null
null
3
2021-01-03 00:00:00
700
null
null
null
null
fiddle
Unknown number of columns is an issue that you can't ignore. The question is - are there any possible expected limits. I question who would get any meaningfull insight from a resulting dataset with hundreds of columns. It doesnt make sense. If you could setup the limit to 10 or 20 or whatever like that then you could build a datagrid structure using pivot where the number of columns would be the same and the data within could be placed as in your question.
Just as an example how - here is the code that does it with up to 6 pairs of your data of interest (COL_DATE and COL_VALUE) - it could be 20 or 30 or ...
First your sample data and some preparation for pivoting (CTE named grid):
WITH -- S a m p l e d a t a
tbl AS
(
SELECT 1 "ID", 'foo' "CRITERIA", DATE '2021-01-01' "INTEREST_1", 100 "INTEREST_2" FROM DUAL UNION ALL
SELECT 1, 'foo', DATE '2021-03-01', 500 FROM DUAL UNION ALL
SELECT 1, 'foo', DATE '2021-06-01', 2000 FROM DUAL UNION ALL
SELECT 1, 'bar', DATE '2021-06-01', 2000 FROM DUAL UNION ALL
SELECT 2, 'foo', DATE '2021-01-02', 200 FROM DUAL UNION ALL
SELECT 2, 'foo', DATE '2021-03-02', 300 FROM DUAL UNION ALL
SELECT 2, 'bar', DATE '2021-06-02', 400 FROM DUAL UNION ALL
SELECT 3, 'foo', DATE '2021-01-03', 700 FROM DUAL
),
grid AS
(SELECT * FROM
( Select ID "ID", CRITERIA "GRP", INTEREST_1 "COL_DATE", INTEREST_2 "COL_VALUE",
Count(*) OVER(Partition By ID, CRITERIA) "ROWS_TOT",
ROW_NUMBER() OVER(Partition By ID, CRITERIA Order By ID, CRITERIA) "RN_GRP_ID",
ROW_NUMBER() OVER(Partition By ID, CRITERIA Order By ID, CRITERIA) "RN_GRP_ID_2"
From tbl t )
ORDER BY ID ASC, GRP DESC, ROWS_TOT DESC
),
Result (grid)
ID GRP COL_DATE COL_VALUE ROWS_TOT RN_GRP_ID RN_GRP_ID_2
---------- --- --------- ---------- ---------- ---------- -----------
1 foo 01-JAN-21 100 3 3 3
1 foo 01-JUN-21 2000 3 2 2
1 foo 01-MAR-21 500 3 1 1
1 bar 01-JUN-21 2000 1 1 1
2 foo 02-MAR-21 300 2 2 2
2 foo 02-JAN-21 200 2 1 1
2 bar 02-JUN-21 400 1 1 1
3 foo 03-JAN-21 700 1 1 1
... next is pivoting (another CTE named grid_pivot) and designing another grid that will be populated with your data of interest...
grid_pivot AS
( SELECT
ID, GRP, ROWS_TOT,
MAX(GRP_1_LINK) "GRP_1_LINK", CAST(Null as DATE) "GRP_1_DATE", CAST(Null as NUMBER) "GRP_1_VALUE",
MAX(GRP_2_LINK) "GRP_2_LINK", CAST(Null as DATE) "GRP_2_DATE", CAST(Null as NUMBER) "GRP_2_VALUE",
MAX(GRP_3_LINK) "GRP_3_LINK", CAST(Null as DATE) "GRP_3_DATE", CAST(Null as NUMBER) "GRP_3_VALUE",
MAX(GRP_4_LINK) "GRP_4_LINK", CAST(Null as DATE) "GRP_4_DATE", CAST(Null as NUMBER) "GRP_4_VALUE",
MAX(GRP_5_LINK) "GRP_5_LINK", CAST(Null as DATE) "GRP_5_DATE", CAST(Null as NUMBER) "GRP_5_VALUE",
MAX(GRP_6_LINK) "GRP_6_LINK", CAST(Null as DATE) "GRP_6_DATE", CAST(Null as NUMBER) "GRP_6_VALUE"
-- ... ... ... ...
FROM
( Select *
From ( Select * From grid )
PIVOT ( Max(RN_GRP_ID) "LINK" --Min(RN_GRP_ID) "GRP_FROM",
FOR RN_GRP_ID_2 IN(1 "GRP_1", 2 "GRP_2", 3 "GRP_3", 4 "GRP_4", 5 "GRP_5", 6 "GRP_6" ) ) -- ... ...
Order By ROWS_TOT DESC, GRP DESC, ID ASC
)
GROUP BY GRP, ROWS_TOT, ID
ORDER BY ROWS_TOT DESC, GRP DESC, ID ASC
)
Result (grid_pivot)
ID GRP ROWS_TOT GRP_1_LINK GRP_1_DATE GRP_1_VALUE GRP_2_LINK GRP_2_DATE GRP_2_VALUE GRP_3_LINK GRP_3_DATE GRP_3_VALUE GRP_4_LINK GRP_4_DATE GRP_4_VALUE GRP_5_LINK GRP_5_DATE GRP_5_VALUE GRP_6_LINK GRP_6_DATE GRP_6_VALUE
---------- --- ---------- ---------- ---------- ----------- ---------- ---------- ----------- ---------- ---------- ----------- ---------- ---------- ----------- ---------- ---------- ----------- ---------- ---------- -----------
1 foo 3 1 2 3
2 foo 2 1 2
3 foo 1 1
1 bar 1 1
2 bar 1 1
... and, finaly, mixing grid_pivot data with grid data using 6 left joins to fit 6 pairs of your data of interest into the grid.
SELECT gp.ID, gp.GRP,
g1.COL_DATE "GRP_1_DATE", g1.COL_VALUE "GRP_1_VALUE",
g2.COL_DATE "GRP_2_DATE", g2.COL_VALUE "GRP_2_VALUE",
g3.COL_DATE "GRP_3_DATE", g3.COL_VALUE "GRP_3_VALUE",
g4.COL_DATE "GRP_1_DATE", g4.COL_VALUE "GRP_4_VALUE",
g5.COL_DATE "GRP_2_DATE", g5.COL_VALUE "GRP_5_VALUE",
g6.COL_DATE "GRP_3_DATE", g6.COL_VALUE "GRP_6_VALUE"
-- ... ... ... ...
FROM grid_pivot gp
LEFT JOIN grid g1 ON(g1.ID = gp.ID And g1.GRP = gp.GRP And g1.RN_GRP_ID = gp.GRP_1_LINK)
LEFT JOIN grid g2 ON(g2.ID = gp.ID And g2.GRP = gp.GRP And g2.RN_GRP_ID = gp.GRP_2_LINK)
LEFT JOIN grid g3 ON(g3.ID = gp.ID And g3.GRP = gp.GRP And g3.RN_GRP_ID = gp.GRP_3_LINK)
LEFT JOIN grid g4 ON(g4.ID = gp.ID And g4.GRP = gp.GRP And g4.RN_GRP_ID = gp.GRP_4_LINK)
LEFT JOIN grid g5 ON(g5.ID = gp.ID And g5.GRP = gp.GRP And g5.RN_GRP_ID = gp.GRP_5_LINK)
LEFT JOIN grid g6 ON(g6.ID = gp.ID And g6.GRP = gp.GRP And g6.RN_GRP_ID = gp.GRP_6_LINK)
-- ... ... ... ...
ORDER BY gp.ROWS_TOT DESC, gp.GRP DESC, gp.ID ASC
R e s u l t :
ID GRP GRP_1_DATE GRP_1_VALUE GRP_2_DATE GRP_2_VALUE GRP_3_DATE GRP_3_VALUE GRP_1_DATE GRP_4_VALUE GRP_2_DATE GRP_5_VALUE GRP_3_DATE GRP_6_VALUE
---------- --- ---------- ----------- ---------- ----------- ---------- ----------- ---------- ----------- ---------- ----------- ---------- -----------
1 foo 01-MAR-21 500 01-JUN-21 2000 01-JAN-21 100
2 foo 02-JAN-21 200 02-MAR-21 300
3 foo 03-JAN-21 700
1 bar 01-JUN-21 2000
2 bar 02-JUN-21 400
Anyway you will probably need dynamic solution so, this could be interesting for something else, who knows what, when and where...

How to return all records from table A , if any one of the column has a specific value in oracle sql?

Below is the sample data
If I pass lot name as a parameter, I want to return employees who has greater than 0 records in The specific Lot . Not just the one record but all the records of that employee.
Table A
Empid lotname itemcount
1 A 1
1 B 1
2 B 0
3 B 1
3 C 0
Parameter - B
Result :
Empid lotname itemcount
1 A 1
1 B 1
3 B 1
3 C 0
Because employee 3 and 1 has count in B lot. All the employee lot details should be returned.
select data.* from A data,
(select Empid,count(lotname)
from A
group by Empid
having count(lotname)>1) MulLotEmp
where data.lotname='B'
and data.Empid=MulLotEmp.Empid;
Check if this query solves your problem. In this I created a inner table first for your first requirement that emp with multiple lot, then I mapped this table with actual table with condition of input lot name.
If I understand correctly, you want all "1" and then only "0" if there is no "1".
One method is:
select a.*
from a
where itemcount = 1 or
not exists (select 1 from a a2 where a2.empid = a.empid and a2.itemcount = 1);
In Oracle, you can use the MAX analytic function:
SELECT Empid,
lotname,
itemcount
FROM (
SELECT t.*,
MAX( itemcount ) OVER ( PARTITION BY Empid ) AS max_itemcount
FROM table_name t
)
WHERE max_itemcount = 1;
So, for you sample data:
CREATE TABLE table_name ( Empid, lotname, itemcount ) AS
SELECT 1, 'A', 1 FROM DUAL UNION ALL
SELECT 1, 'B', 1 FROM DUAL UNION ALL
SELECT 2, 'B', 0 FROM DUAL UNION ALL
SELECT 3, 'B', 1 FROM DUAL UNION ALL
SELECT 3, 'C', 0 FROM DUAL;
This outputs:
EMPID | LOTNAME | ITEMCOUNT
----: | :------ | --------:
1 | A | 1
1 | B | 1
3 | B | 1
3 | C | 0
db<>fiddle here
The analytic function
sum(case when LOTNAME = 'B' /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
calculates for each customer the total number of items with the selected lot.
Feel free to use it as a bind variable, e.g.
sum(case when LOTNAME = ? /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
The whole query is than as follows
with cust as (
select
EMPID, LOTNAME, ITEMCOUNT,
sum(case when LOTNAME = 'B' /* parameter */ then ITEMCOUNT end) over (partition by EMPID) as lot_itemcnt
from tab)
select
EMPID, LOTNAME, ITEMCOUNT
from cust
where lot_itemcnt >= 1;

Throw error , if duplicate data (not duplicate records) found in a table

I have a table which contains information about products, I need to check for duplicate records and throw an error. There are two columns ,product key and product value, where product value contains both semi-colon separate and normal values. The sample data is given below. (No constraints )
source_id |Product_key | Product_value
-----------------------------------------------------------
1 xzy PRODUCT_TAG=SCENT;CODE=123;PRICE=234
1 xhmr POWDER
1 abc PRODUCT_TAG=COMB;CODE=123;PRICE=234
1 xhmr OIL
1 zrmt 123
Now i have to check if any two rows have the same product_key and product_tag value, also if the product_key is xhmr , then product_value should be considered as product_tag value. The query which i have written is given below
select source_id, PRODUCT_KEY, rec , (case when instr(rec,'PRODUCT_TAG')<>0 THEN regexp_substr(TRIM(rec), '[^=]+', 1,2)
ELSE rec
end) as PRODUCT_TAG
from
(select source_id,PRODUCT_KEY ,regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) AS rec from products
connect by regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) is not null
AND prior source_id = source_id
AND PRIOR SYS_GUID() IS NOT NULL) where instr(rec,'PRODUCT_TAG')<>0 or PRODUCT_KEY in('xhmr');
Output
source_id |Product_key | Product_value
-----------------------------------------------------------
1 xzy SCENT
1 xhmr POWDER
1 abc COMB
1 xhmr OIL
After this, I am taking a count of all the rows and all the distinct rows . If both counts are not equal, then throwing error.I was wondering , if all this could be done in a concise way.
I think you might do the following
Get the main query and put into a with clause
Then you can operate with it as many times as needed
You could write it like this:
Update
with main_query
as (
select source_id, PRODUCT_KEY, rec , (case when instr(rec,'PRODUCT_TAG')<>0 THEN regexp_substr(TRIM(rec), '[^=]+', 1,2)
ELSE rec
end) as PRODUCT_TAG
from
(select source_id,PRODUCT_KEY ,regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) AS rec from products
connect by regexp_substr(TRIM(PRODUCT_VALUE), '[^;]+', 1,LEVEL) is not null
AND prior source_id = source_id
AND PRIOR SYS_GUID() IS NOT NULL) where instr(rec,'PRODUCT_TAG')<>0 or PRODUCT_KEY in('xhmr')
)
select
case when total_value = tot_dist then 'OK' -- whatever you want here
else 'ERROR' -- whatever you want here
end as result
from
( ( select count(*) as total_value from main_query ) ,
( select count(*) as total_dist from ( select distinct * from main_query ) )
)
Example in my case
SQL> desc my_test
Name Null? Type
----------------------------------------- -------- ----------------------------
C1 NUMBER
C2 NUMBER
SQL> select * from my_test ;
C1 C2
---------- ----------
1 1
1 1
2 2
SQL> with main_query as ( select c1 , c2 from my_test )
select a , b
from
( select count(*) as a from main_query ) ,
( select count(*) as b from ( select distinct * from main_query ) )
/ 2 3 4 5 6
A B
---------- ----------
3 2
SQL> with main_query as ( select c1 , c2 from my_test )
select case when a = b then 'OK' else 'ERROR' end as result
from
( select count(*) as a from main_query ) ,
( select count(*) as b from ( select distinct * from main_query ) )
/
RESUL
-----
ERROR

SQL check if group containes certain values of given column (ORACLE)

I have table audit_log with these records:
log_id | request_id | status_id
1 | 2 | 5
2 | 2 | 10
3 | 2 | 20
4 | 3 | 10
5 | 3 | 20
I would like to know if there exists request_ids having status_id 5 and 10 at the same time. So this query should return request_id = 2 as its column status_id has values 5 and 10 (request_id 3 is omitted because status_id column has only value of 10 without 5).
How could I do this with SQL?
I think I should use group by request_id, but I don't know how to check if group has status_id with values 5 and 10?
Thanks,
mismas
This could be a way:
/* input data */
with yourTable(log_id , request_id , status_id) as (
select 1 , 2 , 5 from dual union all
select 2 , 2 , 10 from dual union all
select 3 , 2 , 20 from dual union all
select 4 , 3 , 10 from dual union all
select 5 , 3 , 20 from dual
)
/* query */
select request_id
from yourTable
group by request_id
having count( distinct case when status_id in (5,10) then status_id end) = 2
How it works:
select request_id,
case when status_id in (5,10) then status_id end as checkColumn
from yourTable
gives
REQUEST_ID CHECKCOLUMN
---------- -----------
2 5
2 10
2
3 10
3
So the condition count (distinct ...) = 2 does the work
SELECT request_id
FROM table_name
GROUP BY request_id
HAVING COUNT( CASE status_id WHEN 5 THEN 1 END ) > 0
AND COUNT( CASE status_id WHEN 10 THEN 1 END ) > 0
To check if both values exists (without regard to additional values) you can filter before aggregation:
select request_id
from yourTable
where status_id in (5,10)
group by request_id
having count(*) = 2 -- status_id is unique
-- or
having count(distinct status_id) = 2 -- status_id exists multiple times
This should do it:
select
log5.*, log10.status_id
from
audit_log log5
join audit_log log10 on log10.request_id = log5.request_id
where
log5.status_id = 5
and log10.status_id = 10
order by
log5.request_id
;
Here's the output:
+ ----------- + --------------- + -------------- + -------------- +
| log_id | request_id | status_id | status_id |
+ ----------- + --------------- + -------------- + -------------- +
| 1 | 2 | 5 | 10 |
+ ----------- + --------------- + -------------- + -------------- +
1 rows
And here's the sql to set up the example:
create table audit_log (
log_id int,
request_id int,
status_id int
);
insert into audit_log values (1,2,5);
insert into audit_log values (2,2,10);
insert into audit_log values (3,2,20);
insert into audit_log values (4,3,10);
insert into audit_log values (5,3,20);

SQL join multiple tables with/without data

I have no idea how to create an SQL statement to join 4 tables.
1) The 'Vendor Table will always match entries from each table on Vendor #
2) Each of the remaining 3 will match to each other by Vendor # & Seq #
3) Any combination of the 3 can have data (or not)
4) I don't want to select from the Vendor table unless I get a hit on at least one of the 3
VENDOR
Vendor # Name
-------- ----
1 Tom Smith
2 Bruce Lee
3 Seamus O’Leary
4 Jonathan Stewart
5 Benjamin Franklin
Month Range Selected
Vendor # Seq # MonthFrom MonthTo
-------- ----- --------- -------
1 1 3 6
1 2 7 9
3 2 5 6
Week Selected
Vendor # Seq # Week #
-------- ----- ------
1 1 3
3 1 4
4 1 1
Day Selected
Vendor # Seq # Day #
1 1 15
1 2 25
2 1 12
4 1 05
5 1 19
Desired Table (Joined)
Vendor# Name Seq# MonthFrom MonthTo Week# Day#
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 05
5 Benjamin Franklin 1 NULL NULL NULL 19
The trick being that any of the 3 (not including 'Vendor') can or cannot have data and I only want a row returned if there is something from one or more of the 3.
Any Advice?
To join it on Vendor and Seq, we first need to have all possible combinations. Then we can filter the tables based on these combinations. I've ran the following in SQL server:
Setup
declare #Vendors table(id int, name varchar(20));
declare #MonthRangeSelected table (vendor int, seq int null, monthFrom int null, monthTo int null);
declare #WeekSelected table (vendor int, seq int null, week int null);
declare #DaySelected table (vendor int, seq int null, day int null);
insert into #Vendors
select 1, 'Tom Smith'
union all
select 2, 'Bruce Lee'
union all
select 3, 'Seamus O’Leary'
union all
select 4, 'Jonathan Stewart'
union all
select 5, 'Benjamin Franklin';
insert into #MonthRangeSelected
select 1, 1, 3, 6
union all
select 1, 2, 7, 9
union all
select 3, 2, 5, 6;
insert into #WeekSelected
select 1, 1, 3
union all
select 3, 1, 4
union all
select 4, 1, 1;
insert into #DaySelected
select 1, 1, 15
union all
select 1, 2, 25
union all
select 2, 1, 12
union all
select 4, 1, 05
union all
select 5, 1, 19;
Query
select v.Id, v.name, combinations.seq, MonthFrom, MonthTo, Week, Day
from #Vendors v
inner join (select m.vendor, m.seq
from #MonthRangeSelected m
union
select w.vendor, w.seq
from #WeekSelected w
union
select d.vendor, d.seq
from #DaySelected d) combinations
on combinations.vendor = v.id
left join #MonthRangeSelected m
on m.Vendor = combinations.vendor
and m.seq = combinations.seq
left join #WeekSelected w
on w.Vendor = combinations.vendor
and w.seq = combinations.seq
left join #DaySelected d
on d.Vendor = combinations.vendor
and d.seq = combinations.seq
where (MonthFrom is not null
or MonthTo is not null
or Week is not null
or Day is not null)
And this is the result:
Id name seq MonthFrom MonthTo Week Day
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 5
5 Benjamin Franklin 1 NULL NULL NULL 19
This is more complicated than it sounds. According to the result, you do not want a cartesian product when there are multiple matches in a table. So, you need to take seqnum into account.
select v.Vendor, v.name, coalesce(m.seq, w.seq, d.seq) as Seq,
m.MonthFrom, m.MonthTo, w.Week, d.Day
from Vendors v left join
SMonthRangeSelected m
on v.Vendor = m.Vendor full join
WeekSelected w
on v.Vendor = w.Vendor and m.seq = w.seq full join
DaySelected d
on v.Vendor = d.Vendor and d.seq in (w.seq, m.seq)
where m.Vendor is not null or
w.Vendor is not null or
d.Vendor is not null;
Strange things can happen when using full join, particularly if you want any filtering. An alternative approach uses union all and group by:
select mwd.Vendor, v.name, mwd.seq,
max(MonthFrom) as MonthFrom, max(MonthTo) as monthTo,
max(Week) as week, max(Day) as day
from ((select m.Vendor, m.seq, m.MonthFrom, m.MonthTo, NULL as week, NULL as day
from month m
) union all
(select w.Vendor, w.seq, NULL as MonthFrom, NULL as MonthTo, w.week, NULL as day
from week
) union all
(select d.Vendor, d.seq, NULL as MonthFrom, NULL as MonthTo, NULL as week, d.day
from day d
)
) mwd join
Vendor v
on v.vendor = vmwd.vendor
group by mwd.Vendor, v.vname, mwd.seq;
Note that this version does not require the Vendor table.
You should left outer join to each of the 3 tables, and then include the following in your where clause:
(MonthFrom is not null or Week# is not null or Day# is not null)
it sounds like you can inner join to the Vendor table
I believe that this will do what you need:
SELECT
V.[Vendor#], -- I'll never understand why people insist on using names that require brackets
V.Name,
COALESCE(M.[Seq#], W.[Seq#], D.[Seq#]) AS [Seq#],
M.MonthFrom,
M.MonthTo,
W.[Week#],
D.[Day#]
FROM
Vendor V
LEFT OUTER JOIN MonthRange M ON M.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Week W ON W.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Day D ON D.[Vendor#] = V.[Vendor#]
WHERE
(
M.[Vendor#] IS NOT NULL OR
W.[Vendor#] IS NOT NULL OR
D.[Vendor#] IS NOT NULL
) AND
(M.[Seq#] = W.[Seq#] OR M.[Seq#] IS NULL OR W.[Seq#] IS NULL) AND
(M.[Seq#] = D.[Seq#] OR M.[Seq#] IS NULL OR D.[Seq#] IS NULL) AND
(D.[Seq#] = W.[Seq#] OR D.[Seq#] IS NULL OR W.[Seq#] IS NULL)
You need full outer joins on the three tables, so as to get all vendor and seq number combinations. Join these with vendor and you are done:
select vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select vendorno, seqno, m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w using (vendorno, seqno)
full outer join daysel d using (vendorno, seqno)
) x using(vendorno)
order by vendorno, x.seqno;
UPDATE: Without a USING clause the same query get slightly less readable (and thus slightly more error-prone):
select v.vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select
coalesce(m.vendorno, w.vendorno, d.vendorno) as vendorno,
coalesce(m.seqno, w.seqno, d.seqno) as seqno,
m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w on w.vendorno = m.vendorno and w.seqno = m.seqno
full outer join daysel d on d.vendorno in (m.vendorno, w.vendorno)
and d.seqno in (m.seqno, w.segno)
) x on x.vendorno = v.vendorno
order by v.vendorno, x.seqno;
(Hope I didn't mix things up here. It's easy to make copy & paste errors with such a query. So if it doesn't work properly, look out for typos.)