Oracle left join not working as expected - oracle

I'm having a problem with a left join. I have tow tables: Customers and WeeklyShop with each customer's weekly summary
I want to select all customers that did not buy in certain period of time.
My code
SELECT c.CUSTOMER_ID
FROM CUSTOMER c
left join (
SELECT distinct(w.CUSTOMER) as id
FROM WEEKLYSHOP w
WHERE w.WEEK_START >= beginDate
and w.WEEK_END <= endDate
) a
on c.CUSTOMER_ID = a.id
WHERE a.id = null
I was expecting this to exclude all the customers in the subquery, leaving me with the ones I need, but it's returning nothing.
If I run only the subquery, it returns the customers with data in that period correctly.
I hope I made myself clear, if there's something needed to add just tell me. Any help will be highly appreciated.

You should use IS NULL instead of = operator to check if a value is not set:
SELECT c.CUSTOMER_ID
FROM CUSTOMER c
left join (
SELECT distinct(w.CUSTOMER) as id
FROM WEEKLYSHOP w
WHERE w.WEEK_START >= beginDate
and w.WEEK_END <= endDate
) a
on c.CUSTOMER_ID = a.id
WHERE a.id IS null;
Example of NULL in PL/SQL context:
DECLARE
v_bool BOOLEAN;
BEGIN
v_bool := 1 = NULL;
IF NOT v_bool THEN -- same as IF v_bool = false THEN
dbms_output.put_line('False.');
ELSIF v_bool IS NULL THEN
dbms_output.put_line('NULL.');
END IF;
IF (NOT v_bool) IS NULL THEN
dbms_output.put_line('NOT v_bool is also NULL when v_bool is NULL.');
END IF;
END;
The output:
NULL.
NOT v_bool is also NULL when v_bool is NULL.

Related

Oracle Mutating Tables - Trigger

I have a trigger on a table that, whenever you insert a record, it generates a new record (IN A SEPARATE TABLE) from a mapping table. It also does this when you update a record, looking for any change in the contents of one field.
When this executes I get the dreaded error, ORA-04091: table SCHEMA.TABLE1 is mutating, trigger/function may not see it but I do not see how. I am not changing any data in the table that triggered the trigger.
NOTE: I tried the COMPOUND TRIGGER but can't seem to get that going. I'll spend more time on it if someone is able to tell me why the trigger above could possibly throw the mutating table error.
Thanks for any help!
CREATE OR REPLACE TRIGGER SCHEMA.TRIGGER1
AFTER DELETE OR INSERT or UPDATE
ON SCHEMA.TABLE1
FOR EACH ROW
BEGIN
IF updating THEN
IF (:old.TABLE1_FIELD1 != :new.TABLE1_FIELD1
OR (:old.TABLE1_FIELD1 is null and :new.TABLE1_FIELD1 is not null)
OR (:old.TABLE1_FIELD1 is not null and :new.TABLE1_FIELD1 is null)
AND (:new.TABLE1_FIELD1 != 0)
)
THEN
-- DELETE ASSOCIATED RECORDS FIRST
DELETE FROM TABLE2
WHERE TABLE1_ID_FIELD = :old.TABLE1_ID_FIELD;
-- ADD IMT TAG
INSERT INTO TABLE2(TABLE2.FK1, TABLE1_ID_FIELD)
SELECT DISTINCT 1521 /* IMT */, r.TABLE1_ID_FIELD
FROM TABLE3 m INNER JOIN TABLE1 r ON m.TABLE3.FK2 = r.TABLE1_FIELD1
WHERE NOT EXISTS (select * from TABLE2 where TABLE2.FK1=1521 and TABLE1_ID_FIELD =r.TABLE1_ID_FIELD)
And TABLE1_FIELD1 <> 0
And TABLE1_ID_FIELD = :old.TABLE1_ID_FIELD;
-- ADD ALL OTHER TAGS BASED ON MAPPING TABLE
INSERT INTO TABLE2(TABLE2.FK1, TABLE1_ID_FIELD)
SELECT TABLE3.FK1, r.TABLE1_ID_FIELD
FROM TABLE3 m INNER JOIN TABLE1 r ON m.TABLE3.FK2 = r.TABLE1_FIELD1
WHERE NOT EXISTS (select * from TABLE2 where TABLE2.FK1=m.TABLE3.FK1 and TABLE1_ID_FIELD =r.TABLE1_ID_FIELD)
And TABLE1_FIELD1 <> 0
And TABLE1_ID_FIELD = :old.TABLE1_ID_FIELD;
END IF;
ELSIF inserting THEN
IF (:new.TABLE1_FIELD1 != 0) THEN
-- ALWAYS HAVE THIS ASSOCIATION
INSERT INTO TABLE2(TABLE2.FK1, TABLE1_ID_FIELD)
SELECT DISTINCT 1521, r.TABLE1_ID_FIELD
FROM TABLE3 m INNER JOIN TABLE1 r ON m.TABLE3.FK2 = r.TABLE1_FIELD1
WHERE NOT EXISTS (select * from TABLE2 where TABLE2.FK1=1521 and TABLE1_ID_FIELD =r.TABLE1_ID_FIELD)
And TABLE1_FIELD1 <> 0
And TABLE1_ID_FIELD = :new.TABLE1_ID_FIELD;
-- ADD ALL OTHER ASSOCIATIONS BASED ON MAPPING TABLE
INSERT INTO TABLE2(TABLE2.FK1, TABLE1_ID_FIELD)
SELECT TABLE3.FK1, r.TABLE1_ID_FIELD
FROM TABLE3 m INNER JOIN TABLE1 r ON m.TABLE3.FK2 = r.TABLE1_FIELD1
WHERE NOT EXISTS (select * from TABLE2 where TABLE2.FK1=m.TABLE3.FK1 and TABLE1_ID_FIELD =r.TABLE1_ID_FIELD)
And TABLE1_FIELD1 <> 0
And TABLE1_ID_FIELD = :new.TABLE1_ID_FIELD;
END IF;
END IF;
END;```

Need help in output for Oracle PLSQL Procedure

Am currently working on an oracle PL SQL procedure to list the project numbers, titles and names of employees who work on a project.
I am able to write a procedure that is able to get this info. However, I can only draw one output at a time as such:
1001 Computation
Alvin
Peter
How can I change my code to output all of the entries at the same time while printing them as such:
[Fragment Example][Showing only 1st 3 entries]
1001 Computation: Alvin, Peter
1002 Study methods: Bob, Robert
1003 Racing car: Robert
[Current Code]
create or replace procedure PROJECTGROUPS(projectid IN WorksOn.P#%TYPE)
is
PID Project.P#%TYPE;
PNAME Project.PTitle%TYPE;
ENAME Employee.Name%TYPE;
CURSOR query is
select Employee.Name from Employee
left outer join WorksOn On Employee.E# = WorksOn.E#
where WorksOn.P# = projectid
order by Employee.Name ASC
fetch first 20 rows only;
--
--
begin
select P#, PTitle into PID, PNAME from project where project.p# = projectid;
DBMS_OUTPUT.PUT_LINE(PID || ' ' || PNAME);
--
open query;
loop
fetch query into ENAME;
if query%NOTFOUND then exit;
end if;
DBMS_OUTPUT.PUT_LINE(ENAME);
end loop;
close query;
end PROJECTGROUPS;
You can directly use it in a single query and loop through it as follows:
CREATE OR REPLACE PROCEDURE PROJECTGROUPS (
PROJECTID IN WORKSON.P#%TYPE
) IS
BEGIN
FOR I IN (
SELECT PROJECT_NAME,
PTITLE,
LISTAGG(EMP_NAME,
',') WITHIN GROUP(
ORDER BY EMP_NAME
) AS EMP_NAMES
FROM (
SELECT P.P# PROJECT_NAME,
P.PTITLE,
E.EMPLOYEE.NAME EMP_NAME,
ROW_NUMBER() OVER(
PARTITION BY P.P#
ORDER BY E.NAME
) AS RN
FROM PROJECT P
JOIN WORKSON W
ON W.P# = P.P#
JOIN EMPLOYEE E
ON E.E# = W.E#
WHERE P.P# = PROJECTID
)
WHERE RN <= 20
GROUP BY PROJECT_NAME,
PTITLE
) LOOP
DBMS_OUTPUT.PUT_LINE(I.PROJECT_NAME
|| ' '
|| I.PTITLE
|| ' : '
|| I.EMP_NAMES);
END LOOP;
END PROJECTGROUPS;
Also, your query is not actually outer joined as you have used the condition in the WHERE clause.
left outer join WorksOn On Employee.E# = WorksOn.E# -- you want outer join
where WorksOn.P# = projected -- but the outer join is converted to inner join

How to get data with removed duplicates?

The code below is to get distinct data in terms of column name e1 and mdl, but does not show any reslut.
I have put "AND ROWNUM IN (SELECT MAX(ROWNUM) FROM T1.." to remove dulicated row.
If I remove "AND ROWNUM IN (SELECT MAX(ROWNUM) FROM T1..", then of course all the data in Table T1 selected.
<Table and data>
CREATE TABLE T1 (
dte VARCHAR2(15),
gu1 VARCHAR2(15),
gu2 VARCHAR2(15),
eq VARCHAR2(15),
mdl VARCHAR2(15),
val VARCHAR2(15)
);
INSERT INTO T1 VALUES('20190801','30','30','E1','M1','1.5');
INSERT INTO T1 VALUES('20190801','30','30','E1','M1',NULL);
INSERT INTO T1 VALUES('20190801','30','30','E1','M1','0');
INSERT INTO T1 VALUES('20190802','30','30','E1','M1','1.5');
INSERT INTO T1 VALUES('20190803','30','30','E3','M1','3.0');
<Code>
SELECT gu1,gu2,eq,mdl
FROM T1
WHERE val <> '0' AND val IS NOT NULL
AND dte >= '20190801' AND dte <= '20190803'
AND gu1 = '30'
AND ROWNUM IN (SELECT MAX(ROWNUM) FROM T1 --to get only one among dulplicated rows in terms of column e1, mdl,
WHERE val <> '0' AND val IS NOT NULL
AND dte >= '20190801' AND dte <= '20190803'
AND gu1 = '30'
GROUP BY eq,mdl)
;
<Expexted result is>
GU1 GU2 EQ MDL
---- ---- ---- ----
30 30 E1 M1
30 30 E3 M1
rownum is generated after the row is output. what you can do instead is to use
row_number analytical function as follows
SELECT * FROM (
SELECT gu1,gu2,eq,mdl,row_number() over(partition by eq,mdl order by dte desc) as rnk
FROM T1
WHERE val <> '0' AND val IS NOT NULL
AND dte >= '20190801' AND dte <= '20190803'
AND gu1 = '30'
)x
WHERE x.rnk=1
Try to use SELECT DISTINCT Statement.
SELECT DISTINCT
gu1,gu2,eq,mdl
FROM T1
WHERE val <> '0' AND val IS NOT NULL
AND dte >= '20190801' AND dte <= '20190803'
AND gu1 = '30'
;
More info on DISTINCT use here
As far as I understood from sample data and expected output, You can use one of the following method:
Distinct - as decribed in VSMent answer
Using rownum - as described in George Joseph answer
Using EXISTS as described following
-- in following example, you can also use WITH AS to remove excess duplicate coding
SELECT T1.gu1,T1.gu2,T1.eq,T1.mdl
FROM T1
WHERE T1 val <> '0'
AND T1.val IS NOT NULL
AND T1.dte >= '20190801'
AND T1.dte <= '20190803'
AND T1.gu1 = '30'
AND NOT EXISTS (SELECT 1
FROM T2
WHERE T2.val <> '0'
AND T2.val IS NOT NULL
AND T2.dte >= '20190801'
AND T2.dte <= '20190803'
AND T2.GU1 = 30
-- FOLLOWING 3 CONDITION WILL RESTRICT DUPLICATE ROWS
AND T1.EQ = T2.EQ
AND T1.MDL = T2.MDL
T1.ROWID > T2.ROWID
);
Cheers!!

Oracle Function To Return List

I'm trying to develop a PLSQL function that outputs a list of employee names that I can then run through another script. I can't quite get it right though. I'm fairly new to PLSQL and am primarily used to building functions in Python, so I may be thinking about this the wrong way. At the end of all of this, I'd like to use the output of this within another script I'm writing.
Baseline Script:
SELECT EMPLOYEE FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT E.EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1
My attempt at writing a PLSQL function:
CREATE FUNCTION get_employees(EMPLOYEE IN VARCHAR2)
RETURN VARCHAR2
IS EMPLOYEE_LIST;
BEGIN
SELECT EMPLOYEE FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT E.EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1
RETURN EMPLOYEE_LIST
END;
I know I'm missing some syntax, I just don't know what and why...I'm reading through the docs at the moment to try to understand this. Any help you all could provide would be much appreciated!
Thanks!
Without your tables or sample data this has to be a bit of a guess, but a "fixed" version might be something like this:
create or replace type short_string_tt as table of varchar2(100)
/
create or replace function get_employees
( p_role_date_cutoff roles.role_start_date%type )
return short_string_tt
as
l_employee_list short_string_tt;
begin
select employee bulk collect into l_employee_list
from ( select employee
, row_number() over(partition by employee order by role_start_date desc, id desc) rn
from ( select distinct e.employee, e.id, lr.description, role_start_date
from employees e
join roles r
on r.employee_id = e.id
join lu_roles lr
on lr.role_id = r.role_id
where role_start_date <= p_role_date_cutoff )
)
where rn = 1;
return l_employee_list;
end;
/
If the number of rows returned is likely to be significant then you might look at making it a pipelined function, as these stream rows back as they are fetched rather than building the entire collection in memory before returning anything.
try this would it work ?
CREATE FUNCTION get_employees(EMPLOYEE IN VARCHAR2)
RETURN VARCHAR2
IS EMPLOYEE_LIST VARCHAR2(200);
BEGIN
SELECT EMPLOYEE into EMPLOYEE_LIST FROM (
SELECT ID, EMPLOYEE, ROLE, STARTDATE,
ROW_NUMBER() OVER (PARTITION BY EMPLOYEE ORDER BY STARTDATE DESC, ID DESC) RN
FROM (
SELECT DISTINCT EMPLOYEE EMPLOYEE,
E.ID ID,
LR.DESCRIPTION ROLE,
ROLE_START_DATE STARTDATE
FROM EMPLOYEES E
JOIN ROLES R ON E.EMPLOYEE_ID = R.EMPLOYEE_ID
JOIN LU_ROLES LR ON R.ROLE_ID = LR.ROLE_ID
WHERE ROLE_START_DATE <= DATE '2017-12-03'))
WHERE RN = 1;
RETURN EMPLOYEE_LIST;
END;

How to use 'EXIST' in a simple oracle query

I have a table called ‘MainTable’ with following data
Another table called ‘ChildTable’ with following data (foreighn key Number)
Now I want to fetch those records from ‘ChildTable’ if there exists at least one ‘S’ status.
But if any other record for this number id ‘R’ then I don’t want to fetch it
Something like this-
I tried following
Select m.Number, c.Status from MainTable m, ChildTable c
where EXISTS (SELECT NULL
FROM ChildTable c2
WHERE c2.status =’S’ and c2.status <> ‘R’
AND c2.number = m.number)
But here I am getting record having ‘R’ status also, what I am doing wrong?
You can try something like this
select num, status
from
(select id, num, status,
sum(decode(status, 'R', 1, 0)) over (partition by num) Rs,
sum(decode(status, 'S', 1, 0)) over (partition by num) Ss
from child_table) t
where t.Rs = 0 and t.Ss >= 1
-- and status = 'S'
Here is a sqlfiddle demo
The child records with 'R' might be associated with a maintable record that also has another child record with status 'S' -- that is what your query is asking for.
Select
m.Number,
c.Status
from MainTable m
join ChildTable c on c.number = m.number
where EXISTS (
SELECT NULL
FROM ChildTable c2
WHERE c2.status =’S’
AND c2.number = m.number) and
NOT EXISTS (
SELECT NULL
FROM ChildTable c2
WHERE c2.status =’R’
AND c2.number = m.number)
WITH ChildrenWithS AS (
SELECT Number
FROM ChildTable
WHERE Status = 'S'
)
,ChildrenWithR AS (
SELECT Number
FROM ChildTable
WHERE Status = 'R'
)
SELECT MaintTable.Number
,ChildTable.Status
FROM MainTable
INNER JOIN ChildTable
ON MainTable.Number = ChildTable.Number
WHERE MainTable.Number IN (SELECT Number FROM ChildrenWithS)
AND MainTable.Number NOT IN (SELECT Number FROM ChildrenWithR)

Resources