views:

88

answers:

4

I've scoured StackOverflow and Google for an answer to this problem.

I'm trying to create a Microsot SQL Server 2008 view. Not a stored procedure. Not a function. Just a query (i.e. a view).

I have three tables. The first table defines a common key, let's say "CompanyID". The other two tables have a sometimes-common field, let's say "EmployeeName".

I want a single table result that, when my WHERE clause says "WHERE CompanyID = 12" looks like this:

CompanyID | TableA    | TableB
12        | John Doe  | John Doe
12        | Betty Sue | NULL
12        | NULL      | Billy Bob

I've tried a FULL OUTER JOIN that looks like this:

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM Company
FULL OUTER JOIN TableA ON Company.CompanyID = TableA.CompanyID
FULL OUTER JOIN TableB ON 
    Company.CompanyID = TableB.CompanyID AND 
    (TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR TableB.EmployeeName = TableA.EmployeeName)

I'm only getting the NULL from one matched table, I'm not getting the expansion for the other table. In the above sample, I'm basically only getting the first and third rows and not the second.

Can someone help me create this query and show me how this is done correctly?

BTW I already have a stored procedure that looks very clean and populates an in-memory table, but that isn't what I want.

Thanks.

-- EDIT:

Here's a full-running sample of what currently doesn't work (it's missing 'someone 2' and 'someone 3'.

DECLARE @Company TABLE
(
    CompanyID int
)

INSERT INTO @Company (CompanyID) VALUES (10)
INSERT INTO @Company (CompanyID) VALUES (12)

DECLARE @TableA TABLE
(
    EmployeeId int,
    CompanyId int,
    EmployeeName varchar(30)
)

DECLARE @TableB TABLE
(
    EmployeeId int,
    CompanyId int,
    EmployeeName varchar(30)
)

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 1, 10, 'someone' )

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
--VALUES ( 2, 12, 'someone 2' )

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 3' )

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 4' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 1, 10, 'someone' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 2, 12, 'someone 2' )

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
--VALUES ( 3, 12, 'someone 3' )

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )
VALUES ( 3, 12, 'someone 4' )

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM @Company Company
FULL OUTER JOIN @TableA TableA ON Company.CompanyID = TableA.CompanyID
FULL OUTER JOIN @TableB TableB ON Company.CompanyID = TableB.CompanyID
WHERE
(
    TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR 
    TableB.EmployeeName = TableA.EmployeeName
)
AND Company.CompanyID = 12

Result:

CompanyID   EmployeeName    EmployeeName
12          someone 4       someone 4

What I want:

CompanyID   EmployeeName    EmployeeName
12          NULL            someone 2
12          someone 3       NULL
12          someone 4       someone 4
+2  A: 

try this:

SELECT Company.CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM Company
LEFT OUTER JOIN TableA ON Company.CompanyID = TableA.CompanyID
LEFT OUTER JOIN TableB ON Company.CompanyID = TableB.CompanyID
WHERE (TableA.EmployeeName IS NULL OR TableB.EmployeeName IS NULL OR TableB.EmployeeName = TableA.EmployeeName)

EDIT after OP gave test data and expected result set

try this (tables and test data from question):

DECLARE @Company TABLE (CompanyID int)
DECLARE @TableA TABLE (EmployeeId int,CompanyId int,EmployeeName varchar(30))
DECLARE @TableB TABLE (EmployeeId int,CompanyId int,EmployeeName varchar(30))

set nocount on
INSERT INTO @Company (CompanyID) VALUES (10)
INSERT INTO @Company (CompanyID) VALUES (12)

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )--VALUES ( 2, 12, 'someone 2' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 1, 10, 'someone' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 3' )
INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 4' )

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )--VALUES ( 3, 12, 'someone 3' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 1, 10, 'someone' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 2, 12, 'someone 2' )
INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )VALUES ( 3, 12, 'someone 4' )
set nocount off

SELECT coalesce(TableA.CompanyID,TableB.CompanyID) CompanyID,
    TableA.EmployeeName,
    TableB.EmployeeName
FROM @TableA TableA
FULL OUTER jOIN  @TableB TableB ON TableA.CompanyID = TableB.CompanyID AND TableB.EmployeeName = TableA.EmployeeName
WHERE coalesce(TableA.CompanyID,TableB.CompanyID) = 12

OUTPUT:

CompanyID   EmployeeName                   EmployeeName
----------- ------------------------------ ------------------------------
12          NULL                           someone 2
12          someone 3                      NULL
12          someone 4                      someone 4

(3 row(s) affected)
KM
Thanks; doesn't work, as it pulls in the matches for each table (no expansion ~ no NULLs at all).
stimpy77
what is `no expansion ~ no NULLs at all`?? Just edit your question to list sample data from all three tables, and what the output result set should be for that data.
KM
Thanks for the revision. I thought it would look something like that.
stimpy77
I see you edited again where you (I believe it was your answer?) had UNION JOINs. I'm considering all of my options, performance is critical because the joined tables are extremely slow so the CompanyID needs to be pre-filtered on each.
stimpy77
+1  A: 

Try this

DECLARE @Company TABLE 
( 
    CompanyID int 
) 

INSERT INTO @Company (CompanyID) VALUES (10) 
INSERT INTO @Company (CompanyID) VALUES (12) 

DECLARE @TableA TABLE 
( 
    EmployeeId int, 
    CompanyId int, 
    EmployeeName varchar(30) 
) 

DECLARE @TableB TABLE 
( 
    EmployeeId int, 
    CompanyId int, 
    EmployeeName varchar(30) 
) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 1, 10, 'someone' ) 

--INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
--VALUES ( 2, 12, 'someone 2' ) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 3' ) 

INSERT INTO @TableA ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 4' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 1, 10, 'someone' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 2, 12, 'someone 2' ) 

--INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
--VALUES ( 3, 12, 'someone 3' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName ) 
VALUES ( 3, 12, 'someone 4' ) 

INSERT INTO @TableB ( EmployeeId, CompanyId, EmployeeName )  
VALUES ( 3, 12, 'someone 4' )  

SELECT Company.CompanyID,  
   A.EmployeeNameTableA,  
   A.EmployeeNameTAbleB 
FROM @Company Company  
left OUTER JOIN (select TableA.EmployeeName as EmployeeNameTableA, TableB.EmployeeName as EmployeeNameTableB , 
coalesce(TableA.CompanyID,TableB.CompanyID) as CompanyID 
from @TableA TableA  
FULL OUTER JOIN @TableB TableB ON TableA.CompanyID = TableB.CompanyID and TableB.EmployeeName = TableA.EmployeeName and (tablea.companyid = 12 or tableb.companyid = 12))A ON Company.CompanyID = A.CompanyID  

WHERE Company.CompanyID = 12  
HLGEM
This isn't a stored proc it is a standalone query.
HLGEM
Sorry, you're right. I was in a hurry and didn't scroll down. :)
stimpy77
Is it possible to pre-filter the CompanyID in the nested query? The performance of my two tables is really slow, so if it selects everything before filtering that will not work.
stimpy77
Try my edited version
HLGEM
+1  A: 

The FULL OUTER JOIN should be made only between TableA and TableB on companyID AND employeeName since this is the value you want filled as NULL if it exists only on one table.
Once you get this, you can do an inner join with Company to get other data from Company.

FULL OUTER JOIN Solution:

select Company.companyID, EmployeeNameA, EmployeeNameB
from (
    SELECT isnull(TableA.CompanyID, TableB.CompanyID) as companyID,
        TableA.EmployeeName as EmployeeNameA,
        TableB.EmployeeName as EmployeeNameB
    FROM @TableA TableA 
    FULL OUTER JOIN @TableB TableB ON TableA.EmployeeName = TableB.EmployeeName and TableA.companyID = TableB.companyID
    WHERE
     TableA.CompanyID = 12 or TableB.CompanyID = 12 
) merged
inner join @Company Company
    on merged.companyID = Company.companyID

Personally I find it difficult to think in terms of FULL OUTER JOINS. My approach on this would be: Find the distinct EmployeeNames you need in your result by making a UNION between affected tables and then use left joins to get data from both tables thus getting your NULLs when you should.

LEFT JOIN Example:

select c.companyID, a.employeeName, b.employeeName
from  (
    select distinct employeeName, companyID
    from  (
        select a.employeeName, companyID 
        from @tableA  a
        union 
        select b.employeeName, companyID
        from @tableB b
    ) a
) z
inner join @company c
    on c.companyID = z.companyID
left join @tableA  a
    on z.companyID = a.companyID and z.employeeName = a.employeeName
left join @tableB  b
    on z.companyID = b.companyID and z.employeeName = b.employeeName
where z.companyID = 12
Dimitris Baltas
Thanks. Performance is critical because the joined tables are extremely slow they need to be pre-filtered on the CompanyID before they are joined.
stimpy77
.. and to be fair, the FULL OUTER sample you provided does indeed pre-filter on the tables being joined, which is great. But the filter of 12 is hard-coded in the nested query and cannot be passed into a view.
stimpy77
Actually, the nested query in your first sample can stand on its own without the outer wrapper. This gives me something to work with. Thanks!!
stimpy77
yes, i see your points regarding where condition in nested query. Regarding performance an approach (if of course you have the luxury of changing table structure) would be to introduce an Employee(employeeID, companyID, employeeName, companyID) table and then alter TableA and TableB to use employeeID instead of employeeName. In this scenario you would left join Employeee with both TableA and TableB
Dimitris Baltas
A: 

Here is a variation on Dimitris Baltas' answer, which is closer to what I had in mind.

SELECT Company.CompanyID,
    TableA.EmployeeName as EmployeeNameTableA,
    TableB.EmployeeName as EmployeeNameTableB
FROM @TableA TableA 
FULL OUTER JOIN @TableB TableB ON TableA.EmployeeName = TableB.EmployeeName 
    and TableA.companyID = TableB.companyID
INNER JOIN @Company Company ON (
    Company.CompanyID = TableA.CompanyId OR Company.CompanyID = TableB.CompanyId
)
WHERE Company.CompanyID = 12

A key thing I am looking for (and this sample doesn't do this but Dimitris' might) was pre-filtering on the CompanyID so that the execution plan will not sift through all of the rows of each of the joined tables before filtering out the CompanyID. In my case, the two joined tables are extremely slow.

I think what I'll ultimately have to do is keep using sprocs.

stimpy77