Conditional inner joins based on row values MSSQL - sql

I have a MSSQL table that is used for messages. I have clients, workers, and users, each with their own respective tables. For this messaging component, the message can be sent to a client worker or user. To differentiate between which type the the sender/receiver is, I have a column in the message table that is a tiny-int, 1 for users, 2 for clients, 3 for workers.
So, when i am attempting to receive the messages, what is the ideal way to retrieve the name of the sender from the corresponding table(clients, users, or workers) without doing multiple queries.
I know I can first do a query to check the type column, then do another query to get the name but I would rather not do this for performance issues.

You could create a generic view which simply lists the relevant columns from each plus the message type and then join to that from your messages table.
CREATE VIEW ExampleView
AS
begin
SELECT
firstname,
lastname,
1 AS MessageType
FROM
dbo.Users
union ALL
SELECT
firstname,
lastname,
2 AS MessageType
FROM
dbo.Clients
union ALL
SELECT
firstname,
lastname,
3 AS MessageType
FROM
dbo.Workers
END
Query:
SELECT
a.MessageText,
isnull(b.Firstname,'')+ISNULL(b.lastname,'') AS Name
FROM Messaging a
INNER JOIN ExampleView b ON a.MessageType = b.MessageType
If space isn't a massive issue you could store the message type directly on the Users,Clients and Workers table and just join to that column... Better performance but not really as nice database design.

This is a rather large guess at your table structure based on your description. The performance will be down to indexes you have.
Select m.*,
COALESCE(c.ClientName, w.WorkerName, u.UserName) as Name
FROM Messages m
LEFT JOIN Clients c on c.ClientId = m.UserId AND m.type = 2
LEFT JOIN Workers w on w.WorkerId = m.UserId AND m.type = 3
LEFT JOIN Users u on u.UserId = m.UserId AND m.type = 1
Where (c.ClientId is not null
OR
w.WorkerId is not null
OR
u.UserId is not null)

Related

SQL Sets (intersect, Union, And Not)

I have what appears to be a simple query, but is alluding my boolean challenged mind (not enough java (the liquid kind) today).
Three tables:
Users = (UserID, Username, Enabled, LoggedIn, SessionID, Email, SettingsTableVersion, FullName, Initials, UserData, InitialStatusID)
Groups = (GroupID, Groupname, Description, AutoAdd)
GroupMembers = (GroupID, UserID, ProjectID, IsMember)
I have a bunch of users and a dozen or so groups. I have a World group that has every User in it. I have a Terminated Users Group that has just 4 users in it.
What I want is a query that looks at World (everyone is in it) and takes out the Terminated User group users names. This yields me all active users! Blimey if this isn't causing me to pull my hair out. I would surmise its essentially World minus the intersection of World and Terminated Users. No luck thus far. SQL Server 2012.
TIA
A nested query?
Select *
from users
where userid not in ( select userid
from groupmembers
where groupid=[terminated])
Or is that too slow?
If I can assume
The groupnames are 'world' and 'terminatedusers'
duplicates do not exist in group members (groupID and userID are a Unique index)
you really mean World is ALL users an a terminated user coudnl't exist w/o being in the world group.
.
SELECT U.userID
FROM USERS U
INNER JOIN GROUPMEMBERS GM
on U.UserID = GM.UserID
INNER JOIN GROUPS G
on G.GroupID = GM.GroupID
WHERE GroupName in ('World', 'TerminatedUsers')
GROUP BY U.UserID
HAVING count(GM.GroupID) = 1
This basically find all the groups each user is in for the two groups. then only return those who are in just 1 group.
You could join to two defined sets as well:
SELECT U.userID
FROM Users U
INNER JOIN GroupMembers GMWorld
on U.userID = GMWorld.userID
and GMWorld.GroupID = [ID for world]
LEFT JOIN GroupMembers GMTerminated
on U.userID = GMTerminated.UserID
and GMTerminated.GroupID = [ID for terminated]
WHERE GMTerminated.userID is null
If you don't want to use the ID's you could left join twice to Group for GMterminated and GMWorld to use the names.
The 2nd query basically joins users to a worldset and a terminated set we keep all users in world except those having a record in the terminated set.
This one did the trick! Issue was I had one user that was deleted (flips the Enabled bit) and then re-added so they appeared in the list until I added the Enabled = 1. Thanks xQbert... again! Awesome stuff.
SELECt fullname
FROM Users U
LEFT JOIN GroupMembers GMWorld
on U.userID = GMWorld.userID
and GMWorld.GroupID = 3
LEFT JOIN GroupMembers GMTerminated
on U.userID = GMTerminated.UserID
and GMTerminated.GroupID = 14
WHERE GMTerminated.userID is null and Enabled = 1

Is it true that JOINS can be used everywhere to replace Subqueries in SQL

I heard people saying that table joins can be used everywhere to replace sub-queries. I tested it in my query, but found that appropriate data set was only retrieved when I used sub-queries. I was not able to get same data set using joins. I am not sure if what I found is right because I am a newcomer in RDBMS, thus not so much experienced. I will try to draw the schema (in words) of the database in which I was experimenting:
The database has two tables:
Users (ID, Name, City) and Friendship (ID, Friend_ID)
Goal: Users table is designed to store simple user data and Friendship table represents Friendship between users. Friendship table has both the columns as foreign keys, referencing to Users.ID. Tables have many-to-many relationship between them.
Question: I have to retrieve Users.ID and Users.Name of all the Users, which are not friends with a particular user x, but are from same city (much like fb's friend suggestion system).
By using subquery, I am able to achieve this. Query looks like:
SELECT ID, NAME
FROM USERS AS U
WHERE U.ID NOT IN (SELECT FRIENDS_ID
FROM FRIENDSHIP,
USERS
WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x)
AND U.ID != x AND CITY LIKE '% A_CITY%';
Example entries:
Users
Id = 1 Name = Jon City = Mumbai
Id=2 Name=Doe City=Mumbai
Id=3 Name=Arun City=Mumbai
Id=4 Name=Prakash City=Delhi
Friendship
Id= 1 Friends_Id = 2
Id = 2 Friends_Id=1
Id = 2 Friends_Id = 3
Id = 3 Friends_Id = 2
Can I get the same data set in a single query by performing joins. How? Please let me know if my question is not clear. Thanks.
Note: I used inner join in the sub-query by specifying both tables: Friendship, Users. Omitting the Users table and using the U from outside, gives an error (But if not using alias for the table Users, query becomes syntactically okay but result from this query includes ID's and names of users, who have more than one friends, including the user having ID x. Interesting, but is not the topic of the question).
For not in you can use left join and check for is null:
select u.id, u.name
from Users u
left join Friends f on u.id = f.id and f.friend_id = #person
where u.city like '%city%' and f.friend_id is null and u.id <> #person;
There are some cases where you can't work out your way with just inner/left/right joins, but your case is not one of them.
Please check sql fiddle: http://sqlfiddle.com/#!9/1c5b1/14
Also about your note: What you tried to do can be achieved with lateral join or cross apply depending on the engine you are using.
You can rewrite your query using only joins. The trick is to join to the User tables once with an inner join to identify users within the same city and reference the Friendship table with a left join and a null check to identify non-friends.
SELECT
U1.ID,
U1.Name
FROM
USERS U1
INNER JOIN
USERS U2
ON
U1.CITY = U2.CITY
LEFT JOIN
FRIENDSHIP F
ON
U2.ID = F.ID AND
U1.ID = F.FRIEND_ID
WHERE
U2.id = X AND
U1.ID <> U2.id AND
F.id IS NULL
The above query doesn't handle the situation where USER x's primary key is in the FRIEND_ID column of the FRIENDSHIP table. I assume because your subquery version doesn't handle that situation, perhaps you create 2 rows for each friendship, or friendships are not bi-directional.
Joins and subqueries can be used to achieve similar results in some cases, but certainly not all. As an example, this query with a subquery could not be achieve vis-a-vis a join:
SELECT ID, COLUMN1, COUNT(*) FROM MYTABLE
WHERE ID IN (
SELECT DISTINCT ID FROM MYTABLE
WHERE COLUMN2 NOT IN (VALUES1, VALUES2)
)
GROUP BY ID;
This is only one example, but there are many.
Conversely, you cannot get information from another table by using a subquery without joining it.
As to your example
SELECT ID, NAME FROM USERS AS U
WHERE U.ID NOT IN (
SELECT FRIENDS_ID FROM FRIENDSHIP, USERS
WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x)
AND U.ID != x AND CITY LIKE '% A_CITY%';
This could be constructed as:
select ID, NAME from users u
join FRIENDSHIP f on f.ID = u.ID
where u.ID = x
and u.ID != y
and CITY like '%A_CITY';
I changed your second x to a y assumptively, so it wouldn't cause confusion.
Of course, you may also want to LEFT JOIN aka LEFT OUTER JOIN if there is a chance that there may be multiple results in the FRIENDSHIP table.

SQL Conditional Join?

I have a table called LOGS with the following columns:
id - user - object - ref - field - from - to - join - ts
to store logging of a PHP app I am writing. However, is it possible in the SQL query when I return all the data into PHP to do a 'conditional' join? For instance the join column might contain 'people' indicating that the field column needs to be joined in relationship with the table people.
Is this possible? Or shall I have to do it on the PHP side?
A LEFT join should do the trick here
select *
from
LOGS l
left join
PEOPLE p on p.peopleid = l.field and l.join = 'people'
I'm not sure I've used the correct relationship fields between LOGS and PEOPLE but by including a clause of join where log type is people then you can see PEOPLE entries are conditionally returned.
Things become more complicated when you want to conditionally return from different tables, because you need to make sure the extra fields, brought in by the entity table are the same (or at least are identified as the same). In which case your forced to UNION results.
select
l.*,
p.peopleid as entityid,
p.fullname as displayname
from
LOGS l
left join
PEOPLE p on p.peopleid = l.field and l.join = 'people'
union all
select
l.*,
a.accountid as entityid,
p.accountname as displayname
from
LOGS l
left join
ACCOUNT a on a.accountid = l.field and l.join = 'account'
or perhaps this
select
l.*,
entity.entityid as entityid,
entity.displayname as displayname
from
LOGS l
left join
(
select 'people' as type, p.peopleid as entityid, p.fullname as displayname
from PEOPLE
union all
select 'account', a.accountid, p.accountname
from ACCOUNT
) entity on entity.type = l.join and entity.entityid = l.field
But I can imagine combining lots of your entity tables like this to return logs could make for a really slow query.

SQL Query trouble

I am trying to write an SQL query to retrieve all of a users pending events, however it is difficult with how my tables are structured.
My tables are as follows :
event {
event_id
name
group_id}
Pending {
GroupID
UserID
}
Users{
Username
UserID
}
Ever user is identified by a UserID, and every group by a GroupID. Events have in them a GroupID which points to a list of users. I need to retrieve all pending events for a certain user, so :
SELECT * FROM event
WHERE event.group_id = (SELECT GroupID FROM Pending)
But how do I then link this so only the Pending events for a user with a certain UserID are returned?
select e.* from event e
inner join pending p on
e.group_id = p.GroupID
inner join Users u
on p.UserID = u.UserID
where u.UserID = 123
actually you can skip the join with the Users-table if you already have the UserID:
select e.* from event e
inner join pending p on
e.group_id = p.GroupID
where p.UserID = 123
The typical way to write this is using inner join. Traditionally it has been better performing than sub selects, but modern DBMS:s optimize them into the same query. If you really want to write with a sub select you type like this
SELECT * FROM event
WHERE event.group_id in (SELECT GroupID FROM Pending WHERE UserID = 123)
You want to use the JOIN clause to link the tables together rather than using the WHERE clause to filter
SELECT u.username,
e.name
FROM users u
INNER JOIN pending p
ON u.userid = p.userid
INNER JOIN event e
ON p.groupid = e.groupid
WHERE
u.UserID = SomeID
SELECT
event.event_id
FROM event
LEFT JOIN Pending ON event.group_id = Pending.GroupID
LEFT JOIN Users USING (UserID)
WHERE Users.Username = 'foobar'
USING() can be utilized when columns on both sides of selection have the same name. It makes for easier reading. That is one of main reason why i would recommend to have same name for same data throughout the database. For example, if you Documents table and primary key document_id, then in all the other tables, where you are referencing the ID of a document, you use the same name for the column.
To learn more about JOINs : in mysql or postgresql read the links. And for visual representation of what each join does: this article.
Also you should get some book about your preferred RDBMS and learn all the basics, then you can expand your knowledge by reading SQL Antipatterns book. Or you could just carefully look through slides, made by book's author.
You can try :
select e.*, u.* from pending as p, users as u, event as e
WHERE
p.group_id = e.group_id
AND p.user_id = u.userID
SELECT * FROM event E,
pending P,
Users U
WHERE E.group_id = P.GroupID
AND P.UserID = U.UserID
AND U.UserID = (Some XYZ id)
XYZ should be some Integer value.

How to combine results from a JOIN with another result set?

I have 2 tables, CLIENTS and MEMBERS. We are trying to get the member's address from the CLIENTS table and all the records from the MEMBERS table where the ID from MEMBERS matches the ID from CLIENTS.
At the same time, we would also like to show the rest of the member's info from the MEMBERS table where the ID is either NULL or doesn't exist in the CLIENTS table.
I thought the following query would do it, but it isn't getting all the records from MEMBERS tables:
SELECT c.ID
,m.ID
,m.fname
,m.lname
,m.address
,m.city
,m.state
,m.zip
from Client c
inner join members m on c.id = m.id
UNION ALL
select '',m.* from members m
where m.id IS NULL or m.id NOT IN (select ca.id from clients ca)
What am I doing wrong?
Unless I am missing something it appears that you just need an OUTER JOIN
SELECT COALESCE(c.ID, '') AS ID,
m.ID,
m.fname,
m.lname,
m.address,
m.city,
m.state,
m.zip
FROM members m
LEFT OUTER JOIN Client c
ON c.id = m.id
With respect to the issue in your question do you have any NULL values for id in the Clients table? That is often the cause of unexpected NOT IN behaviour.