Joining a table (or not) based on a condition in SQL - sql

I have the three tables following in my SQL Server 2008 database:
Cars
Drivers
UnavailableDrivers
What I want is to show unavailableDrivers to the end users if they set #isAvailable = 0. On the other hand, if isAvailable=1, then the end user should see only available drivers. Finally, if isAvailable is NULL then the user should see all the drivers.
declare #isAvailable bit;
I need to write a query like that:
Select *
From Cars c
Inner join Drivers d on (c.driverId = d.Id)
CASE
WHEN #isAvailable = 0 THEN inner join UnavailableDrivers uc on (c.driverId= uc.Id)
WHEN #isAvailable = 1 THEN inner join UnavailableDrivers uc on (c.driverId != uc.Id)
ELSE #IsAvailable END
-- ELSE -> basically DO NOT JOIN UnavailableDrivers
It is giving incorrect syntax error and I couldn't find the right syntax for a few hours unfortunately. Actually I don't feel the query is right either. So any help to fix my query on this logic would be appreciated!

The following query do you want:
SELECT *
FROM Cars c
INNER JOIN Drivers d ON (c.driverId = d.Id)
WHERE (#isAvailable IS NULL) -- will not join to UnavailableDrivers is isAvailable is null
OR (#isAvailable = 0 AND c.driverId IN (SELECT uc.id FROM UnavailableDrivers uc)) -- will join to UnavailableDrivers only if isAvailable equals to 0
OR (#isAvailable = 1 AND c.driverId NOT IN (SELECT uc.id FROM UnavailableDrivers uc)) -- will join to UnavailableDrivers because isAvailable equals to 1

Terrible approach, but you asked for it
USE [DB]
GO
CREATE PROCEDURE [GetDrivers]
#isAvailable bit
AS
BEGIN
IF (#isAvailable = 0)
BEGIN
Select * From Cars c
Inner join Drivers d on (c.driverId = d.Id)
inner join UnavailableDrivers uc on (c.driverId= uc.Id)
END
ELSE
begin
Select * From Cars c
Inner join Drivers d on (c.driverId = d.Id)
inner join UnavailableDrivers uc on (c.driverId!= uc.Id)
end
END

SELECT
*
, CASE
WHEN d.DriverId IS NOT NULL THEN 'Availible'
WHEN u.DriverId IS NOT NULL THEN 'Unavailible'
ELSE 'Driver is in both'
AS 'Driver Status'
FROM Cars c
LEFT JOIN Drivers d
ON c.DriverId = d.DriverId
LEFT JOIN UnavailableDrivers u
ON u.DriverId = c.DriverId

Related

How to only run left join when the variable is not null?

I'm trying to simplify my stored procedure and I have one that is only using left join based on the user id that is passed in. If the user id is null, don't do left join, but if it is null, left join it with another table. How should I re-write it ? Thank you
CREATE OR ALTER PROCEDURE [dbo].[GetRoom]
#RoomId UNIQUEIDENTIFIER NULL,
#UserId UNIQUEIDENTIFIER
AS
BEGIN
IF (#UserId IS NULL)
BEGIN
SELECT r.Id, r.DisplayName
FROM Room r
INNER JOIN Game g ON r.GameId = g.Id
INNER JOIN ProfileDuplicated pd ON r.HostedById = pd.Id
WHERE r.Id = #RoomId
END
ELSE
SELECT
r.Id, r.DisplayName,
ru.Description, -- this is coming from the left join table
ru.Tags -- from left join table
FROM Room r
INNER JOIN Game g ON r.GameId = g.Id
INNER JOIN ProfileDuplicated pd ON r.HostedById = pd.Id
LEFT JOIN RoomUser ru ON ru.RoomId = r.Id
WHERE r.Id = #RoomId AND ru.UserId = #UserId
END
Currently your stored procedure return different number columns depending on the #UserId.
You may remove the IF condition, and combined it as one single query by moving ru.UserId = #UserId to ON condition. This will make it a true LEFT JOIN to table RoomUser.
This will means it always return 4 columns as result
SELECT r.Id,
r.DisplayName,
ru.Description,
ru.Tags
FROM Room r
INNER JOIN Game g ON r.GameId = g.Id
INNER JOIN ProfileDuplicated pd ON r.HostedById = pd.Id
LEFT JOIN RoomUser ru ON ru.RoomId = r.Id
AND ru.UserId = #UserId
WHERE r.Id = #RoomId
Try something like below,
SELECT
r.Id, r.DisplayName,
ru.Description, -- this is coming from the left join table
ru.Tags -- from left join table
FROM Room r
INNER JOIN Game g ON r.GameId = g.Id
INNER JOIN ProfileDuplicated pd ON r.HostedById = pd.Id
LEFT JOIN RoomUser ru ON ru.RoomId = r.Id
AND r.Id IS NOT NULL
AND r.Id = #RoomId
AND ru.UserId IS NOT NULL
AND ru.UserId = #UserId
You can use ISNULL function to check the NULL value of the parameter. (Assuming ru.userId will not be NULL)
WHERE r.Id = #RoomId AND
ru.UserId = ISNULL(#UserId, ru.UserId)

How do I go about checking the existence of records in all tables JOIN`ed in a statement?

I want ensure none of the tables included through INNER JOIN return 0 rows, resulting in an empty result-set due to the join being INNER. I want to be able to return information to the user about which table had no records, causing the query to return empty.
PS: Please ignore Foreign Keys for this question.
Example-query:
SELECT * FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
INNER JOIN Email E ON E.UserId = U.Id
INNER JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
Let's say the user had no associated Email-records. I then want to be able to tell the user about this. Note: I only need to print the first failing step, I.E if the user has no Email and no Company, I only need to tell the user about him not having any Email.
How I would have solved it earlier:
I would perform the complete query in steps, building it up join by join, doing a whole lot of redundant querying. I really don't like this solution at all, which is why I'm asking for help.
-- Ensure Person exists
IF NOT EXISTS (
SELECT * FROM Person P
WHERE P.Id = 3
)
BEGIN
PRINT 'No associated Person was found';
RETURN 1;
END
-- Ensure User exists
IF NOT EXISTS (
SELECT * FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
WHERE P.Id = 3
)
BEGIN
PRINT 'No associated User was found';
RETURN 1;
END
And so on. How can I write this more concise, in a way that solves the same problem but avoids repeating queries?
Thanks in advance.
Update
By looking at the answers, I realized my example was bad. Using LEFT JOIN is ok in this example, since the "join-chain" is not straight; Person joins User, then User joins in multiple directions, like this:
Person - User - Email
|
Company
I'll try to provide a different example:
SELECT * FROM a
INNER JOIN b ON b.a_id = a.id
INNER JOIN c ON c.b_id = b.id
INNER JOIN d ON d.c_id = c.id
INNER JOIN e ON e.d_id = d.id
INNER JOIN f ON f.e_id = e.id
INNER JOIN g ON g.f_id = f.id
The join-chain would then look like this:
a - b - c - d - e - f - g
If I were to use LEFT JOIN's to ensure existence of records, the IIF/CASE statements would get pretty terrible. Example:
SELECT
CASE WHEN b.id is null THEN 'b is null' END as b_is_null
CASE WHEN b.id is not null and c.id is null THEN 'b is null' END as c_is_null
..
CASE WHEN b.id is not null and c.id is not null and d.id is not null and e.id is not null and f.id is not null and g.id is null THEN 'g is null' AS g_is_null
FROM a
LEFT JOIN b ON b.a_id = a.id
LEFT JOIN c ON c.b_id = b.id
LEFT JOIN d ON d.c_id = c.id
LEFT JOIN e ON e.d_id = d.id
LEFT JOIN f ON f.e_id = e.id
LEFT JOIN g ON g.f_id = f.id
It can get really ugly. And these examples are with 1-character alias-names and 2-character property-names.
Keep in mind, I also want to check if the first table (Person/a, the one not joined) also returns rows.
You need to use left join. From your query, if a person doesn't have any user they obviously doesn't have any email or company. So all left join will be fine.
SELECT P.*,
IIF(U.PersonID IS NULL, 0, 1) AS isUserExists,
IIF(E.UserId IS NULL, 0, 1) AS isEmailExists,
IIF(C.Id IS NULL, 0, 1) AS isCompanyInformationExists
FROM Person P
LEFT JOIN [User] U ON U.PersonId = P.Id
LEFT JOIN Email E ON E.UserId = U.Id
LEFT JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
If U.PersonID IS NULL then no user existed, if E.UserId IS NULL then no email existed, if C.Id IS NULL then no company information existed for that person. By this you can use your messages as you need.
Use case statements like below and print your message.
select
case when u.PersonId is null and p.id is not null then 'user not exists'
WHEN u.PersonId IS NOT NULL AND p.id IS NOT NULL AND e.mailid is null THEN 'mail id not exist'
else 'No associated Person was found' end message
FROM Person P
left JOIN [User] U ON U.PersonId = P.Id
left JOIN Email E ON E.UserId = U.Id
left JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
DECLARE #Email VARCHAR(50)
DECLARE #Company VARCHAR(50)
SELECT #Email = E.Email, #Company = C.CompanyName FROM Person P
INNER JOIN [User] U ON U.PersonId = P.Id
LEFT JOIN Email E ON E.UserId = U.Id
LEFT JOIN Company C on C.Id = U.CompanyId
WHERE P.Id = 3
IF #Email IS NULL
Print 'NO Email'
IF #Company IS NULL
Print 'No Company'

Combine results without using intersect in SQL SERVER 2010

is there any way that i can restructure this below query without using intersect. Because using intersect causing very slow performance. Please suggest
SELECT DISTINCT( PFM.id ) AS PfmFolderFK
FROM cm.pfmfolder PFM WITH(nolock)
INNER JOIN cm.pfmfoldermstipmap PFMMST WITH(nolock)
ON PFMMST.pfmfolderfk = PFM.id
INNER JOIN cm.mstip MST WITH(nolock)
ON MST.id = PFMMST.mstipfk
WHERE MST.registrycode = #RegistryCode
AND PFM.deletedby IS NULL
AND PFM.deleteddate IS NULL
INTERSECT
SELECT DISTINCT( FMAP.pfmfolderfk ) AS PfmFolderFK
FROM cm.mstip MIP WITH(nolock)
INNER JOIN cm.pfmfoldermstipmap FMAP WITH(nolock)
ON MIP.id = FMAP.mstipfk
AND MIP.registrycode = #RegistryCode
AND MIP.deletedby IS NULL
AND MIP.deletedate IS NULL
An intersect is taking values from both tables. The queries are quite similar here, so I think you just need an additional join in the first table to complete the logic without an intersect:
SELECT DISTINCT( PFM.id ) AS PfmFolderFK
FROM cm.pfmfolder PFM WITH(nolock) INNER JOIN
cm.pfmfoldermstipmap PFMMST WITH(nolock)
ON PFMMST.pfmfolderfk = PFM.id INNER JOIN
cm.mstip MST WITH(nolock)
ON MST.id = PFMMST.mstipfk INNER JOIN
cm.pfmfoldermstipmap FMAP WITH(nolock)
ON PFMMST.id = FMAP.mstipfk AND
PFMMST.registrycode = #RegistryCode AND
PFMMST.deletedby IS NULL AND
PFMMST.deletedate IS NULL
WHERE MST.registrycode = #RegistryCode AND
PFM.deletedby IS NULL AND
PFM.deleteddate IS NULL;
How about this? I think you need to take only the first part of your query and add two contitions to it.
SELECT DISTINCT FMAP.pfmfolderfk AS PfmFolderFK
FROM cm.mstip MST WITH(nolock)
INNER JOIN cm.pfmfoldermstipmap FMAP WITH(nolock) ON MST.id = FMAP.mstipfk
INNER JOIN cm.pfmfolder PFM WITH(nolock) ON FMAP.pfmfolderfk = PFM.id
WHERE MST.registrycode = #RegistryCode
AND PFM.deletedby IS NULL
AND PFM.deleteddate IS NULL
AND MST.deletedby IS NULL
AND MST.deletedate IS NULL
Queries with DISTINCT are always dubious ;-) You are looking for pfmfolders where certain records exist in pfmfoldermstipmap and mstip. Use EXISTS for that. Actually you are looking for all pfmfolders that are in pfmfoldermstipmap and mstip and are either not deleted themselves or having not deleted mstips:
select id
from cm.pfmfolder
where exists
(
select *
from cm.pfmfoldermstipmap
inner join cm.mstip on mstip.id = pfmfoldermstipmap.mstipfk
and pfmfoldermstipmap.pfmfolderfk = pfmfolder.id
and mstip.registrycode = #registrycode
and
(
(pfmfolder.deletedby is null and pfmfolder.deleteddate is null)
or
(mstip.deletedby is null and mstip.deleteddate is null)
)
);

SQL Server Query Returning Same Row Twice

I seem to be having trouble with the following query. It basically works, but I have a case where it is returning one row from mc_WorkoutDetails twice!
Here's the original query:
ALTER PROCEDURE [dbo].[mc_Workouts_GetActivities]
#WorkoutID bigint
AS
BEGIN
SET NOCOUNT ON
SELECT d.ID, a.Description,
CASE WHEN Reps = 0 THEN NULL ELSE Reps END AS Reps,
CASE WHEN Sets = 0 THEN NULL ELSE Sets END AS Sets,
CASE WHEN Minutes = 0 THEN NULL ELSE Minutes END AS Minutes,
d.Comments, c.Name AS Category, a.CategoryID,
(CASE WHEN v.ActivityID IS NULL THEN 0 ELSE 1 END) AS HasVideo,
a.ID AS ActivityID
FROM mc_WorkoutDetails d
INNER JOIN mc_Activities a ON d.ActivityID = a.ID
INNER JOIN mc_Activities_Categories c ON a.CategoryID = c.ID
LEFT OUTER JOIN mc_TrainerVideos v ON a.ID = v.ActivityID
WHERE (d.WorkoutID = #WorkoutID)
ORDER BY SortOrder, a.Description
RETURN ##ERROR
END
Then I tried changing:
INNER JOIN mc_Activities a ON d.ActivityID = a.ID
INNER JOIN mc_Activities_Categories c ON a.CategoryID = c.ID
To:
LEFT OUTER JOIN mc_Activities a ON d.ActivityID = a.ID
LEFT OUTER JOIN mc_Activities_Categories c ON a.CategoryID = c.ID
But that didn't seem to help. I still get the duplicate row.
Can anyone see what's happening?
What you can do is add a join back to the same table using a group to weed out the duplicate rows.
So add this to your join after FROM mc_WorkoutDetails d:
inner join (select [columns you want to select], max(id) id
from mc_WorkoutDetails
group by [columns you want to select] ) q on q.id = d.id
Let me know if that makes sense. Basically you are doing a distinct and getting the max id so you eliminate one of the rows in the join. You have to remember that even if you want there to be duplicates, they will be eliminated even if they are suppose to be there.
The full alter would be:
ALTER PROCEDURE [dbo].[mc_Workouts_GetActivities]
#WorkoutID bigint
AS
BEGIN
SET NOCOUNT ON
SELECT d.ID, a.Description,
CASE WHEN Reps = 0 THEN NULL ELSE Reps END AS Reps,
CASE WHEN Sets = 0 THEN NULL ELSE Sets END AS Sets,
CASE WHEN Minutes = 0 THEN NULL ELSE Minutes END AS Minutes,
d.Comments, c.Name AS Category, a.CategoryID,
(CASE WHEN v.ActivityID IS NULL THEN 0 ELSE 1 END) AS HasVideo,
a.ID AS ActivityID
FROM mc_WorkoutDetails d
inner join (select Reps, Sets, Comments, Minutes, max(id) id
from mc_WorkoutDetails
group by Reps, Sets, Comments, Minutes ) q on q.id = d.id
INNER JOIN mc_Activities a ON d.ActivityID = a.ID
INNER JOIN mc_Activities_Categories c ON a.CategoryID = c.ID
LEFT OUTER JOIN mc_TrainerVideos v ON a.ID = v.ActivityID
WHERE (d.WorkoutID = #WorkoutID)
ORDER BY SortOrder, a.Description
RETURN ##ERROR
END
Thanks for everyone's input. The general suggestions here were correct: I had two rows in the mc_workoutDetails table that referenced the same row in the mc_Activities table.
While the foreign key was part of a unique primary key, it was a compound key and so this column could contain duplicates as long as the other column in the key were different.

Conditional Inner Join

I want to be able to inner join two tables based on the result of an expression.
What I've been trying so far:
INNER JOIN CASE WHEN RegT.Type = 1 THEN TimeRegistration ELSE DrivingRegistration AS RReg
ON
RReg.RegistreringsId = R.Id
RegT is a join I made just before this join:
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
This SQL-script does not work.
So all in all, if the Type is 1, then it should join on the table TimeRegistration else it should join on DrivingRegistration.
Solution:
In my select statement I performed the following joins:
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
LEFT OUTER JOIN TimeRegistration AS TReg ON TReg.RegistreringsId = R.Id AND RegT.Type = 1
LEFT OUTER JOIN DrivingRegistration AS DReg ON DReg.RegistreringsId = R.Id AND RegT.Type <>1
Then I edited my where-clause to output the correct, depending on the RegType, like this:
WHERE (CASE RegT.Type WHEN 1 THEN TReg.RegistreringsId ELSE DReg.RegistreringsId END = R.Id)
Try putting both tables in the query using LEFT JOIN's
LEFT JOIN TimeRegistration TR ON r.rid = TR.Id AND RegT.type =1
LEFT JOIN DrivingRegistration DR ON r.rid = DR.Id AND RegT.type <>1
Now, in you select clause, use
CASE RegType.Type WHEN 1 THEN TR.SomeField ELSE DR.someField END as SomeField
The other option is to use dynamic SQL
You probably need to perform two left joins, one onto TimeRegistration and one onto DrivingRegistration, and return the fields you want from the appropriate join table something like this:
LEFT JOIN TimeRegistration ON TimeRegistration.RegistreringsId = R.Id
LEFT JOIN DrivingRegistration ON DrivingRegistration.RegistreringsId = R.Id
and you select statement would be something like this:
SELECT CASE WHEN RegT.Type = 1 THEN TimeRegistration.Foo ELSE DrivingRegistration.Bar END
I like what you're trying to do, but I don't think SQL is that clever.
SELECT
R.foo, tr.bar
FROM
SomeTable AS R
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
AND RegT1.Type = 1
INNER JOIN TimeRegistration AS tr ON /* whatever */
UNION
SELECT
R.foo, dr.bar
FROM
SomeTable AS R
INNER JOIN RegistrationTypes AS RegT ON R.RegistrationTypeId = RegT.Id
AND RegT1.Type = 2
INNER JOIN DrivingRegistration AS dr ON /* whatever */
So I had a scenario where there were three email columns in one table (don't ask why) and any of them could be null (or empty). In this example code I will just deal with a case where it is null.
I had to join it to another table by any of the emails to retrieve the users firstname.
Here is what worked
select
m.email1,
m.email2,
m.email3,
m2.firstName
from MyTable m
left join MyOtherTable m2 on m2.Email =
case when m.email1 is null then
case when m.email2 is null then
case when m.email3 null then
'nonexistent#mydomain.com' -- i stopped here
else m.email3 end
else wm.email2 end
else m.email1 end
Obviously you would include further conditions like
case when m.email1 is null or m.email1 = '' then ...
To cover for empty values.