Oracle 18c - Left join conditional USING clause - sql

I have a cursor with a left join like so:
CURSOR c_emp
SELECT * FROM FROM EMP_NEW
LEFT JOIN
(SELECT EMP_ID,
DEPT_ID,
SUB_DEPT_ID,
EMP_UID,
FROM EMP)
USING (DEPT_ID, SUB_DEPT_ID, EMP_UID);
Which is then referenced in a for loop:
For i in c_emp
IF i.emp_id IS NULL
then
insert into emp...
else
update emp...
where emp_id = i.emp_id;
end if;
We have a scenario when loading new data into EMP table from the EMP_NEW table, the emp_new.dept_id and emp_new.emp_uid can have a duplicate in the EMP table with a different sub_dept_id value. When we get such new data, the emp.emp_id is not selected in the cursor, thus logic goes to INSERT instead of UPDATE. I would like for the duplicate emp_id to be selected so it goes to the UPDATE.
EMP data:
EMP_ID DEPT_ID SUB_DEPT_ID EMP_UID
123 1 10 123.123
EMP_NEW data:
DEPT_ID SUB_DEPT_ID EMP_UID
1 20 123.123
The above cursor currently returns the following:
EMP_ID DEPT_ID SUB_DEPT_ID EMP_UID
null 1 20 123.123
What I would like it to return:
EMP_ID DEPT_ID SUB_DEPT_ID EMP_UID
123 1 20 123.123

You seem to want to match on the 3 columns first and if not on just two columns. I think a lateral join better fits your needs:
SELECT en.*, e.*
FROM EMP_NEW en LEFT JOIN LATERAL
(SELECT EMP_ID
FROM EMP e
WHERE en.DEPT_ID = e.DEPT_ID AND en.EMP_UID = e.EMP_UID
ORDER BY (CASE WHEN en.SUB_DEPT_ID = e.SUB_DEPT_ID THEN 1 ELSE 2 END)
FETCH FIRST 1 ROW ONLY -- presumably you want only one row either way
) e

Related

How to add a column that contains the id value of the first distinct record for a set of duplicate records

I have the following table named emp
id
fname
lname
1
Mihir
Sheth
2
Mit
Dasondi
3
Narra
Harsha
4
Mihir
Sheth
5
Shrey
Malvi
6
Mit
Dasondi
7
Ujas
Patel
I want to alter the table emp and create a new column NewId, such that for duplicate records it will have the value of the id of the first occurence of distinct record.
For distinct records the value will be null in the NewId column. By the "first occurence of the distinct record" I mean the record that occurs first when sorted on basis of id column.
For a better understanding, please see the following.
id
NewId
fname
lname
1
null
Mihir
Sheth
2
null
Mit
Dasondi
3
null
Narra
Harsha
4
1
Mihir
Sheth
5
null
Shrey
Malvi
6
2
Mit
Dasondi
7
null
Ujas
Patel
Please advise me on how to proceed and also keep in mind that I want the new column to be in the actual table, not in cte or other temporary table.
We can try using MIN as an analytic function here:
SELECT id, CASE WHEN id <> MIN(id) OVER (PARTITION BY fname, lname)
THEN MIN(id) OVER (PARTITION BY fname, lname) END AS NewId,
fname, lname
FROM emp
ORDER BY id;
You need first to add a column to the emp schema:
ALTER TABLE emp ADD NewId INT;
Then you can use a selection of the smallest ids for each employee and update the emp table where the id does not correspond to the minimum id:
UPDATE emp
INNER JOIN (SELECT fname, lname, MIN(id) AS id
FROM emp
GROUP BY fname, lname) min_emps
ON emp.fname = min_emps.fname
AND emp.lname = min_emps.lname
AND emp.id <> min_emps.id
SET emp.NewId = min_emps.id;
Here's a MySQL demo, though this may work in sybase too.
Edit: Given Sybase documentation on update statements, the corresponding UPDATE statement for your problem may be the following:
UPDATE emp
SET emp.NewId = min_emps.id;
FROM emp
JOIN (SELECT fname, lname, MIN(id) AS id
FROM emp
GROUP BY fname, lname ) min_emps
ON emp.fname = min_emps.fname
AND emp.lname = min_emps.lname
AND emp.id <> min_emps.id

Is there a function in sql server "isEmpty" return data if the select return empty result set

I have a table like this
EmpId No_of_days
1 24
2 28
6 24
7 30
8 10
9 15
when I write a select statement
SELECT No_of_days FROM _table WHERE EmpId = 3 or 4 or 5
I got an empty result set.
I want the result set return 0 days for employees 3 and 4 and 5
You can use a values() construct & do left join :
select tt.empid, isnull(t.No_of_days, 0)
from ( values (3), (4), (5)
) tt (empid) left join
table t
on t.empid = tt.empid;
EDIT : If you have a more employees id then you have to maintain one staging table & do left join :
select tt.empid,
ISNULL(t.No_of_days, 0)
from table tt left join -- possible employee id table
[_table] t
on t.empid = tt.empid;
Assuming that you have another table with all your employees in (if not, where are employees 3, 4 and 5 coming from?), you could use a LEFT JOIN onto your example table (_table):
SELECT e.empid,
ISNULL(t.No_of_days,0)
FROM Employee e --No idea what your employee table is actually called, so guessed.
LEFT JOIN _table t ON e.empid = t.empid;
This will provide rows for all your employees. If you wanted just 3, 4 and 5, then you would add your WHERE:
WHERE e.empid IN (3,4,5)
use coalesce
select coalesce(No_of_days,0)
from _table WHERE EmpId in( 3 , 4 , 5)
BTW use In instead or and if you have to push those id in EmpId 1st then
coalesce() will work otherwise if no values in empid then it will not work

Retrieving values from table 2 using table 1

I have two tables dept and work; values are like this
INSERT INTO DEPT(DEPTID, DEPTNAME) VALUES
( 1, 'DEPT1'),
(2, 'DEPT2'),
(3,'DEPT2'),
(4,'DEPT4'),
(5,'DEPT4'),
(6,'DEPT6');
INSERT INTO WORK (WORKID, DEPTID, SAL,CITYID) VALUES
( 100,1,1000,10),
( 100,2,2000,10),
( 200,1,2500,20),
( 300,3,1000,10),
( 300,6,3000,10),
( 300,6,1000,30)
;
For the given cityid ( work table), take all deptids for each deptname (from dept table ) and match in work table, if exists display count (count of deptids present in work table) and avgsal else 0 for count and avgsal all dept names in the same order present in dept table. Output should be like this
Cityid deptname count avg-sal
10 dept1 1 1000
10 dept2 2 1500
10 dept4 0 0
10 dept6 1 3000
Grandtotal 3 2333.33
I tried thru joins, but didnt work properly
You have a very strange department table -- containing duplicates.
In any case, this is a join and group by:
select 10 as cityid, coalesce(d.deptname, 'Total') as name,
count(w.workid) as cnt,
avg(sal) as average_salary
from dept d left join
work w
on w.deptid = d.deptid and w.cityid = 10
group by grouping sets ((w.cityid, d.deptname) ());
Here is a db<>fiddle.
Note that this returns the average salary as the average over the whole population. This is usually what one wants. The value is 1750, which looks correct to me.

SQL query to return a particular column or a default value in case of row not existing in the db

I have a requirement of getting an employee id from department id (lets consider that one department will only contain 1 employee at most)
I can do something like below:
Select emp_id from emp where dept_id = 101;
Now in case this row was not existing, I won't be getting any value.
However since I am using this query in PL/SQL layer, it will throw a NO_DATA_FOUND exception (when no employee exists in db)
Can you refactor this query to return some emp_id = -1 in case no employee exists in db.
i can do something like below (which results in 2 sql queries, so not efficient)
select count(1) INTO temp_count from emp where dept_id = 101; -- or use an exists clause
if (temp_count != 0)
Select emp_id from emp where dept_id = 101;
Try:
select nvl(min(emp_id),-1)
from emp
where dept_id = 101;
Other than the provided answer ,there are multiple ways you can address this requirement.
You can use DECODE,COALESCE,NULLIF like below :
Select COALESCE(emp_id,<default value>) from emp where dept_id = 101;
Select DECODE(emp_id,NULL,<default value>,emp_id) from emp where dept_id = 101;
Go through this Link NULL-Related Functions for more examples
You can use this code block for your problem :
select t.emp_id
from (select nvl(e.emp_id
,-1) as emp_id
from emp e
where e.dept_id = 101) t
where t.emp_id <> -1
You can try this code block :
select case
when e.emp_id is null then
-1
else
e.emp_id
end as emp_id
from emp e
where e.dept_id = 101
If you're using PL/SQL, presumably a function, why not just handle the no_data_found error, rather than faffing around with the sql statement and possibly making it less efficient?
E.g. something like:
create or replace function get_emp_id (p_dept_id number)
return number
is
v_emp_id number;
begin
select emp_id
into v_emp_id
from emp
where dept_id = p_dept_id;
return v_emp_id;
exception
when no_data_found then
return -1;
end get_emp_id;
/
You can use below code
Select nvl(emp_id,default_value) from emp where dept_id = 101;
Select DECODE(emp_id,NULL,default_value,emp_id) from emp where dept_id = 101;

How to write a Query to show multiple output in same column

I have 2 tables tbl_emp & tbl_EmpSal where Emp_Id is primary_key in tbl_emp and foreign key for tbl_EmpSal as shown below.
create table tbl_emp
( Emp_Id int,
Emp_Name Varchar(20)
)
insert into tbl_emp(Emp_Id,Emp_Name)
select 1,'aaa'
union
select 2,'bbb'
union
select 3,'ccc'
union
select 4,'ddd'
--select * from tbl_emp
create table tbl_EmpSal
(Emp_id int,
EmpSal int)
insert into tbl_EmpSal
select 1,2000
union
select 2,4000
union
select 3,NULL
--select * from tbl_EmpSal
Now I want to write the SQL query to show output as below:
EMP_Name EmpSal/Details
aaa 2000
bbb 4000
ccc NEW JOINEE
ddd Contractor
The output shows that for any given Emp_id in tbl_EmpSal table if EmpSal column is NULL we have to show output as 'NEW JOINEE' in EmpSal/Details column.
And when there is no Row for any given Emp_id in tbl_EmpSal table we have to show output as 'Contractor' in EmpSal/Details column.
SELECT e.Emp_Name,
[EmpSal/Details] = CASE
WHEN s.EmpSal > 0 THEN CONVERT(VARCHAR(12), s.EmpSal)
WHEN s.Emp_id IS NOT NULL THEN 'NEW JOINEE'
WHEN s.EmpSal IS NULL THEN 'Contractor'
END
FROM dbo.tbl_emp AS e
LEFT OUTER JOIN dbo.tbl_EmpSal AS s
ON e.Emp_id = s.Emp_id;
This is a rather tricky question. It involves both a left outer join and distinguishing between a NULL value that is found and a missing match:
select Emp_Name,
(case when es.Emp_Id is NULL then 'Contractor'
when es.EmpSal is NULL then 'NEW JOINEE'
else cast(EmpSal as varchar(255))
end) as EmpSal_Details
from tbl_emp e left outer join
tbl_EmpSal es
on e.Emp_id = es.Emp_Id;