Complex SQL Join help!
(For a quick recap please see sections titled , DATA. Can only get results in FIGURE A, and FIGURE B, what I want is DESIRED RESULTS, tried everything posted in the answers section.)
All, I have been working hard to start smartly using joins in my application. Boy it took me a while but I think I am getting the hang of this, but I can't get this query to work.
Please help me! Here is the join as is:
SELECT TOP (100) PERCENT a.Id, a.DateCreated, a.DateModified, a.LastUpdatedBy, a.AccomplishmentTypeId, a.Title, a.Description, a.ApplicableDate,
a.LocalId, at.Name AS AccomplishmentTypeName, a.Name AS AreaName, u.FirstName, u.LastName, ug.Name AS UserGroupName,
ugo.Name AS OtherUserGroupName
FROM dbo.Accomplishment AS a INNER JOIN
dbo.AccomplishmentType AS at ON at.Id = a.AccomplishmentTypeId INNER JOIN
dbo.AccomplishmentArea AS aal ON aal.AccomplishmentId = a.Id INNER JOIN
dbo.Area AS al ON al.Id = aal.AreaId INNER JOIN
dbo.UserAccomplishment AS ua ON ua.AccomplishmentId = a.Id INNER JOIN
dbo.[User] AS u ON u.Id = ua.UserId INNER JOIN
dbo.UserUserGroup AS uug ON uug.UserId = u.Id INNER JOIN
dbo.UserGroup AS ug ON ug.Id = uug.UserGroupId INNER JOIN
dbo.UserGroupType AS ugt ON ugt.Id = ug.UserGroupTypeId INNER JOIN
dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id LEFT OUTER JOIN
dbo.UserGroup AS ugo ON ugo.Id = uugo.UserGroupId LEFT OUTER JOIN
dbo.UserGroupType AS ugto ON ugto.Id = ugo.UserGroupTypeId
WHERE (ug.LocalId = 2) AND (ugo.LocalId <> 2) AND (ugto.LocalId = 4)
ORDER BY a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
Now this is kind of complex, but what I am doing is select info from all these related tables where there is an accomplishment, which has a user associated with it where that user has a usergroup localId = 2, but then if they do have that, I also want to return all the usergroups that user is in where the usertype.localid = 4.
Let me give some example data to hopefully make this clear, right now I have 3 accomplishments in the database that look like this:
-------(DATA)
Accomplishment 1:
User: John
Usergroups: Group A (Of UserGroupType 2), Group B (Of UserGroupType 3),
Group C (Of UserGroupType 4), Group D (Of UsergroupType 4)
Accomplishment 2:
User: John
Usergroups: Group A (Of UserGroupType 2), Group B (Of UserGroupType 3),
Group C (Of UserGroupType 4), Group D (Of UsergroupType 4)
Accomplishment 3:
User: Sue
Usergroups: Group A (Of UserGroupType 2)
Now my above Join results 4 rows :
-------(FIGURE A):
Accomplishment 1, John, Group A, Group C
Accomplishment 1, John, Group A, Group D
Accomplishment 2, John, Group A, Group C
Accomplishment 2, John, Group A, Group D
Now this is correct insofar as there is a row for each userGroup the user has that has a usergrouptype.localid of 4, however, how can I make it so that it also displays Accomplishment 3?? I first started with all the joins being inner joins, and then thought making the last few left joins would take care of it to return the user even if it has no usergroups of usergrouptype localid 4, but it doesn't. Basically I want it to return any accomplishments of a user who is in group A, and if they have any usergroups that are of usergrouptype 4, return all those usergroups as well.
Any thoughts? And if I am not being clear please let me know, this is super complex and I may not have explained enough.
EDIT:
(For a quick recap please see sections titled , DATA. Can only get results in FIGURE A, and FIGURE B, what I want is DESIRED RESULTS, tried everything posted in the answers section.)
Both HLGEM and Tom H.s results are the same, and closer to what I need, but still off. Now I get 9 results in Figure B.
-------(FIGURE B):
Accomplishment 1, John, Group A, Group C
Accomplishment 1, John, Group A, Group D
Accomplishment 1, John, Group A, Null
Accomplishment 1, John, Group A, Group B (Group B is UsergroupType 3)
Accomplishment 3, Sue, Group A, Null
Accomplishment 2, John, Group A, Group C
Accomplishment 2, John, Group A, Group D
Accomplishment 2, John, Group A, Null
Accomplishment 2, John, Group A, Group B (Group B is UsergroupType 3)
So for some reason it's including a null row for John even though he has usergroups that are of usergrouptype 4, and its including a row with group B which is of usergrouptype 3 and shouldn't be showing up.
EDIT AGAIN:
(For a quick recap please see sections titled , DATA. Can only get results in FIGURE A, and FIGURE B, what I want is DESIRED RESULTS, tried everything posted in the answers section.)
Yep I am truely stumped by this, I have tried pretty much every combination of putting some stuff in the where clause and it either is 4 rows without accomplishment 3, or 9 rows where user John gets two extra rows per accomplishment, or 6 Rows without accomplish 3 and an extra row for usergrouptype 3 usergroup. What's even more baffling is that adding the ugto.localid = 4
to the last line doesn't seem to have any affect on the results. It's showing my a usergroup which has usergrouptype 3 and I don't see anywhere in the query where that is okay.
EDIT 5/02/10
(For a quick recap please see sections titled , DATA. Can only get results in FIGURE A, and FIGURE B, what I want is DESIRED RESULTS, tried everything posted in the answers section.)
I think what I want my be impossible with how it's set up?? Anyway the result I want is these 5 rows:
-------(DESIRED RESULT):
Accomplishment 1, John, Group A, Group C
Accomplishment 1, John, Group A, Group D
Accomplishment 2, John, Group A, Group C
Accomplishment 2, John, Group A, Group D
Accomplishment 3, Sue, Group A, Null
Here is a list of everything I have tried and the results they give: (Please forgive the crappy SSMS formatting)
Everything below is based on this base query:
Starting Query/Attempt 1:
SELECT TOP (100) PERCENT a.Id, a.DateCreated, a.DateModified, a.LastUpdatedBy, a.AccomplishmentTypeId, a.Title, a.Description, a.ApplicableDate,
a.LocalId, at.Name AS AccomplishmentTypeName, al.Name AS AreaName, u.FirstName, u.LastName, ug.Name AS UserGroupName,
ugo.Name AS OtherUsergroupName
FROM dbo.Accomplishment AS a INNER JOIN
dbo.AccomplishmentType AS at ON at.Id = a.AccomplishmentTypeId INNER JOIN
dbo.AccomplishmentArea AS aal ON aal.AccomplishmentId = a.Id INNER JOIN
dbo.Area AS al ON al.Id = aal.AreaId INNER JOIN
dbo.UserAccomplishment AS ua ON ua.AccomplishmentId = a.Id INNER JOIN
dbo.[User] AS u ON u.Id = ua.UserId INNER JOIN
dbo.UserUserGroup AS uug ON uug.UserId = u.Id INNER JOIN
dbo.UserGroup AS ug ON ug.Id = uug.UserGroupId INNER JOIN
dbo.UserGroupType AS ugt ON ugt.Id = ug.UserGroupTypeId INNER JOIN
dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id LEFT OUTER JOIN
dbo.UserGroup AS ugo ON ugo.Id = uugo.UserGroupId AND ugo.LocalId <> 2 LEFT OUTER JOIN
dbo.UserGroupType AS ugto ON ugto.Id = ugo.UserGroupT开发者_开发知识库ypeId AND ugto.LocalId = 4
WHERE (ug.LocalId = 2)
ORDER BY a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
Result: Figure B (See way above) Why it's bad: Nulls for John show up and usergroup of type 3 is showing up.
Attempt 2:
Attempt 1, minus all in-line join filtering, (still keeping left joins on the last two) and the following where statement:
WHERE (ug.LocalId = 2)
AND (ugo.LocalId <> 2 OR ugo.LocalId is null)
AND (ugto.LocalId = 4 OR ugto.LocalId is null)
Result: Figure A (See way above) Why it's bad: Doesn't include Accomplishment 3
Attempt 3:
Same as attempt 1 with changes starting underneath this line:
dbo.[User] AS u ON u.Id = ua.UserId INNER JOIN
Changes under that line:
dbo.UserUserGroup AS uug ON uug.UserId = u.Id INNER JOIN
dbo.UserGroup AS ug ON ug.Id = uug.UserGroupId AND ug.LocalId = 2 INNER JOIN
dbo.UserGroupType AS ugt ON ugt.Id = ug.UserGroupTypeId INNER JOIN
dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id LEFT OUTER JOIN
dbo.UserGroup AS ugo ON ugo.Id = uugo.UserGroupId AND ugo.LocalId <> 2 LEFT OUTER JOIN
dbo.UserGroupType AS ugto ON ugto.Id = ugo.UserGroupTypeId
WHERE (ugto.LocalId = 4)
ORDER BY a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
(For a quick recap please see sections titled , DATA. Can only get results in FIGURE A, and FIGURE B, what I want is DESIRED RESULTS, tried everything posted in the answers section.)
@ChaosPandion's comment about reformatting is a good idea, but perhaps you need some help understanding what good formatting is. Well, it's probably different for everyone :-) but if I was writing your query I'd format it as follows:
SELECT TOP (100) PERCENT
a.Id,
a.DateCreated,
a.DateModified,
a.LastUpdatedBy,
a.AccomplishmentTypeId,
a.Title,
a.Description,
a.ApplicableDate,
a.LocalId,
at.Name AS AccomplishmentTypeName,
a.Name AS AreaName,
u.FirstName,
u.LastName,
ug.Name AS UserGroupName,
ugo.Name AS OtherUserGroupName
FROM dbo.Accomplishment AS a
INNER JOIN dbo.AccomplishmentType AS at
ON at.Id = a.AccomplishmentTypeId
INNER JOIN dbo.AccomplishmentArea AS aal
ON aal.AccomplishmentId = a.Id
INNER JOIN dbo.Area AS al
ON al.Id = aal.AreaId
INNER JOIN dbo.UserAccomplishment AS ua
ON ua.AccomplishmentId = a.Id
INNER JOIN dbo.[User] AS u
ON u.Id = ua.UserId
INNER JOIN dbo.UserUserGroup AS uug
ON uug.UserId = u.Id
INNER JOIN dbo.UserGroup AS ug
ON ug.Id = uug.UserGroupId
INNER JOIN dbo.UserGroupType AS ugt
ON ugt.Id = ug.UserGroupTypeId
INNER JOIN dbo.UserUserGroup AS uugo
ON uugo.UserId = u.Id
LEFT OUTER JOIN dbo.UserGroup AS ugo
ON ugo.Id = uugo.UserGroupId
LEFT OUTER JOIN dbo.UserGroupType AS ugto
ON ugto.Id = ugo.UserGroupTypeId
WHERE ug.LocalId = 2 AND
ugo.LocalId <> 2 AND
ugto.LocalId = 4
ORDER BY a.DateCreated DESC,
u.LastName,
u.FirstName,
u.Id,
UserGroupName
I think this formatting makes it easier to read.
Share and enjoy.
You have made a classic mistake when learning left joins, you put conditions in the where clause that turn them into inner joins
Try this
SELECT a.Id, a.DateCreated, a.DateModified, a.LastUpdatedBy, a.AccomplishmentTypeId, a.Title,
a.Description, a.ApplicableDate, a.LocalId, at.Name AS AccomplishmentTypeName,
a.Name AS AreaName, u.FirstName, u.LastName, ug.Name AS UserGroupName, ugo.Name AS OtherUserGroupName
FROM dbo.Accomplishment AS a
INNER JOIN dbo.AccomplishmentType AS at ON at.Id = a.AccomplishmentTypeId
INNER JOIN dbo.AccomplishmentArea AS aal ON aal.AccomplishmentId = a.Id
INNER JOIN dbo.Area AS al ON al.Id = aal.AreaId
INNER JOIN dbo.UserAccomplishment AS ua ON ua.AccomplishmentId = a.Id
INNER JOIN dbo.[User] AS u ON u.Id = ua.UserId
INNER JOIN dbo.UserUserGroup AS uug ON uug.UserId = u.Id
INNER JOIN dbo.UserGroup AS ug ON ug.Id = uug.UserGroupId
INNER JOIN dbo.UserGroupType AS ugt ON ugt.Id = ug.UserGroupTypeId
INNER JOIN dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id
LEFT OUTER JOIN dbo.UserGroup AS ugo ON ugo.Id = uugo.UserGroupId AND ugo.LocalId <> 2
LEFT OUTER JOIN dbo.UserGroupType AS ugto ON ugto.Id = ugo.UserGroupTypeId AND ugto.LocalId = 4
WHERE ug.LocalId = 2
ORDER BY a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
see this link for an explanation of this: http://wiki.lessthandot.com/index.php/WHERE_conditions_on_a_LEFT_JOIN
I'm not sure that I completely understand what you're trying to do, but I think that you need to move your criteria into the LEFT OUTER JOINs. If you put it in the WHERE clause then if no match is found by the LEFT OUTER JOIN, those columns will appear as NULL and so they will fail the WHERE clause check. You are in effect turning the LEFT OUTER JOIN into an INNER JOIN.
FROM
dbo.Accomplishment AS a
INNER JOIN dbo.AccomplishmentType AS at ON
at.Id = a.AccomplishmentTypeId
INNER JOIN dbo.AccomplishmentArea AS aal ON
aal.AccomplishmentId = a.Id
INNER JOIN dbo.Area AS al ON
al.Id = aal.AreaId
INNER JOIN dbo.UserAccomplishment AS ua ON
ua.AccomplishmentId = a.Id
INNER JOIN dbo.[User] AS u ON
u.Id = ua.UserId
INNER JOIN dbo.UserUserGroup AS uug ON
uug.UserId = u.Id
INNER JOIN dbo.UserGroup AS ug ON
ug.Id = uug.UserGroupId AND
ug.localid = 2
INNER JOIN dbo.UserGroupType AS ugt ON
ugt.Id = ug.UserGroupTypeId
INNER JOIN dbo.UserUserGroup AS uugo ON
uugo.UserId = u.Id
LEFT OUTER JOIN dbo.UserGroup AS ugo ON
ugo.Id = uugo.UserGroupId AND
ugo.localid <> 2
LEFT OUTER JOIN dbo.UserGroupType AS ugto ON
ugto.Id = ugo.UserGroupTypeId
WHERE
ugto.localid = 4
ORDER BY
a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
Here's what I re-wrote so far:
SELECT a.Id, a.DateCreated, a.DateModified, a.LastUpdatedBy, a.AccomplishmentTypeId,
a.Title, a.Description, a.ApplicableDate, a.LocalId,
at.Name AS AccomplishmentTypeName, a.Name AS AreaName, u.FirstName,
u.LastName, ug.Name AS UserGroupName, ugo.Name AS OtherUserGroupName
FROM dbo.Accomplishment a
JOIN dbo.AccomplishmentType AS at ON at.Id = a.AccomplishmentTypeId
JOIN dbo.AccomplishmentArea AS aal ON aal.AccomplishmentId = a.Id
JOIN dbo.Area AS al ON al.Id = aal.AreaId
JOIN dbo.UserAccomplishment AS ua ON ua.AccomplishmentId = a.Id
JOIN dbo.[User] AS u ON u.Id = ua.UserId
JOIN dbo.UserUserGroup AS uug ON uug.UserId = u.Id
JOIN dbo.UserGroupType AS ugt ON ugt.Id = ug.UserGroupTypeId
JOIN dbo.UserGroup AS ug ON ug.Id = uug.UserGroupId
JOIN dbo.UserGroupType AS ugto ON ugto.Id = ug.UserGroupTypeId
ORDER BY a.DateCreated DESC, u.LastName, u.FirstName, u.Id, UserGroupName
You've got a redundant join to UserUserGroup
, and your joins to UserGroup
can be consolidated because the LEFT JOIN criteria would cancel out what's done in the previous join to the same table.
You JOINs are all fine. It's the filtering!
If you write a filter that does not allow nulls in left joined tables, then your filter will remove the additional records that the left joins allow.
ugo and ugto are both reached by LEFT JOIN
WHERE (ug.LocalId = 2)
AND (ugo.LocalId <> 2 OR ugo.LocalId is null)
AND (ugto.LocalId = 4 OR ugto.LocalId is null)
PS - mixing JOIN and FILTERING criteria (both in ON or both in WHERE) is a recipe for insanity. Stay sane!
Edit: found an error in the join:
INNER JOIN dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id
Should be
LEFT JOIN dbo.UserUserGroup AS uugo ON uugo.UserId = u.Id
精彩评论