INNER JOIN + JOIN returning duplicating result - sql-server

I have a SQL Server query that it trying to connect 3 tables. I am getting the results I need, but have a duplication issue.
table1 is the main record table. table2 is basically a user table. table3 is a lookup table that connects users to managers.
I need to connect one user to all the records connected to himself and any other users connected to his manager (or the people he manages, if a manager). All managers are users, and any user can 'own' a record on table1.
table3 connects userIDs on table2 to the userID of the manager also on table2.
table3 (lookup table)
id | altID
15 | 205
16 | 205
17 | 205
18 | 246
table2 (user table)
id | other_col
15 | abc
16 | def
17 | ggg
18 | hhh
205| yyy
246| zzz
table1 (record table)
id | record_data
15 | abc
16 | def
17 | ggg
18 | hhh
205| yyy
246| zzz
The user will put in their ID. So if id = 'XYZ'.
SELECT c.id, c.col, s.col2, s.col3
FROM table1 AS c
INNER JOIN table2 AS s
ON c.id = s.id
JOIN table3 AS p
ON c.id = p.id OR c.id = p.altID
WHERE
(p.id = 'XYZ'
OR p.altID = 'XYZ')
AND
(c.id = p.altID
OR c.id = p.id)
AND
NULLIF(LTRIM(RTRIM(c.column)), '') IS NOT null
This is giving me all the associated records (all the users that XYZ is related to). It is, however, including XYZ twice, and I cannot figure out why.
This is resolvable using DISTINCT(c.id) in the SELECT statement, but I am trying to understand why the duplication happens to begin with.

Here’s what I get when I work through the logic in the query:
The join from table1 (c) to table2 (s) is one-to one, as (s) contains a single row with the “name” for the Id in (c). No duplciation problems there.
You then join table1 (c) to table 3 (p), from the “owing” id in (c) to all rows in (p) for that id, for both user and manager columns. Thus, if the item in (c) is owned by a manager, you will get one or more hits, once for them as a user and once for each time they are a manager.
Leastways, that’s what I figure based solely on the code and the descriptions you provided.

If you want to show the relation USER - MANAGER defined in table3 one posible way is to join the lookup table (table3) again with the user table (to get the manager record).
SELECT c.id, s.other_col, p.altid, manager.other_col manager_name
FROM table1 c
JOIN table2 s
ON c.id = s.id
LEFT OUTER JOIN table3 p
ON c.id = p.id
LEFT OUTER JOIN table2 manager
ON p.altid = manager.id;
ID OTHER_COL ALTID MANAGER_NAME
---------- --------- ---------- ------------
17 ggg 205 yyy
16 def 205 yyy
15 abc 205 yyy
18 hhh 246 zzz
246 zzz
205 yyy
I'm using OUTER JOIN, that will show also users withou manager. INNER JOIN will suppress those records.

Related

Sqlserver CTE how to get first non null value

I am trying to use the following query to get a user with his detail and any of his manager or manager's manager id who got a record in UserTag table.
I have got the partial result, but not sure how to get the first non null value. I have show my expected result below.
With Managers AS
(
--initialization
SELECT Id, Email, ManagerID, NULL as UserID
FROM dbo.[User]
WHERE email='user#test.com'
UNION ALL
--recursive execution
SELECT u.Id, u.Email, u.ManagerID, tb2.UserID
FROM dbo.[User] u
INNER JOIN Managers m ON m.ManagerID = u.ID
OUTER apply (select userid from UserTag where userid = m.ManagerID and (TagID=9 or TagName = 'test')) tb2
)
SELECT * FROM Managers
Expected result:
Sample data and expected result:
User table
=========
Id Email ManagerID
--- ----- ---------
11813 userA#test.com 1251
1251 userB#test.com 302
302 userC#test.com 1221
1221 userD#test.com 358
358 userE#test.com 988
988 userE#test.com NULL
100 userA1#test.com 101
101 userB1#test.com 102
102 userC1#test.com 103
103 userD1#test.com 104
104 userF1#test.com NULL
User Tag
Id UserId TagId TagName
1 1221 9 test
2 104 9 test
Expected result
==============
UserId Email TagManagerId
------ ----- ------------
11813 userA#test.com 1221
1251 userB#test.com 1221
302 userC#test.com 1221
Brief description:
I have all users in User table. This is a self referencing table.
I want to get all users whose immediate manager or manager's manager or any manager in his/hierarchy got a record in UserTag table with tagid=9 or tagname='test' as shown here.
Schema db<>fiddle here
Based on your Brief description, I believe that this is an XY Problem
There are solutions to the real need that don't include get first non null value.
What I believe you really need to do is find all the managers that are tagid=9 or tagname='test' and then recursively find all their subordinates. (Run the recursion in the opposite direction to your example query.)
The only complexity I can think of is that such a manager may have a subordinate that also meets those criteria, so I included a condition to avoid recursing them more than once.
WITH
test9_managed_users AS
(
SELECT
u.*,
u.managerid AS closest_test9_manager_id,
0 AS depth
FROM
[user] AS u
INNER JOIN
UserTag AS t
ON t.userid = u.managerid
AND (t.TagID=9 or TagName = 'test')
UNION ALL
SELECT
u.*,
m.closest_test9_manager_id,
m.depth+1
FROM
test9_managed_users AS m
INNER JOIN
[user] AS u
ON u.managerid = m.id
WHERE
-- If a user is themselves a 'test9_manager', don't recurse their children
-- > They would already have been included in the Anchor part of the CTE
NOT EXISTS (
SELECT *
FROM UserTag AS t
WHERE t.userid = m.id
AND (t.TagID=9 or TagName = 'test')
)
)
SELECT
*
FROM
test9_managed_users
ORDER BY
email
Demo with my own test data:
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=5e901a6f7db97362bb27ebacb1d4331c
Demo with your test data:
https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=1ab3625ba797d8fc13c49872d1cd5872

Select records when 2 column's data will match

I have two tables as shown below:
-----------------------
|EmpNo|Complaint |
-----------------------
|9091 |Change required|
|9092 |No change |
|9093 |Changes done |
-----------------------
Above table contains employee number and his complaints.
I have one another table which contains employee all kind of details as shown below.
-------------------------------
|EmpNo|EmailID |EmpBossNO|
-------------------------------
|9091 |abc#gmail.com|9092 |
|9092 |xyz#gmail.com|9093 |
|9093 |mno#gmail.com|9099 |
-------------------------------
Here, if Empno:9091 will raise any complain then a mail will send to his boss that the complain is raise by your employee and you have to accept it so I want to get EmailID of employee's boss and for that I want one SQL query. I tried the query shown here, but it doesn't work.
select EmpEmailID
from tblComplaint
inner join tblEmpMaster on tblEmpMaster.EmpNo = tblComplaint.EmpPSNo
where tblComplaint.EmpPSNo = tblEmpMaster.EmpBossNo
I want output like.. if complaint is raised by EmpNo:9091 then it will return EmailID of his boss which is xyz#gmail.com.
You are on the right track with a join between the tblComplaint and tblEmpMaster tables. But, you need an additional join to tblEmpMaster to bring in the boss' email for each employee complaint.
SELECT
c.EmpNo,
c.Complaint,
COALESCE(e2.EmailID, 'NA') AS boss_email
FROM tblComplaint c
INNER JOIN tblEmpMaster e1
ON c.EmpNo = e1.empNo
LEFT JOIN tblEmpMaster e2
ON e1.EmpBossNO = e2.EmpNo;
Demo
I used a left self join above, in case a given employee does not have a boss (e.g. for the highest ranking boss). In this case, I display NA for the boss email.
You should self join tblEmpMaster
select boss.EmpEmailID
from tblComplaint
inner join tblEmpMaster emp on emp.EmpNo = tblComplaint.EmpPSNo
inner join tblEmpMaster boss on boss.EmpNo = emp.EmpBossNO
where tblComplaint.EmpPSNo = 9091
DB Fiddle
you can even use sub queries to get the Email_Id of the boss as shown below
SELECT Email_Id
FROM EMP_Details
WHERE Emp_No IN (
SELECT Boss_Id
FROM Emp_Details) AND
Emp_No IN (
SELECT Emp_No
FROM Emp_Complaints)

SQL query to get data from two rows connected by key

I have a question that seems very simple to me at first but I'm relatively new to SQL and I can't solve it.
I have two tables: 'Applicants' and 'Family Units'.
Applicants:
ApplicantID | FirstName
----------- | ----------
1 | John
2 | Mary
Family Units:
ApplicantID | UnitID| Note
1 | 10 | Member
2 | 10 | Mother
I need to bring in one table Applicant Name and Mother Name.
Mother applicantID should be determined by having same UnitID in Family Units table and Mother Note in the same table (and other details they are not relevant here).
I tried this query:
That obviously doesn't work correct, I get applicant name instead of applicant's mother's name.
Need you help, links to articles and explanations also will be great because I feel that I'm missing something very basic.
; with cte as
(
select fu.*,a.name from
FamilyUnit fu
join applicant a on a.id = fu.id
where type = 'Mother'
)
select a.id,a.name,c.name
from applicant a
join FamilyUnit fu on fu.id = a.id
join cte c on c.unit = fu.unit
where c.id <> a.id
Im not so sure if this is correct: just try it
select * from
(select * from #family_units where note = 'member') as a
left join
(select * from #family_units where note = 'mother') as b
on a.unitid = b.unitid
left join #applicant_table as c
on a.ApplicantID =c.ApplicantID
Test result:
1 10 Member 2 10 Mother 1 John

EF6 - Generating unneeded nested queries

I have the following tables:
MAIN_TBL:
Col1 | Col2 | Col3
------------------
A | B | C
D | E | F
And:
REF_TBL:
Ref1 | Ref2 | Ref3
------------------
A | G1 | Foo
D | G1 | Bar
Q | G2 | Xyz
I wish to write the following SQL query:
SELECT M.Col1
FROM MAIN_TBL M
LEFT JOIN REF_TBL R
ON R.Ref1 = M.Col1
AND R.Ref2 = 'G1'
WHERE M.Col3 = 'C'
I wrote the following LINQ query:
from main in dbContext.MAIN_TBL
join refr in dbContext.REF_TBL
on "G1" equals refr.Ref2
into refrLookup
from refr in refrLookup.DefaultIfEmpty()
where main.Col1 == refr.Col1
select main.Col1
And the generated SQL was:
SELECT
[MAIN_TBL].[Col1]
FROM (SELECT
[MAIN_TBL].[Col1] AS [Col1],
[MAIN_TBL].[Col2] AS [Col2],
[MAIN_TBL].[Col3] AS [Col3]
FROM [MAIN_TBL]) AS [Extent1]
INNER JOIN (SELECT
[REF_TBL].[Ref1] AS [Ref1],
[REF_TBL].[Ref2] AS [Ref2],
[REF_TBL].[Ref3] AS [Ref3]
FROM [REF_TBL]) AS [Extent2] ON [Extent1].[Col1] = [Extent2].[Ref1]
WHERE ('G1' = [Extent2].[DESCRIPTION]) AND ([Extent2].[Ref1] IS NOT NULL) AND CAST( [Extent1].[Col3] AS VARCHAR) = 'C') ...
Looks like it is nesting a query within another query, while I just want it to pull from the table. What am I doing wrong?
I may be wrong, but it looks like you don't do the same in linq query and sql query, especially on your left joining clause.
I would go for this, if you want something similar to your sql query.
from main in dbContext.MAIN_TBL.Where(x => x.Col3 == "C")
join refr in dbContext.REF_TBL
on new{n = "G1", c = main.Col1} equals new{n = refr.Ref2, c = refr.Col1}
into refrLookup
from r2 in refrLookup.DefaultIfEmpty()
select main.Col1
By the way, it doesn't make much sense to left join on a table which is not present in the select clause : you will just get multiple identical Col1 if there's more than one related item in the left joined table...

SELECT DISTINCT showing duplicate dates per customer email

I am trying to retrieve information for the past ten months, but am having a couple of errors. First, my query is getting data from as far back as 2013. Secondly, I am seeing duplicates in my results based on the PolEffDate field, like this:
EntityID | PolEffDate | EMail | CustNo | Producer | BusinessPhone
abcde-12345-fghij-67890 | 2013-09-24 | somewhere#email.com | 31000 | Bob Builder | 123-456-7890
abcde-12345-fghij-67890 | 2013-12-01 | somewhere#email.com | 31000 | Bob Builder | 123-456-7890
abcde-12345-fghij-67890 | 2014-09-24 | somewhere#email.com | 31000 | Bob Builder | 123-456-7890
Here is my SQL Query:
SELECT DISTINCT
CONVERT(VarChar(36), Customer.CustId) AS EntityID
, BasicPolInfo.PolEffDate, Customer.EMail, Customer.CustNo
, (isnull(Employee.Firstname + ' ','') + isnull(Employee.LastName,''))
AS Producer, Employee.BusFullPhone
FROM
Customer INNER JOIN BasicPolInfo ON Customer.CustId = BasicPolInfo.CustId INNER JOIN
Transaction ON BasicPolInfo.PolId = Transaction.PolId INNER JOIN
GeneralBranch ON Customer.GLBrnchCode = GeneralBranch.GLBrnchCode INNER JOIN
GeneralDepartment ON Customer.GLDeptCode = GeneralDepartment.GLDeptCode INNER JOIN
GeneralDivision ON Customer.GLDivCode = GeneralDivision.GLDivCode INNER JOIN
Employee ON BasicPolInfo.ExecCode = Employee.EmpCode
WHERE
BasicPolInfo.PolExpDate >= DATEADD(MONTH, -10,CONVERT(VARCHAR(11),GETDATE(),106))
AND BasicPolInfo.PolExpDate <= CONVERT(VARCHAR(11),GETDATE(),106)
AND Customer.Active = 'Y'
AND Customer.typeCust = 'P'
Thank you for the help. I will try my best to answer any questions.
Daniel, the duplication you are seeing is caused because you have multiple records in BasicPolInfo for each CustID value. You can confirm this by running the following query:
SELECT CustID, COUNT(*)
FROM BasicPolInfo
GROUP BY CustID
HAVING COUNT(*) > 1
Depending on your schema, this may not be an issue - after all, there is probably a perfectly legitimate reason for that! Multiple policies per Customer is my guess.
To resolve the duplication, I would recommend a GROUP BY with MIN() or MAX().
Your other issue, that of retrieving data from earlier dates, is because you are selecting the PolEffDate (presumably, policy effective date), but filtering the PolExpDate (presumably, policy expiration date). Which are you intending to use? Policies that have finished sometime in the last ten months could have started much earlier than that.
To resolve the wider date range, reference the same value in your SELECT and WHERE clauses.
Query below (using MAX() and PolExpDate):
SELECT
CONVERT(VarChar(36), Customer.CustId) AS EntityID,
MAX(BasicPolInfo.PolExpDate) AS PolExpDate, -- note that this is now PolExpDate
Customer.EMail,
Customer.CustNo,
(isnull(Employee.Firstname + ' ','') + isnull(Employee.LastName,'')) AS Producer,
Employee.BusFullPhone
FROM
Customer INNER JOIN
BasicPolInfo ON Customer.CustId = BasicPolInfo.CustId INNER JOIN
[Transaction] ON BasicPolInfo.PolId = [Transaction].PolId INNER JOIN
GeneralBranch ON Customer.GLBrnchCode = GeneralBranch.GLBrnchCode INNER JOIN
GeneralDepartment ON Customer.GLDeptCode = GeneralDepartment.GLDeptCode INNER JOIN
GeneralDivision ON Customer.GLDivCode = GeneralDivision.GLDivCode INNER JOIN
Employee ON BasicPolInfo.ExecCode = Employee.EmpCode
WHERE
BasicPolInfo.PolExpDate >= DATEADD(MONTH, -10,CONVERT(VARCHAR(11),GETDATE(),106))
AND BasicPolInfo.PolExpDate <= CONVERT(VARCHAR(11),GETDATE(),106)
AND Customer.Active = 'Y'
AND Customer.typeCust = 'P'
GROUP BY
CONVERT(VarChar(36), Customer.CustId),
Customer.EMail,
Customer.CustNo,
(isnull(Employee.Firstname + ' ','') + isnull(Employee.LastName,'')),
Employee.BusFullPhone

Resources