tags:

views:

1118

answers:

4

The following query returns a single row, as desired. The 'contracts' table has 6 fields, each with a different username in it, for which I want to retrieve first/last names from a separate 'users' table. This works fine, but is there something more concise? I'm think the solution must be something using GROUP BY contracts.id to keep it one row, but I can't seem to find anything better than this slew of sub-selects.

Help!

SELECT contracts.field1, contracts.field2, 
(SELECT first_name FROM users WHERE username = service_provider_1),
(SELECT last_name FROM users WHERE username = service_provider_1),
(SELECT first_name FROM users WHERE username = service_provider_2),
(SELECT last_name FROM users WHERE username = service_provider_2),
(SELECT first_name FROM users WHERE username = service_org_business_contact),
(SELECT last_name FROM users WHERE username = service_org_business_contact),
(SELECT first_name FROM users WHERE username = client_service_contact_1),
(SELECT last_name FROM users WHERE username = client_service_contact_1),
(SELECT first_name FROM users WHERE username = client_service_contact_2),
(SELECT last_name FROM users WHERE username = client_service_contact_2),
(SELECT first_name FROM users WHERE username = client_business_contact),
(SELECT last_name FROM users WHERE username = client_business_contact)
FROM contracts
WHERE id = ?

It wouldn't be so bad if I could get both first/last name from a single sub-select... so even with a cruddy sub-select solution I have twice the verbosity I should think I need...

EDIT: I get it now. The answer to being able to join to the same table multiple times is to use an alias for the table. Thank folks! New code is:

SELECT contracts.field1, contracts.field2, 
sp1.first_name, sp1.last_name, 
sp2.first_name, sp2.last_name, 
sobc.first_name, sobc.last_name, 
csc1.first_name, csc1.last_name, 
csc2.first_name, csc2.last_name, 
cbc.first_name, cbc.last_name
FROM contracts
JOIN users AS sp1 ON service_provider_1 = sp1.username
JOIN users AS sp2 ON service_provider_2 = sp2.username
JOIN users AS sobc ON service_org_business_contact = sobc.username
JOIN users AS csc1 ON client_service_contact_1 = csc1.username
JOIN users AS csc2 ON client_service_contact_2 = csc2.username
JOIN users AS cbc ON client_business_contact = cbc.username
WHERE contracts.id = ?

Sadly, using joins is almost as verbose as using the subselects, but I assume it might be faster?

+8  A: 

Why not join to the users table 6 times?

dotjoe
Can't do anything except agree...
gbn
Fully agree - I go cross-eyed looking at the above query :-)
BrynJ
Yup, agreed. Happy to write the SQL out for you if you are not familiar with what has been suggested
Kristen
Yeah, I'm lost. When I try to join to the same table more than once I get "Not unique table/alias: 'users'". I appreciate Russ's code but it looks as long as mine (maybe longer if the etc. portions are filled out?)
Marcus
@Marcus - the ids in the ON clause probably aren't necessary so that will shorten the statement
Russ Cam
@Russ - good call, I've made it more concise now. Definitely a huge improvement. How could I have gone so, so long writing SQL without having to join to the same table multiple times?
Marcus
+6  A: 
SELECT contracts.field1, contracts.field2, 
sp1.first_name,
sp1.last_name 
sp2.first_name,
sp2.last_name,
/* etc, etc */
FROM
contracts
INNER JOIN
users sp1
ON
contracts.id = sp1.id
AND sp1.username = service_provider_1
INNER JOIN 
users sp2
ON contracts.id = sp2.id
AND sp2.username = service_provider_2
INNER JOIN 
users sobc
ON contracts.id = sobc.id
AND sobc.username = service_org_business_contact
INNER JOIN
/* etc, etc */
WHERE contracts.id = @myid

did you also want to combine first_name and last_name for each username? You can do this like

RTRIM(sp1.first_name) + ' ' + RTRIM(sp1.last_name) as sp1_name

in your SELECT clause. The RTRIM are necessary if the data type is (N)CHAR, not necessary if the type is (N)VARCHAR

EDIT: As stated in the comments on this answer, the JOIN on id is probably not necessary, in which case it becomes

SELECT 
contracts.field1, 
contracts.field2, 
sp1.first_name,
sp1.last_name 
sp2.first_name,
sp2.last_name,
/* etc, etc */
FROM
contracts
INNER JOIN
users sp1
ON
sp1.username = service_provider_1
INNER JOIN 
users sp2
ON
sp2.username = service_provider_2
INNER JOIN 
users sobc
ON 
sobc.username = service_org_business_contact
INNER JOIN
/* etc, etc */
WHERE contracts.id = @myid

My layout probably makes it appear longer! You may need to use LEFT OUTER JOINS if it is possible to have a contract record that doesn't have a first_name and last_name for one of it's fields within the users table.

Russ Cam
+1  A: 

I can't help but think that pushing the names out to a separate table along with a column for the name type might be a good idea here.

Edit: Or have another join table sitting between contracts and users to do a many-to-many join properly.

R. Bemrose
that does sound like a good idea
Russ Cam
The thing is, any given user can be a different "name type" depending on the contract, so there is no hard association there.
Marcus
A many-to-many join table between contracts and people, with a field for contact type in said table, might work for you as well. However, as with all normalization steps, it has the potential to slow things down. However, given that you're doing 12 joins otherwise, I couldn't see how.
R. Bemrose
+5  A: 
select 
 c.field1,c.field2
,SP1.first_name ,SP1.last_name
,SP2.first_name ,SP2.last_name
,SOBC.first_name,SOBC.last_name
,CSC1.first_name,CSC1.last_name
,CSC2.first_name,CSC2.last_name
,CBC.first_name ,CBC.last_name
from contracts C
left join users as SP1  on SP1.Username  = C.service_provider_1 
left join users as SP2  on SP2.Username  = C.service_provider_2 
left join users as SOBC on SOBC.Username = C.service_org_business_contact 
left join users as CSC1 on SP1.Username  = C.client_service_contact_1 
left join users as CSC2 on SP1.Username  = C.client_service_contact_2 
left join users as CBC  on CBC.Username  = C.client_business_contact 

where c.ID = ?
feihtthief
You should have gotten full credit for this answer too because I actually like the formatting best and used it in the edit above. But Russ got there first and was the one to clue me, so I marked the answer for him. Thanks to all!!
Marcus