oracle join or union - sql

I have a condition where i get the users data from table A or table B wherever the data exists.
I cannot write a condition in java but it has to be done totally in oracle and result set sent to UI.
How can i do this in oracle sql.
Users table:
userId firstName LastName MI
11 AAA 111 A1
12 BBB 222 B2
TableA
UserId ROLE firstName LastName MI Grade
11 MANAGER AAA 111 A1 A1
TableB
UserId ROLE firstName LastName MI Grade
12 LEAD BBB 222 B2 A4
OutPut should be:
UserId FirstName LastName MI ROLE Grade
11 AAA 111 A1 MANAGER A1
12 BBB 222 B2 LEAD A4
Select * from users u, tableA a, tableB b
where u.userId = a.userId
and u.userId = b.userId
and u.userId = :userId
Where :userId is the data passed from UI.
I have to get the data from tableA 1st and only when not found I have to get from tableB
Should I do a union or join which is more efficient?

You can try to use UNION ALL on tableA and tableB, then do JOIN instead of ,(CROSS JOIN)
SELECT u,*,t1.ROLE,t1.Grade
FROM (
SELECT UserId ,ROLE ,Grade
FROM tableA
UNION ALL
SELECT UserId ,ROLE,Grade
FROM tableB
) t1 INNER JOIN users u
ON u.UserId = t1.UserId

I would use left join:
select u.*,
(case when a.userid is not null then 'MANAGER' else 'LEAD' end) as role,
coalesce(a.grade, b.grade) as grade
from users u left join
tablea a
on a.userid = u.userid left join
tableb b
on b.userid = u.userid;

Related

How to count result values from join query in another table?

I have two tables like so:
table1(user, id, sex) table2(user, name, sex)
jjj 123 m jjj John m
jjj 124 m bbb Bob m
jjj 125 m ppp Pete f
bbb 126 m
bbb 127 f
ppp 128 f
ppp 129 m
ppp 130 m
I want result table where it displays all the users with their names and sex from table 2 who changed their sex at some point along with a count of how many users each name has. So this would be the result table:
(user, name, sex, count)
bbb Bob m 2
ppp Pete f 3
Currently im using this query:
select table2.user, table2.name, table2.sex, count(*)
from table1
join table2 on table1.user = table2.user
where table1.sex <> table2.sex
group by table2.user, table2.name, table2.sex
order by user
However the count column just counts from the resulting join table and not from original table1. Any ideas? thanks.
If I follow this correctly, one option use a lateral join and filtering:
select t2.*, t1.cnt
from table2 t2
inner join lateral (
select count(*) as cnt, min(sex) as minsex, max(sex) as maxsex
from table1 t1
where t1.user = t2.user
) t1 on t1.minsex <> t1.maxsex or t1.minsex <> t2.sex
Basically this filters table1 on users that have different sex or whose sex is different than in table2.

Can't Retrieve Exact Result with LEFT JOIN

I have three tables with the following structures:
User Table
UserID Name Age
------ ---- ---
001 AA 23
002 BB 25
003 CC 30
004 DD 24
005 EE 23
temp_User Table
UserID Name Age
------ ---- ---
001 AA 23
002 BB 25
004 DD 24
005 EE 23
007 GG 23
009 HH 28
ExceptionUsers Table
UserID Status
------ --------
021 Active
002 Inactive
004 Active
010 Active
012 Inactive
I used the following query to retrieve my result:
Query
select
A.Name
,B.Name
,A.Age
,B.Age
from User A
inner join temp_User B
on A.UserID = B.UserID
left join ExceptionUsers C
on A.UserID = C.UserID
and C.Status = 'Inactive'
order by
A.Name
Result
001 AA 23
002 BB 25
005 EE 23
But the result includes users who are 'Active'.
001 AA 23
002 BB 25
004 DD 24
005 EE 23
How can I try the query to get my result?
Move C.Status = 'Inactive' to the where clause.
I'd move the LEFT JOIN logic to the WHERE clause, to a NOT EXISTS.
select A.Name, B.Name, A.Age, B.Age
from User A
inner join temp_User B
on A.UserID = B.UserID
where not exists (select * from ExceptionUsers C
where A.UserID = C.UserID
and C.Status = 'active')
order by A.Name
Left join does not filter the data provided on joining condition.
So you may move the joining condition and C.Status = 'Inactive' to where clause and it treats as inner join
select A.Name, B.Name, A.Age, B.Age
from User A
inner join temp_User B
on A.UserID = B.UserID
left join ExceptionUsers C
on A.UserID = C.UserID
where
C.Status = 'Inactive'
order by A.Name
you can use the below query to get the list of active user with their details.
select * from temp_User where UserID in (select UserID from ExceptionUsers where Status = 'Inactive')

Using 'AND' in a many-to-many relationship

I have a Users table and a Groups table. Users can be in multiple groups via a 'UserInGroup' table and Groups can have a 'GroupTypeId'.
[User]
--------------
Id | Name
1 | Bob
2 | James
[UserInGroup]
-----------------
UserId | GroupId
1 1
1 2
[Group]
Id | Name | TypeId
------------------------
1 | Directors | 1
2 | IT | 1
3 | London | 2
I want to create a query to return for example users that are in both 'Directors' AND 'London' (rather than 'Directors' OR 'London'). However, I only want to AND groups of a different 'Type', I want to OR groups of the same type. I could do with having a separate table per group type but I can't as they are created dynamically.
Ideally I want to be able to query users who are in 'Directors' OR 'IT' AND 'London'.
What is the most efficient way of doing this?
This problem is commonly known as Relational Division.
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(*) = 2
SQLFiddle Demo
SQL of Relational Division
But if a UNIQUE constraint was not enforce on GROUP for every USER, DISTINCT keywords is needed to filter out unique groups:
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(DISTINCT c.Name) = 2
OUTPUT from both queries
╔══════╗
║ NAME ║
╠══════╣
║ Bob ║
╚══════╝
I arrived at the following solution (with help from J W and this article):
SELECT
u.Name UserName
FROM [User] u
INNER JOIN [UserInGroup] uig
ON uig.UserId = u.Id
INNER JOIN [Group] g
ON g.Id = uig.GroupId
WHERE
g.Id IN (1,2,3) -- these are the passed in groupids
GROUP BY
u.Name
having count(distinct g.TypeId)
= (select count(distinct g1.TypeId)
from [group] g1 where g1.Id IN (1,2,3))
This allows me to group the relational division by a discriminator field. An alternative would be this:
SELECT a.Name
FROM [User] a
INNER JOIN
(
SELECT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name IN ('Directors','IT')
GROUP BY b.UserID
HAVING COUNT(DISTINCT c.Name) >= 1
) b ON a.ID = b.UserID
INNER JOIN
(
SELECT DISTINCT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name = 'London'
) c ON a.ID = c.UserID
With an extra join for each GroupTypeId. Execution plans look similar, so I went with the first option.

Join a table with one table if condition1 is true and with another table if condition1 is false?

I have the following tables :
User_Group
id group_id group_type
------------------------
1 100 A
1 100 B
2 101 B
2 102 A
Group_A
id name
---------
100 A
101 B
102 C
Group_B
id name
---------
100 D
101 E
102 F
I want the group names of all users (using array.agg()). We have to get the group name from group A if the user's group type = A and from group B if the user's group type = B. The result should be :
userid groups
--------------
1 A,D
2 E,C
I have created a fiddle for this, and given a solution using union of 2 separate queries. Can it be done without the union, something in which I can decide on which table to pick the group name from with a single join of user_groups, group_A and group_B ?
select ug.id, array_agg(
case ug.group_type
when 'A' then g_a.name
when 'B' then g_b.name
else 'N/A'
end)
from user_groups ug
left outer join group_A g_a on ug.group_id = g_a.id
left outer join group_B g_b on ug.group_id = g_b.id
group by ug.id
SQL Fiddle Example
You can do this without union using left joins (I'd advise using explicit joins anyway since implicit joins are 20 years out of date Aaron Bertrand has written a good blog as to why). The Group_Type can become a join condition meaning the table is only joined when the right group type is found:
SELECT ug.ID, ARRAY_AGG(COALESCE(a.Name, b.Name))
FROM User_Groups ug
LEFT JOIN group_A a
ON a.ID = ug.group_ID
AND ug.Group_Type = 'A'
LEFT JOIN group_B b
ON b.ID = ug.group_ID
AND ug.Group_Type = 'B'
WHERE COALESCE(a.ID, b.ID) IS NOT NULL -- ENSURE AT LEAST ONE GROUP IS MATCHED
GROUP BY ug.ID;
However I would be inclined to use a UNION Still, but move it as follows:
SELECT ug.ID, ARRAY_AGG(Name)
FROM User_Groups ug
INNER JOIN
( SELECT 'A' AS GroupType, ID, Name
FROM Group_A
UNION ALL
SELECT 'B' AS GroupType, ID, Name
FROM Group_B
) G
ON g.GroupType = ug.Group_Type
AND g.ID = ug.Group_ID
GROUP BY ug.ID;
Your Fiddle with my queries added

SQL Query: joining two fields from two separate rows

I have the following two tables:
USER
FID UID VALUE
4 3 John
3 3 Doe
4 4 Jack
3 4 Russel
Should be fairly clear that FID 3 = Surname, and FID 4 = Name.
DATEDATA
UID DATE
3 1234
4 4321
I want to join these two tables, so that I end up with something like this:
UID DATE NAME SURNAME
3 1234 John Doe
4 4321 Jack Russel
or... alternatively...
UID DATE FULLNAME
3 1234 John Doe
4 4321 Jack Russel
Any SQL gurus out there?
This is what I have so far:
SELECT UID, DATE, VALUE
from DATEDATA as D
left join USER as U
on U.uid = D.uid where fid = 3 OR fid = 4
But that gives me this:
UID DATE VALUE
3 1234 Doe
3 1234 John
4 4321 Russel
4 4321 Jack
Anyone?
SELECT D.UID, DATE, U.VALUE + ' ' + U2.Value as fullName
from DATEDATA as D
left join USER as U on U.uid = D.uid and U.fid = 3
left join USER as U2 on U2.uid = D.uid and U2.fid = 4
Though this could give you a NULL name whenever either first or last is NULL. You may want to use an ISNULL to make either name an empty string in that case if you can accept cases where the user would only have one name or the other in your system.
SELECT A.UID, DATEDATA.DATE, A.VALUE, B.VALUE from DATEDATA, USER A, USER B
WHERE A.UID = B.UID AND A.FID = 3 AND B.FID = 4 AND DATEDATA.UID = A.UID
select
D.UID
, D.DATE
, isnull(U.VALUE, '') 'firstName'
, isnull(U2.VALUE, '') 'surName'
from
DateData D
left join User U on U.uid = D.uid and U.fid = 3
left join User U2 on U2.uid = D.uid and U2.fid = 4
You can use something like the following. It assumes you always have a first name. If you always have some field and it's not first name, make that the first from and readjust the joins. If you're never guaranteed a value, then let us know and we'll work a harder solution.
select d.uid,COALESCE(d.date,'none entered'),COALESCE(frst.value,'') as NAME,COALESCE(lst.value,'') as SURNAME
from
user frst (NOLOCK)
left join user lst (NOLOCK) on lst.uid=frst.uid
left join datedata d (NOLOCK) on d.uid = frst.uid
where
frst.fid=4
AND lst.fid=3