Efficient way to join multiple columns to the same column? - SQL - sql

I have a bunch of tables combined together, which each have a column containing some form of user_id. This leads to 12 user_id columns in total.
I want to join each of these user_id columns with a user_id column in a mapping table in order to retrieve the username for each of these user ids.
So (assuming I have 5 user id columns),
Input:
My Combined Tables Result:
t1.user_id t2.user_id t3.user_id t4.user_id t5.user_id
1 2 3 4 5
Mapping Table:
user_id username
1 A
2 B
3 C
4 D
5 E
Output:
t1.username t2.username t3.username t4.username t5.usernamne
A B C D E
My code looks something like:
SELECT m1.username, m2.username, m3.username, m4.username, m5.username
FROM {join logic for 5 tables here}
JOIN mapping m1
ON t1.user_id = m1.user_id
JOIN mapping m2
ON t2.user_id = m2.user_id
JOIN mapping m3
ON t3.user_id = m3.user_id
JOIN mapping m4
ON t4.user_id = m4.user_id
JOIN mapping m5
ON t5.user_id = m5.user_id
I'm realizing this is extremely inefficient, especially for 12 columns which would mean 12 JOINs. Is there a better or faster way to do this? Thanks!

You might find it easier to use a correlated subquery for each username, especially where you have to implement many columns as it's easier to cut n paste!
Something like:
select
(select Username from Mapping m where m.UserId = t.UserId1) Username1,
(select Username from Mapping m where m.UserId = t.UserId2) Username2,
(select Username from Mapping m where m.UserId = t.UserId3) Username3 etc
from InputTable t

Related

Bad performance when joining two sets based on a

To better illustrate my problem picture the following data set that has Rooms that contain a "range" of animals. To represent the range, each animal is assigned a sequence number in a separate table. There are different animal types and the sequence is "reset" for each of them.
Table A
RoomId
StartAnimal
EndAnimal
GroupType
1
Monkey
Bee
A
1
Lion
Buffalo
A
2
Ant
Frog
B
Table B
Animal
Sequence
Type
Monkey
1
A
Zebra
2
A
Bee
3
A
Turtle
4
A
Lion
5
A
Buffalo
6
A
Ant
1
B
Frog
2
B
Desired Output
Getting all the animals for each Room based on their Start-End entries, e.g.
RoomId
Animal
1
Monkey
1
Zebra
1
Bee
1
Lion
1
Buffalo
2
Ant
2
Frog
I have been able to get the desired output by first creating a view where the rooms have their start and end sequence numbers, and then Join them with the animal list comparing the ranges.
The problem is that this is performing poorly in my real data set where there are around 10k rooms and around 340k animals. Is there a different (better) way to go about this that I'm not seeing?
Example fiddle I'm working with: https://dbfiddle.uk/RnagCTf0
The query I tried is
WITH fullAnimals AS (
SELECT DISTINCT(RoomId), a.[Animal], ta.[GroupType], a.[sequence] s1, ae.[sequence] s2
FROM [TableA] ta
LEFT JOIN [TableB] a ON a.[Animal] = ta.[StartAnimal] AND a.[Type] = ta.[GroupType]
LEFT JOIN [TableB] ae ON ae.[Animal] = ta.[EndAnimal] AND ae.[Type] = a.[Type]
)
SELECT DISTINCT(r.Id), Name, b.[Animal], b.[Type]
FROM [TableB] b
LEFT JOIN fullAnimals ON (b.[Sequence] >= s1 AND b.[Sequence] <= s2)
INNER JOIN [Rooms] r ON (r.[Id] = fullAnimals.[RoomId]) --this is a third table that has more data from the rooms
WHERE b.[Type] = fullAnimals.[GroupType]
Thanks!
One option, to remove the aggregations, is to use the following joins:
between TableA and TableB, to gather "a.StartAnimal" id
between TableA and TableB, to gather "a.EndAnimal" id
between TableB and the previous two TableBs, to gather only the rows that have b.Sequence between the two values of "a.StartAnimal" id and "b.StartAnimal" id, on the matching "Type".
between Table A and Rooms, to gather room infos
SELECT r.*, b.Animal, b.Type
FROM TableA a
INNER JOIN TableB b1 ON a.StartAnimal = b1.Animal
INNER JOIN TableB b2 ON a.EndAnimal = b2.Animal
INNER JOIN TableB b ON b.Sequence BETWEEN b1.Sequence AND b2.Sequence
AND a.GroupType = b.Type
INNER JOIN Rooms r ON r.Id = a.roomId
Check the updated demo here.

SQL replace values in selection many columns

I have two tables:
Table Users
UserId Name
1 John
2 Alice
3 Tom
4 Charles
....
23120 Bob
and
Table Clients
Id Name1 Name2 Name3 .... Name2345
1 1 3 450 4
2 2 17 33 1
...
15302920 44 231 5 7
I would like to make a SQL query where for
Clients.Id = 1
the results are like:
Id Name1 Name2 Name3 ... Name2345
1 John Tom Bill Charles
My clients table has thousands of columns and I try to find a way to replace the values in all columns of the selected results (except the ID column) without thousands of joins.
I know that I can use a query of the following type (but I would like to avoid thousands of joins):
SELECT a.ID,
b.name name1,
c.name name2,
d.name name3
FROM clients a
INNER JOIN users b
ON a.name1 = b.userID
INNER JOIN users c
ON a.name2 = c.userID
INNER JOIN users d
ON a.name3 = d.userID
WHERE a.ID = 1
In addtion:
I can't change the data/structure of the "Clients" table but I can change the "Users" table as necessary.
I can't duplicate the "Clients" table as well since it very large and changes rapidly.
The "Clients" table has thousands of columns and millions of rows, but the selected part is always a small enough subset of the table.
Is it possible?
There is no simple query to achieve this since SQL is fundamentally flexible in rows (vertically) and rigid in columns. This is the closest I came up with:
select c.id,
(select name from dbo.users where UserId = c.name1),
(select name from dbo.users where UserId = c.name2),
(select name from dbo.users where UserId = c.name3),
(select name from dbo.users where UserId = c.name4)
from clients c

Left Outer Join with one result per match and "priority" of match set by a different field in match SQL Server 2005

I am trying to get a single result (PHONE) per match (CONTACT_ID) in a Left Outer Join. I imagine that there is a way to accomplish this with the preference (or order) being set by another column/field- the phone type (TYPE), but I haven't been able to figure it out. Below is a list of facts to help better explain what I am trying to accomplish and then following is an example Table A and B with the desired result. I've looked at min() and group by, but I don't know how to make those work here. As a side note, after this is working, I will be joining it to more tables to the left of it in a simpler fashion.
The student can have an unlimited number of CONTACT_ID.
A contact does not always have all phone types.
The preferred order of phone types (TYPE) is C,H,W (which, fortunately, happens to be alphabetical)
ignore match and go to the next in priority if PHONE is null
TableA:
STUDENT_ID CONTACT_ID
---------- ----------
X 1
X 2
Y 3
Y 4
TableB:
CONTACT_ID TYPE PHONE
---------- ---- -----
1 H 21
1 C
1 W 44
2 H 78
2 C 92
2 W 11
Desired Result:
STUDENT_ID CONTACT_ID TYPE PHONE
---------- ---------- ---- -----
X 1 H 21
X 2 C 92
Y 3
Y 4
Here is the query that I have that will make a join with all phone matches (minus all of my crazy attempts at getting what I want).
SELECT *
FROM Table TableA T1
LEFT OUTER JOIN TableB T2 ON T1.CONTACT_ID = T2.CONTACT_ID
All help greatly appreciated!
Edited code from Stefan Onofrei's solution:
(results in some duplicate entries)
SELECT
T1.STUDENT_ID,
T1.CONTACT_ID,
T2.PHONE_TYPE,
T3.PHONE
FROM REG_STU_CONTACT T1
INNER JOIN
(SELECT MIN(PHONE_TYPE) AS PHONE_TYPE, CONTACT_ID
FROM REG_CONTACT_PHONE
WHERE PHONE IS NOT NULL
GROUP BY CONTACT_ID) T2 ON T1.CONTACT_ID = T2.CONTACT_ID
INNER JOIN REG_CONTACT_PHONE T3 ON T2.CONTACT_ID = T3.CONTACT_ID AND T2.PHONE_TYPE = T3.PHONE_TYPE
ORDER BY T1.STUDENT_ID
Select A.STUDENT_ID A.CONTACT_ID B.TYPE c.PHONE
from TableA A
inner join
(select MIN(type ) as type, Contact_ID
from Tableb
where phone is not null
group by contactid) B
on A.contactid = b.contactid
inner join Tableb C
on B.contactid = c.conatctid and b.type = c.type

SQL Join to only one record in one to many relationship

I have the following query:
SELECT c.[ClientID]
,u.[AccessId]
FROM [tblClient] c
INNER JOIN [tblUser] u ON u.[Id] = c.[UserId]
This tblUser has multiple ID's for each UserID row.
So it would look like this:
UserID AccessID
1 AD2F0A-965B78414-2B34906F2-0127AA5A
1 ID2F0A9-65B784-142B34906-F20127AA5A
1 UD2F0A9-65B78-4142B34906F-20127AA5A
2 TD2F0A9-65B784142-B34906F-20127AA5A
2 RD2F0A9-65B784142B3-4906-F20127AA5A
3 WD2F0A96-5B784142-B34906F201-27AA5A
3 ZD2F0A96-5B784-142B34-906F2-0127AA5A
3 CD2F0A965-B784142B3-4906F20-127AA5A
Is there a way to only get the top(or 1) AccessId for each UserID? It doesnt matter which AccessID i get, I just want 1 of them.
Thanks
SELECT c.[ClientID], MAX(u.[AccessId])
FROM [tblClient] c
INNER JOIN [tblUser] u
ON u.[Id] = c.[UserId]
GROUP BY c.[ClientID]

SQL - Left join 2 foreign keys to 1 primary key

I have two tables, Games and Teams. What should my sql statement look like to make a list of games that pulls in the TeamName that is linked to the TeamID1 and TeamID2 fields? I believe I could use a left join but I'm not sure what to do with two foreign keys that link to one primary key. Thank you very much for any help you could provide.
Games
GameID
TeamID1
TeamID2
Result
Teams
TeamID
TeamName
I often see folks struggle with the idea of joining a table unto itself or multiple times in the same query (as it were here). Once mastered, it's a great technique to use on tables that have a lot of relationships between rows (such as a list of teams that have to play each other!). As others have pointed out, you need to use two inner joins to accomplish this:
select
*
from
games g
inner join teams t1 on
g.teamid1 = t1.teamid
inner join teams t2 on
g.teamid2 = t2.teamid
So, if your games table looks like this:
GameID TeamID1 TeamID2
----------------------------
1 1 3
2 4 2
3 2 1
You will get the result set of:
g.GameID g.TeamID1 g.TeamID2 t1.TeamID t1.Name t2.TeamID t2.Name
----------------------------------------------------------------------------------
1 1 3 1 Lions 3 Bears
2 4 2 4 Oh My 2 Tigers
3 2 1 2 Tigers 1 Lions
Of course, I would alias these columns in the select statement, if I were me, for usability's sake:
select
g.GameID,
t1.Name as Team1,
t2.Name as Team2
from
...
This way, columns can be named appropriately, instead of having the t1 and t2 columns share the same names.
Now, to address the confusion about what a left join is. You see, a left join will take all of the rows from the first (or left) table, and then match up any rows on the join condition to the second (or right) table. For any rows from the left table, you will get null in all of the columns on the right table.
Delving into an example, let's say that somebody put in a null for TeamID2 on one of the rows for whatever reason. Let's also say that a team of TeamID 4 used to exist, but doesn't any more.
GameID TeamID1 TeamID2
----------------------------
1 1 3
2 4 2
3 1 null
Now, let's take a look at what a left join would be in terms of the query:
select
*
from
games g
left join teams t1 on
g.teamid1 = t1.teamid
left join teams t2 on
g.teamid2 = t2.teamid
Logically, this will grab all of our games, and then match them up to the respective teams. However, if a TeamID doesn't exist, we'll get nulls. It will look like so:
g.GameID g.TeamID1 g.TeamID2 t1.TeamID t1.Name t2.TeamID t2.Name
----------------------------------------------------------------------------------
1 1 3 1 Lions 3 Bears
2 4 2 null null 2 Tigers
3 1 null 1 Lions null null
Therefore, a left join will only be necessary if a team is optional.
In your case, you'll be using an inner join to join a table multiple times. This is a very common practice and is rather useful. It avoids some of the pitfalls of subqueries (especially on MySQL), while allowing you to grab data from the table for intratable comparisons. This is markedly useful when trying to find the order of something, or related rows.
Anyway, I hope this very rambling answer helps out somebody somewhere.
SELECT *
FROM Games G
INNER JOIN Teams T1
ON G.TeamID1 = T1.TeamID
INNER JOIN Teams T2
ON G.TeamID2 = T2.TeamID
I don't think you'll need a left outer join unless one of the two teams in a game is optional. Logically to me it seems that both would be required, so your query would look like this:
SELECT *
FROM Games AS G
INNER JOIN Teams AS T1 ON T1.TeamID = G.TeamID1
INNER JOIN Teams AS T2 ON T2.TeamID = G.TeamID2
If TeamID1 and TeamID2 are NOT NULL, then you want to use an INNER JOIN, otherwise you could use a LEFT JOIN.
You need to use alias:
SELECT GameID, team1.TeamName, team2.TeamName
FROM Games INNER JOIN teams team1 ON (Games.TeamID1 = team1.TeamID)
INNER JOIN teams team2 ON (Games.TeamID2 = team2.TeamID)