Is multiple left join is better and faster with same table ? - sql

SELECT
E.Id,
E.Name,
CASE EL.Application_Level WHEN 33 then 1 ELSE 0 END As MyDisplayName,
CASE EL1.Application_Level WHEN 34 then 1 ELSE 0 END As FoodTag
FROM
Entity E
INNER JOIN dbo.Countries CN ON cn.id=E.COUNTRY_ID
LEFT JOIN dbo.Entity_Levels EL ON E.ID = EL.Entity_ID AND EL.Application_Level = 33
LEFT JOIN dbo.Entity_Levels EL1 ON E.ID = EL1.Entity_ID AND EL1.Application_Level = 34
Instead of two join with same table i want to use OR Condition, becasue there will be more application levels added and i dont want to do add more joins.
Is there anyway i can use or condition and one join and merge multiple records to one record.

Since you don't show any sample data, this is only a guess, but it looks like you could do something like this with only 1 join to the ENTITY_LEVELS table:
SELECT
E.Id,
max(E.Name) as Name,
max(case when EL.application_level = 33 then 1 else 0 end) as MyDisplayName,
max(case when EL.application_level = 34 then 1 else 0 end) as FoodTag,
max(case when EL.application_level = 35 then 1 else 0 end) as RandomOtherThing
from entity E
INNER JOIN dbo.Countries CN ON cn.id=E.COUNTRY_ID
LEFT JOIN dbo.Entity_Levels EL ON E.ID = EL.Entity_ID
group by E.Id

SELECT E.Id
,E.Name
,CASE WHEN EL.Application_Level = 33
THEN 1
ELSE 0
END AS MyDisplayName
,CASE WHEN EL.Application_Level = 34
THEN 1
ELSE 0
END AS FoodTag
FROM Entity E WITH(NOLOCK)
INNER JOIN dbo.Countries CN WITH(NOLOCK) ON CN.id=E.COUNTRY_ID
LEFT JOIN dbo.Entity_Levels EL WITH(NOLOCK) ON E.ID=EL.Entity_ID

Related

Oracle merge two rows as single row with more columns

I have two tables Employee, Employeerows. I have to pull the employee records who has role 2 or 3.
I have the query below.
SELECT
E.ID,
E.NAME,
ER.PHONE,
ER.ADDRESS,
ER.ROLE
FROM
EMPLOYEE E LEFT JOIN EMPLOYEEROWS ER ON E.ID = ER.ID WHERE ER.ROLE_ID IN (2,3)
This returns either 1 or 2 records for each employee
ID NAME PHONE ADDRESS ROLE
1 ABC 9898989 ABC NJ 2
1 ABC 7878787 ABC XJ 3
2 DEF 7898765 DEF NJ 2
But I have to merge two records into one for that employee with phone number and address as separate columns if the employee has 2 records. My result should like this.
ID NAME PHONE ALT_PHONE ADDRESS ALT_ADDESS
1 ABC 9898989 7878787 ABC NJ ABC XJ
2 DEF 7898765 DEF NJ
Please help me with this.
You can use conditional aggregation. But the left join seems superfluous. You only want to use the left join if you want "employees" that have no rows.
It appears from your data that "2" corresponds to the primary address and "3" to the alternate:
select e.id, e.name,
max(case when er.role_id = 2 then er.phone end) as phone,
max(case when er.role_id = 3 then er.phone end) as alt_phone,
max(case when er.role_id = 2 then er.address end) as address,
max(case when er.role_id = 3 then er.address end) as alt_address
from employee e join
employeerows er
on e.id = er.id
where er.role_id in (2, 3)
group by e.id, e.name;
However, if "2" could be missing, then you would phrase this as:
select e.id, e.name,
max(case when seqnum = 2 then er.phone end) as phone,
max(case when seqnum = 3 then er.phone end) as alt_phone,
max(case when seqnum = 2 then er.address end) as address,
max(case when seqnum = 3 then er.address end) as alt_address
from employee e join
(select er.*,
row_number() over (partition by er.id order by er.role_id) as seqnum
from employeerows er
where er.role_id in (2, 3)
) er
on e.id = er.id
group by e.id, e.name;
You can pivot with conditional aggregation:
select e.id, e.name,
max(case when er.role_id = 2 then er.phone end) as phone,
max(case when er.role_id = 3 then er.phone end) as alt_phone,
max(case when er.role_id = 2 then er.address end) as address,
max(case when er.role_id = 3 then er.address end) as alt_address
from employee e
left join employeerows er on e.id = er.id where er.role_id in (2,3)
group by e.id, e.name
You can use conditional aggregation. But your query is not doing LEFT OUTER JOIN but it is INNER JOIN as you have used the er.role_id in (2,3) in WHERE clause.
Use the following aggreagation technique to fetch the desired result:
SELECT
E.ID,
E.NAME,
MIN(ER.PHONE),
CASE WHEN MIN(ER.PHONE) <> MAX(ER.PHONE) THEN MAX(ER.PHONE) END AS ALT_PHONE,
MIN(ER.ADDRESS),
CASE WHEN MIN(ER.ADDRESS) <> MAX(ER.ADDRESS) THEN MAX(ER.ADDRESS) END AS ALT_ADDRESS
FROM EMPLOYEE E
LEFT JOIN EMPLOYEEROWS ER ON E.ID = ER.ID
AND ER.ROLE_ID IN (2,3) -- added it in the join condition
GROUP BY E.ID, E.NAME;

merge multiple tables (belonging to group 'x')

I have three tables in the database:
customer -table
customername
customerid
group -table
groupid
groupname
customer-group table
customerid
groupid
What kind of SQL generates the output:
customername, 'X' if the customer belongs to a group 1, 'X' if customer belongs to a group 2, ...
(Group number is limited to 7.)
For example:
Matt, 'X', '' , 'X', '' , 'x', '' , ''
Mark, '' , 'X', '' , 'X', '' , 'X', ''
Thanks
You can do conditional aggregation:
select c.customername,
max(case when cg.groupid = 1 then 'X' end) has_grp1,
max(case when cg.groupid = 2 then 'X' end) has_grp2,
max(case when cg.groupid = 3 then 'X' end) has_grp3,
max(case when cg.groupid = 4 then 'X' end) has_grp4,
max(case when cg.groupid = 5 then 'X' end) has_grp5,
max(case when cg.groupid = 6 then 'X' end) has_grp6,
max(case when cg.groupid = 7 then 'X' end) has_grp7
from customer c
inner join customer_group cg on cg.customerid = c.customerid
group by c.customerid, c.customername
Notes:
You did not tell which database you are running; the above query uses standard case expressions, that (almost) all databases support. Your database may have neater alternatives though
It does not look like we need table group to generate the desired result; if that's needed for some reason (say you want to pivot over the group name rather than over the group id), you can just add another join to the query
In the code below I am joining the main table which is the customer with the navigation table customer-group multiple times once for each possible group.
Since I am using left joins, I am ensuring the the left part each time will always be returned and the if the right part does not exist then it will be returned as a null value
select
customername,
case when cg1.groupid is null then '' else 'X' end as 'ExistsInGroup1',
case when cg2.groupid is null then '' else 'X' end as 'ExistsInGroup2',
case when cg3.groupid is null then '' else 'X' end as 'ExistsInGroup3',
case when cg4.groupid is null then '' else 'X' end as 'ExistsInGroup4',
case when cg5.groupid is null then '' else 'X' end as 'ExistsInGroup5',
case when cg6.groupid is null then '' else 'X' end as 'ExistsInGroup6',
case when cg7.groupid is null then '' else 'X' end as 'ExistsInGroup7'
from customer c
left join [customer-group] cg1
on c.customerid = cg.customerid
and groupid = 1
left join [customer-group] cg2
on c.customerid = cg.customerid
and groupid = 2
left join [customer-group] cg3
on c.customerid = cg.customerid
and groupid = 3
left join [customer-group] cg4
on c.customerid = cg.customerid
and groupid = 4
left join [customer-group] cg5
on c.customerid = cg.customerid
and groupid = 5
left join [customer-group] cg6
on c.customerid = cg.customerid
and groupid = 6
left join [customer-group] cg7
on c.customerid = cg.customerid
and groupid = 7
As many problems in the world this is also able to be solved with many different approaches. For instance you could use many subqueries for each field like so:
select
customername,
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 1) then 1 else 0 end as 'ExistsInGroup1',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 2) then 1 else 0 end as 'ExistsInGroup2',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 3) then 1 else 0 end as 'ExistsInGroup3',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 4) then 1 else 0 end as 'ExistsInGroup4',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 5) then 1 else 0 end as 'ExistsInGroup5',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 6) then 1 else 0 end as 'ExistsInGroup6',
case when exists (select top 1 1 from [customer-group] cg where c.customerid = cg.customerid and groupid = 7) then 1 else 0 end as 'ExistsInGroup7'
from customer c
Another example of a solution would be to utilize a pivot. A bit more advanced case. You may check documentation for SqlServer PIVOT here
select c.customername,
iif(g.groupname=1, 'x', ''),
iif(g.groupname=2, 'x', ''),
iif(g.groupname=3, 'x', '') -- etc etc
from Customer c inner join customer-Group cg on c.customerid = cg.customerid
inner join group g on cg.groupid=g.groupid

Add flag for row existence in another table with group by

For below query I want to have a flag called isHold that will evaluate to 0
if there is no billNo from the view viewBills exists in onHold table and
1 otherwise
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when exists (select 1 from OnHold oh
where oh.billNo = billNo) then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
where cb.statusid <> 3
group by cb.id
I got this error when executing
Cannot perform an aggregate function on an expression
containing an aggregate or a subquery.
It's reasonable but how can achieve that?
You can use outer apply or a left join to move the logic to the FROM clause:
select max(t.id) as TrackingID , max(vb.billNo) as billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) as isMultiple,
max(case when t.TrackingID = 31 then 1 else 0 end) as IsCancelled,
max(case when oh.billNo is not null then 1 else 0 end) as IsHold
from viewBills vb join
tracking t
on vb.billNo = t.billNo join
customerBills cb
on vb.billNo = cb.billNo join
customerPieces cp
on cb.id = cp.customerBillId outer apply
(select top (1) oh.*
from OnHold oh
where oh.billNo = cb.billNo
) oh
where cw.statusid <> 3
group by cb.id;
You can go for LEFT OUTER JOIN and do the aggregation as given below:
select max(t.id) TrackingID , max(vb.billNo) billNo, cb.id ,
max(case when vb.[count] > 1 then 1 else 0 end) isMultiple ,
max(case when t.TrackingID = 31 then 1 else 0 end) IsCancelled,
max(case when oh.billNo IS NOT NULL then 1 else 0 end) IsHold
from viewBills vb
join tracking t on vb.billNo = t.billNo
join customerBills cb on vb.billNo = cb.billNo
join customerPieces cp on cb.id = cp.customerBillId
LEFT OUTER JOIN OnHold oh ON oh.billNo = vb.billNo
where cb.statusid <> 3
group by cb.id

Nested Oracle SQL - Multiple Values

I have a table structure like:
Table = contact
Name Emailaddress ID
Bill bill#abc.com 1
James james#abc.com 2
Gill gill#abc.com 3
Table = contactrole
ContactID Role
1 11
1 12
1 13
2 11
2 12
3 12
I want to select the Name and Email address from the first table where the person has Role 12 but not 11 or 13. In this example it should return only Gill.
I believe I need a nested SELECT but having difficulty in doing this. I did the below but obviously it isn't working and returning everything.
SELECT c.Name, c.Emailaddress FROM contact c
WHERE (SELECT count(*) FROM contactrole cr
c.ID = cr.ContactID
AND cr.Role NOT IN (11, 13)
AND cr.Role IN (12)) > 0
You can use a combination of EXISTS and NOT EXISTS
SELECT *
FROM contact c
WHERE
EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role = 12)
AND NOT EXISTS(SELECT 1 FROM contactrole cr WHERE cr.ContactID = c.ID AND cr.Role IN(11, 13))
Another option is to use GROUP BY and HAVING:
SELECT c.*
FROM contact c
INNER JOIN contactrole cr
ON cr.ContactID = c.ID
GROUP BY
c.ID, c.Name, c.Emailaddress
HAVING
SUM(CASE WHEN cr.Role = 12 THEN 1 ELSE 0 END) > 0
AND SUM(CASE WHEN cr.Role IN(11, 13) THEN 1 ELSE 0 END) = 0
Use conditional aggregation in Having clause to filter the records
Try this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) > 1
AND Count(CASE WHEN role in (11,13) THEN 1 END) = 0)
If you have only 11,12,13 in role then use can use this
SELECT c.NAME,
c.emailaddress
FROM contact c
WHERE id IN (SELECT contactid
FROM contactrole
GROUP BY contactid
HAVING Count(CASE WHEN role = 12 THEN 1 END) = count(*)
You can do this using JOINs:
SELECT c.*
FROM CONTACT c
INNER JOIN CONTACTROLE cr12
ON cr12.CONTACTID = c.ID AND
cr12.ROLE = 12
LEFT OUTER JOIN CONTACTROLE cr11
ON cr11.CONTACTID = c.ID AND
cr11.ROLE = 11
LEFT OUTER JOIN CONTRACTROLE cr13
ON cr13.CONTACTID = c.ID AND
cr13.ROLE = 13
WHERE cr11.ROLE IS NULL AND
cr13.ROLE IS NULL
The INNER JOIN CONTACTROLE cr12 requires that role 12 exist for the given contact ID; the LEFT OUTER JOIN CONTACTROLE cr11 and LEFT OUTER JOIN CONTRACTROLE cr13 check to see if roles 11 and 13 might exist for the given contact ID; and the WHERE clause verifies that neither roles 11 or 13 exist.
Best of luck.

Join multiple tables, select counts from different tables and group by one column in one query

I need to join multiple tables, select counts from different tables and group by one column in one query. This is how I would do this separately:
select c.CommunityName, SUM(case when m.ListKey = c.ListKey then 1 else 0 end) as Posts
from Community c with(NOLOCK)
join messages_ m with(NOLOCK)
on c.ListKey = m.ListKey
group by c.CommunityName
select c.CommunityName, SUM(case when b.CommunityKey = c.CommunityKey then 1 else 0 end) as Blogs
from Community c with(NOLOCK)
join Blog b with(NOLOCK)
on c.CommunityKey = b.CommunityKey
group by c.CommunityName
select c.CommunityName, SUM(case when ce.CommunityKey = c.CommunityKey then 1 else 0 end) as Events
from Community c with(NOLOCK)
join CalendarEvent ce with(NOLOCK)
on c.CommunityKey = ce.CommunityKey
where ce.StartDateTime >= GETDATE()
group by c.CommunityName
or simply
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join messages_ m with(NOLOCK)
on c.ListKey = m.ListKey
group by c.CommunityName
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join Blog b with(NOLOCK)
on c.CommunityKey = b.CommunityKey
group by c.CommunityName
select c.CommunityName, COUNT(*)
from Community c with(NOLOCK)
join CalendarEvent ce with(NOLOCK)
on c.CommunityKey = ce.CommunityKey
where ce.StartDateTime >= GETDATE()
group by c.CommunityName
There are more tables, some that require additional joins... Can someone please help?
If I understand your question correctly, you are looking for community name along with the counts such as posts, blogs, event etc..
As your queries count individually, add dummy columns in the SELECT for the other counts and then in the end UNION them and get the SUM.
SELECT CommunityName , SUM(MessageCount), SUM(BlogCount), SUM(EventCount)
FROM (
SELECT c.CommunityName CommunityName , COUNT(*) MessageCount, 0 BlogCount, 0 EventCount
FROM Community c with(NOLOCK)
JOIN messages_ m with(NOLOCK)
ON c.ListKey = m.ListKey
GROUP BY c.CommunityName
UNION
SELECT c.CommunityName, 0, COUNT(*), 0
FROM Community c with(NOLOCK)
JOIN Blog b with(NOLOCK)
ON c.CommunityKey = b.CommunityKey
GROUP BY c.CommunityName
UNION
SELECT c.CommunityName, 0, 0, COUNT(*)
FROM Community c with(NOLOCK)
JOIN CalendarEvent ce with(NOLOCK)
ON c.CommunityKey = ce.CommunityKey
WHERE ce.StartDateTime >= GETDATE()
GROUP BY c.CommunityName
) CountsTable
GROUP BY CountsTable.CommunityName
CountsTable will look like
| COMMUNITYNAME | MESSAGECOUNT | BLOGCOUNT | EVENTCOUNT |
|---------------|--------------|-----------|------------|
| Name | 10 | 0 | 0 |
| Name | 0 | 20 | 0 |
| Name | 0 | 0 | 30 |
So, you can GROUP BY name and sum up the counts to get your result
| COMMUNITYNAME | MESSAGECOUNT | BLOGCOUNT | EVENTCOUNT |
|---------------|--------------|-----------|------------|
| Name | 10 | 20 | 30 |
Have you thought about using LEFT JOIN to connect your tables? Then you can check for NULLs and sum up the non-NULL values.
SELECT
c.CommunityName,
SUM(case when m.ListKey IS NOT NULL then 1 else 0 end) as Posts,
SUM(case when b.CommunityKey IS NOT NULL then 1 else 0 end) as Blogs,
SUM(case when ce.CommunityKey IS NOT NULL then 1 else 0 end) as Events
FROM
Community c WITH(NOLOCK)
LEFT JOIN
messages_ m WITH(NOLOCK)
ON c.ListKey = m.ListKey
LEFT JOIN
Blog b WITH(NOLOCK)
ON c.CommunityKey = b.CommunityKey
LEFT JOIN
CalendarEvent ce WITH(NOLOCK)
ON c.CommunityKey = ce.CommunityKey
WHERE
ce.StartDateTime >= GETDATE()
GROUP BY
c.CommunityName