Need to get multiple value from a table in the left join - sql

I have a table having data as below.
Say I have two versions of the project and I need to migrate data from older version to a new version.
Let's say tblFolders in version1
+----+------------+--------------+--------------+
| id | FolderName | CreatedBy | ModifiedBy |
+----+------------+--------------+--------------+
| 1 | SIMPLE | 5 | 6 |
| 2 | SIMPLE1 | 8 | 1 |
+----+------------+--------------+--------------+
And another table having userid of both versions.
Let's say its tblUsersMapping
+----+----------------+-------------------+
| id | Version1UserID | Version2UserID |
+----+----------------+-------------------+
| 1 | 1 | 500 |
| 2 | 2 | 465 |
| 3 | 3 | 12 |
| 4 | 4 | 85 |
| 5 | 5 | 321 |
| 6 | 6 | 21 |
| 7 | 7 | 44 |
| 8 | 8 | 884 |
+----+----------------+-------------------+
Now I need to transfer data from version 1 to version 2. When I transferring data, CreatedBy and Modifiedby ids should by of the new version.
So though I have data as below
| 1 | SIMPLE | 5 | 6 |
It should be transferred as below
| 1 | SIMPLE | 321 | 21 |
For that, I have added a join so far between these two tables as below.
SELECT id,
foldername,
B.version2userid AS CreatedBy
FROM tblfolders A WITH(nolock)
LEFT JOIN tblusersmapping B WITH(nolock)
ON A.createdby = B.version1userid
This would give me a proper result for column CreatedBy.
But how can I get userid from tblUsersMapping for ModifiedBy column?
Doing below will not work and will give NULL for both the columns.
SELECT id,
foldername,
b.version2userid AS createdby,
b.version2userid AS modifiedby
FROM tblfolders A WITH(nolock)
LEFT JOIN tblusersmapping B WITH(nolock)
ON a.createdby = b.version1userid,
a.modifiedby = b.version1userid
One way is I can add another join with tblusersmapping table. But its not a good idea because tables can have a huge data and another join will affect the performance of the query.
My question is how can I get Version1UserID and Version2UserID from mapping table based on createdby and modifiedby columns?

You can use multiple select which may help you.
SELECT id,
foldername,
(SELECT version2userid from tblUsersMapping where Version1UserID=tblfolders.CreatedBy) AS CreatedBy,
(SELECT version2userid from tblUsersMapping where Version1UserID=tblfolders.ModifiedBy) AS ModifiedBy
FROM tblfolders

If you want to populate both the column where each column joins to to a different row, in that case you have to join the same table twice like following. You can't get it with a single table join the way you are expecting.
SELECT id,
foldername,
B.version2userid AS CreatedBy
C.Version2UserID AS ModifiedBy
FROM tblfolders A WITH(nolock)
LEFT JOIN tblusersmapping B WITH(nolock)
ON A.createdby = B.version1userid
LEFT JOIN tblusersmapping C WITH(nolock)
ON A.ModifiedBy = C.version1userid

Try this, it will work across all sample data,
select tf.id,tf.FolderName
,oa.Version2UserID as CreatedBy
,oa1.Version2UserID as ModifiedBy
from #tblFolders tf
outer apply(select top 1 Version2UserID
from #tblUsersMapping tu
where tu.Version1UserID= tf.CreatedBy order by id desc)oa
outer apply(select top 1 Version2UserID
from #tblUsersMapping tu
where tu.Version1UserID= tf.ModifiedBy order by id desc)oa1

You can use UDF to return modifiedby and INNER JOIN instead of LEFT JOIN (if requirement meets) as below. I think it will help in the preformance
CREATE TABLE tblFolders (id INT, folderName VARCHAR(20), createdBy INT, modifiedBy INT)
INSERT INTO tblFolders VALUES
(1,'SIMPLE', 5,6),
(2,'SIMPLE1', 8,1)
CREATE TABLE tblUsersMapping(id INT, Version1UserID INT, Version2UserID INT)
INSERT INTO tblUsersMapping VALUES
(1,1,500),
(2,2,465),
(3,3,12),
(4,4,85),
(5,5,321),
(6,6,21),
(7,7,44),
(8,8,884)
SELECT a.id,
a.foldername,
b.version2userid AS createdby,
dbo.FNAReturnModifiedBy(a.modifiedBy) AS modifiedby
FROM tblfolders A WITH(nolock)
INNER JOIN tblusersmapping B WITH(nolock) ON a.createdby = b.version1userid
--Function
IF OBJECT_ID(N'dbo.FNAReturnModifiedBy', N'FN') IS NOT NULL
DROP FUNCTION dbo.FNAReturnModifiedBy
GO
CREATE FUNCTION dbo.FNAReturnModifiedBy(#updated_by INT)
RETURNS INT AS
BEGIN
DECLARE #updateUserID INT
SELECT #updateUserID = Version2UserID
FROM tblusersmapping WHERE Version1UserID = #updated_by
RETURN #updateUserID
END
OUTPUT:
id foldername createdby modifiedby
1 SIMPLE 321 21
2 SIMPLE1 884 500

Note :
I did not know about how to find the query performance. I wrote only for your expected output.
I am using SQL Server 2012.
I did not use more than one Join.
Query did JOIN, GROUP BY, ROW_NUMBER (), CASE instead of two LEFT JOIN
Input :
create table ##ver (id int, FolderName varchar (10), CreatedBy int, ModifiedBy int)
insert into ##ver values
(1,'SIMPLE',5,6)
,(2,'SIMPLE1',8,1)
,(3,'File',7, 5)
select * from ##ver
create table ##veruser (id int, Version1UserID int, Version2UserID int)
insert into ##veruser values
(1 , 1 , 500)
,(2 , 2 , 465)
,(3 , 3 , 12 )
,(4 , 4 , 85 )
,(5 , 5 , 321)
,(6 , 6 , 21 )
,(7 , 7 , 44 )
,(8 , 8 , 884)
select * from ##veruser
Query :
select
id, FolderName
, max (case when rn = 1 then Version2UserID end) Version1UserID
, max (case when rn = 2 then Version2UserID end) Version2UserID
from (
select
v.id, v.FolderName, u.Version1UserID, u.Version2UserID
, ROW_NUMBER () over
(partition by v.id order by v.id, v.CreatedBy,
case
when v.CreatedBy > v.ModifiedBy then u.Version1UserID
end desc
) rn
, v.CreatedBy, v.ModifiedBy
from ##ver v
join ##veruser u
on u.Version1UserID in (v.CreatedBy, v.ModifiedBy)
) a
group by id, FolderName
order by id
Update 1:
Query does :
Join the tables.
Row numbering, over (),
Partition by Id.
Order by File id (v.id), Creator id ascending, If creator id greater
than modified id, then creator id descending. (Due to second step this reordering is must)
Depends on 'rn' values, rows are transfer to columns
(You can find many examples at here)
Output :
id FolderName Version1UserID Version2UserID
1 SIMPLE 321 21
2 SIMPLE1 884 500
3 File 44 321

Try this one.
Select a.id,folderName,b.Version2UserId as createdby,c.Version2UserId as modifiedby
from tblFolders as a WITH(nolock)
inner join tblUsersMapping as b WITH(nolock) on a.createdby =b .Version1UserID
inner join tblUsersMapping as c WITH(nolock) on a.modifiedBy =c .Version1UserID

Related

query select equaled data on three columns of table with four columns and ignore one

I have table have four columns like (right ,left ,up ,down) I want to build query that show equaled data on three of the four columns;
example:
| id | right | left | up | down |
|:---|:------:|:-----:|:------:| -----:|
| 1 | street |hospital|coffee |building|
| 2 | house |hospital|coffee |building|
| 3 | road | bus |coffee |sign |
| 4 | house |hospital|coffee |sign |
| 5 | car |road |coffee |sign |
the result should be like:
id
right
left
up
down
1
street
hospital
coffee
building
2
house
hospital
coffee
building
id number 3 and 5 not included because every column should equal it's self
is that query correct
select t.* from test_table t where
(t.right,t.left,t.up) in (select t.right,t.left,t.up from test_table t group by t.right,t.left,t.up having count(*)>1)
or (t.right,t.left,t.down) in (select t.right,t.left,t.down from test_table t group by t.right,t.left,t.down having count(*)>1)
or (t.right,t.up,t.down) in (select t.right,t.up,t.down from test_table t group by t.right,t.up,t.down having count(*)>1)
or (t.left,t.up,t.down) in (select t.left,t.up,t.down from test_table t group by t.left,t.up,t.down having count(*)>1)
and t.mud_id=285 order by t.right,t.left,t.up,t.down ;
if it correct it's go in loop without result for more than 10m waiting
if not
please what is the correct query to get the result
Since there are some assumptions to be made on how how to order and/or organize the results, you may use the query below to identify each matching pair.
DECLARE #T TABLE(ID INT, [right] VARCHAR(20), [left] varchar(20), up
varchar(20), down varchar(20))
INSERT INTO #T VALUES
(1,'street','hospital','coffee','building'),
(2,'house','hospital','coffee','building'),
(3,'road','bus','coffee','sign'),
(4,'house','hospital','coffee','sign'),
(5,'car','road','coffee','sign'),
(6,'street','road','coffee','sign')
SELECT
T1.ID AS MatchID1,
T2.ID AS MatchID2
FROM
#T T1
INNER JOIN #T T2 ON T1.ID <> T2.ID
GROUP BY
T1.ID, T2.ID
HAVING
MAX(CASE WHEN T1.[right] = T2.[right] THEN 1 ELSE 0 END +
CASE WHEN T1.[left] = T2.[left] THEN 1 ELSE 0 END +
CASE WHEN T1.up = T2.up THEN 1 ELSE 0 END +
CASE WHEN T1.down = T2.down THEN 1 ELSE 0 END) = 3
MatchID1 MatchID2
2 1
1 2
4 2
2 4
6 5
5 6
A trick to bring back matches by first occurrence using:
INNER JOIN #T T2 ON T1.ID > T2.ID
MatchID1 MatchID2
1 2
2 4
5 6

SQL Query : how to Select Maximum value of each group of joined tables

I have this problem of returning maximum AGE of players in these 2 tables I have, Table tblplayers (with 34 records) when this table is joined to another table called tblClubs (with 9 records).
tblPlayers fields are:
ID(Autonumber) | CLubID(Number) | Player Name(Text) | PlayerAge(Number)
tblClubs fields are:
ID(Autonumber) | ClubName (Text)
Now I need to show Names of players with maximum ages among other players in their own clubs and the club name beside that like this :
Club Name | Player Name | Maximum Age (older player of each club)
please tell me how can i make it?
You can solve this with window functions, if your database supports them:
select c.club_name, p.player_name, p.player_age
from clubs c
inner join (
select
p.*,
rank() over(partition by p.club_id order by p.player_age desc) rn
players p
) p on p.club_id = c.id and p.rn = 1
A common and quite portable alternative is to filter with a subquery:
select
c.club_name,
t.player_name,
t.player_age
from players p
inner join clubs c on c.id = p.club_id
where p.age = (select max(p1.age) from players p1 where p1.club_id = p.club_id)
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE tblPlayers (ID INT, ClubID INT,PlayerName VARCHAR(255)
,PlayerAge INT)
CREATE TABLE tblClubs (ID int,ClubName VARCHAR(255))
INSERT INTO tblPlayers(ID,ClubID,PlayerName
,PlayerAge) VALUES (1,1,'John',30)
,(2,1,'Mark',25)
,(3,1,'Albert',36)
,(4,2,'David',33)
,(5,2,'John',31)
INSERT INTO tblClubs(ID, ClubName) VALUES(1,'TEAM 1')
,(2,'TEAM 2')
Query 1:
SELECT
*
FROM
(SELECT A.*,RANK() OVER
(PARTITION BY A.ClubID ORDER BY A.PlayerAge desc) as rn
FROM tblPlayers A
INNER JOIN tblClubs B ON A.ClubID=B.ID) t
WHERE
t.rn = 1
Results:
| ID | ClubID | PlayerName | PlayerAge | rn |
|----|--------|------------|-----------|----|
| 3 | 1 | Albert | 36 | 1 |
| 4 | 2 | David | 33 | 1 |

Join tables with distinct highest ranked row

I have three tables defined like this:
[tbMember]
memberID | memberName
1 | John
2 | Peter
[tbGroup]
groupID | groupName
1 | Alpha
2 | Beta
3 | Gamma
[tbMemberGroupRelation]
memberID | groupID | memberRank (larger number is higher)
1 | 1 | 0
1 | 2 | 1
2 | 1 | 5
2 | 2 | 3
2 | 3 | 1
And now I want to perform a table-join selection to get result which contains (distinct) member with his highest ranked group in each row, for the given example above, the query result is desired to be:
memberID | memberName | groupName | memberRank
1 | John | Beta | 1
2 | Peter | Alpha | 5
Is there a way to implement it in a single SQL like following style ?
select * from tbMember m
left join tbMemberGroupRelation mg on (m.MemberID = mg.MemberID and ......)
left join tbGroup g on (mg.GroupID = g.GroupID)
Any other solutions are also appreciated if it is impossible to write in a simple query.
========= UPDATED =========
Only ONE highest rank is allowed in table
One solution would be to create an inverted sequence/rank of the memberRank so that the highest rank per member is always equal to 1.
This is how I achieved it using a sub-query:
SELECT
m.memberID,
m.memberName,
g.groupName,
mg.memberRank
FROM
tbMember m
LEFT JOIN
(
SELECT
memberID,
groupID,
groupName,
memberRank,
RANK() OVER(PARTITION BY memberID ORDER BY memberRank DESC) AS invRank
FROM
tbMemberGroupRelation
) mg
ON (mg.memberID = m.memberID)
AND (mg.invRank = 1)
LEFT JOIN
tbGroup g
ON (g.groupID = mg.groupID);
An alternative method:
SELECT
M.memberID,
M.memberName,
G.groupName,
MG.memberRank
FROM
Member M
LEFT OUTER JOIN MemberGroup MG ON MG.memberID = M.memberID
LEFT OUTER JOIN MemberGroup MG2 ON
MG2.memberID = M.memberID AND
MG2.memberRank > MG.memberRank
INNER JOIN [Group] G ON G.groupid = MG.groupid
WHERE
MG2.memberid IS NULL
Might perform better in some situations due to indexing, etc.
create table [tbGroup] (groupid int, groupname varchar(8000))
Insert [tbGroup] Values (1, 'Alpha')
Insert [tbGroup] Values (2, 'Beta')
Insert [tbGroup] Values (3, 'Gamma')
create table [tbMemberGroupRelation] (memberid int, groupid int, memberrank int)
Insert [tbMemberGroupRelation] Values (1,1,0)
Insert [tbMemberGroupRelation] Values (1,2,1)
Insert [tbMemberGroupRelation] Values (2,1,5)
Insert [tbMemberGroupRelation] Values (2,2,3)
Insert [tbMemberGroupRelation] Values (2,3,1)
;With cteMemberGroupRelation As
(
Select *, Row_Number() Over (Partition By MemberID Order By MemberRank Desc) SortOrder
From [tbMemberGroupRelation]
)
Select *
From tbMember M
Join (Select * From cteMemberGroupRelation Where SortOrder = 1) R On R.memberid = M.memberid
Join tbGroup G On G.groupid = R.groupid

Retrieve data from one table and insert into another table

I am trying to retrieve data from one table and then insert it into another table.
This is a a sample of the first table in which there is the following data. tb1 is the table which consists of data. The two columns Manager and TeamLeader basically means for example : Josh is managed by Vik and so on. An employee can also be a manager to another employer. For example, Josh is the manager of Nirvan and Deva.
+---------+-------------+
| tbl1 |
+---------+-------------+
| Manager | Employee |
+---------+-------------+
| Vik | Josh |
+---------+-------------+
| Vik | Cindy |
+---------+-------------+
| Vik | Alvin |
+---------+-------------+
| Vik | Kim |
+---------+-------------+
| Josh | Nirvan |
+---------+-------------+
| Josh | Deva |
+---------+-------------+
| Cindy | Mervyn |
+---------+-------------+
| Nirvan | Reeta |
+---------+-------------+
| Nirvan | Zaki |
+---------+-------------+
| Nirvan | Sunny |
+---------+-------------+
What i want is to insert all these records in another table with the following columns : Id(which is set to IDENTITY/AUTONUM), Name(name of employee/manager), ParentId(of the manager which a particular employee has to report to).
So for example,
I should be getting something of the sort :
ID Name ParentId
1 Vik 0
2 Josh 1
3 Cindy 1
4 Alvin 1
5 Kim 1
6 Nirvan 2
7 Deva 2
8 Mervyn 3
9 Reeta 6
10 Zaki 6
11 Sunny 6
I am having difficulty to get the right sql to retrieve this data from the first table and insert it into another table.
You have to do it in two steps. One to create all your ID fields. Then you can join with this table to match up the employee to find the manager and the manager's ID:
insert into MyNewTable (Name, ParentID)
select Manager, ParentID
from tbl1
union
select Employee, ParentID
from tbl1
update MyNewTable
set MyNewTable.ParentId = Managers.Id
from MyNewTable
join tbl1
on tbl1.Employee = MyNewTable.Name
join MyNewTable Managers
on MyNewTable.Name = Managers.Manager
INSERT INTO tbl2 (
Name
,parentId
)
SELECT DISTINCT manager
,0
FROM tbl1
WHERE manager NOT IN (
SELECT employee
FROM tbl1
)
INSERT INTO tbl2
SELECT DISTINCT employee
,0
FROM tbl1
UPDATE tbl2
SET parentid = parent.id
FROM tbl2
INNER JOIN tbl1 ON tbl2.Name = tbl1.employee
INNER JOIN tbl2 parent ON parent.Name= tbl1.manager
create table #tbl1 (manager char(15), employee char(15))
create table #tbl2 (ID int identity(1,1), Name char(15), ParentID int)
insert into #tbl1
values ('Vik', 'Josh') ,
('Vik' , 'Cindy') ,
('Vik' , 'Alvin') ,
('Vik' , 'Kim') ,
('Josh' , 'Nirvan'),
('Josh' , 'Deva') ,
('Cindy' ,'Mervyn') ,
('Nirvan' , 'Reeta'),
('Nirvan' , 'Zaki'),
('Nirvan' , 'Sunny')
--- big Boss
insert into #tbl2(Name, ParentID)
SELECT DISTINCT manager, 0
FROM #tbl1
WHERE manager NOT IN ( SELECT employee FROM #tbl1 )
--- Grab all Employees (Name)
insert into #tbl2(Name)
select distinct employee from #tbl1
--- Update ParentID
declare #StructureLevl int = 3 -- 0 1 2 3 Do something to figure out this number first
declare #Var_int int = 0
while (#Var_int < #StructureLevl)
begin
update #tbl2
set ParentID= #Var_int +1
where Name in (select A.employee
from #tbl1 A inner join #tbl2 B on A.manager = B.Name
where B.ParentID = #Var_int)
set #Var_int +=1
end
select * from #tbl2
order by ParentID

Rotate columns to rows for joined tables

I have two tables similar to shown below (just leaving out fields for simplicity).
Table lead :
id | fname | lname | email
---------------------------------------------
1 | John | Doe | jd#test.com
2 | Mike | Johnson | mj#test.com
Table leadcustom :
id | leadid | name | value
-------------------------------------------------
1 | 1 | utm_medium | cpc
2 | 1 | utm_term | fall
3 | 1 | subject | business
4 | 2 | utm_medium | display
5 | 2 | utm_term | summer
6 | 2 | month | may
7 | 2 | color | red
I have a database that captures leads for a wide variety of forms that often have many different form fields. The first table gets the basic info that I know is on each form. The second table captures all other forms fields that were sent over so it can really contain a lot of different fields.
What I am trying to do is to do a join where I can grab all fields from lead table along with utm_medium and utm_term from leadcustom table. I don't need any additional fields even if they were sent over.
Desired results :
id | fname | lname | email | utm_medium | utm_term
---------------------------------------------------------------------------
1 | John | Doe | jd#test.com | cpc | fall
2 | Mike | Johnson | mj#test.com | display | summer
Only way I know I could do this is to grab all lead data and then for each record make more calls to get leadcustom data I am looking for but I know there has to me a more efficient way of getting this data.
I appreciate any help with this and it is not something I can change the way I capture that data and table formats.
If your columns are fixed, you can do this with group by + case + max like this:
select
fname,
lname,
email,
max(case when name = 'utm_medium' then value end) as utm_medium,
max(case when name = 'utm_term' then value end) as utm_term
from
lead l
join leadcustom c
on l.id = c.leadid
group by
fname,
lname,
email
The case will assign value from the leadcustom table when it matches the given name, otherwise it will return null, and max will pick take the assigned value if it exists over the null.
You can test this in SQL Fiddle
The other way to do this is to use pivot operator, but that syntax is slightly more complex -- or at least this is more easy for me.
Unless I interpret your question incorrectly - in which case I'm happy to be corrected - you could achieve your goal with a simple left join where you join on ID of the first table:
select ld.*, ldcust.utm_medium, ldcust.utm_term
from lead ld
left join leadcustom ldcust
on ld.id = ldcust.leadid
You can use a cte or a derived table to solve this:
cte:
;with cte as
(
select leadid, [name], [value]
from leadcustom
where name in('utm_medium', 'display')
)
select id, fname, lname, email, [name], [value]
from lead
inner join cte on(id = leadid)
Derived table:
select id, fname, lname, email, [name], [value]
from lead
inner join
(
select leadid, [name], [value]
from leadcustom
where name in('utm_medium', 'display')
) derived on(id = leadid)
and since suslov used JamesZ's fiddle, I will use it too...
declare #t table (Id int,fname varchar(10),lname varchar(10),email varchar(20))
insert into #t(Id,fname,lname,email)values (1,'john','doe','jd#test.com'),(2,'mike','johnson','mj#test.com')
declare #tt table (id int,leadid int,name varchar(10),value varchar(10))
insert into #tt(id,leadid,name,value)values
(1,1,'utm_medium','cpc'),
(2,1,'utm_term','fall'),
(3,1,'subject','business'),
(4,2,'utm_medium','display'),
(5,2,'utm_term','summer'),
(6,2,'month','may'),(7,2,'color','red')
select Id,fname,lname,
email,
[utm_medium],
[utm_term]
from (
select t.Id,
t.fname,
t.lname,
t.email,
tt.name,
tt.value
from #t t JOIN #tt tt
ON t.Id = tt.leadid)R
PIVOT(MAX(value) for name IN([utm_medium],[utm_term]))P
You can try with pivot and join:
select [id]
, [fname]
, [lname]
, [email]
, [utm_medium]
, [utm_term]
from ( select t2.*
, t1.[name]
, t1.[value]
from [leadcustom] t1
join [lead] t2 on t2.[id] = t1.[leadid]
) t
pivot (
max([value])
for [name] in ([utm_medium], [utm_term])
) pt
pivot rotates the joined table-valued expression, by turning the unique values from [value] column in the expression into [utm_medium] and [utm_term] columns in the output, and performs fake aggregation with max function (it works so because a corresponding column can have multiple values for one unique pivoted column, in this case, [name] for [value]).
SQLFiddle