views:

47

answers:

6

I have several tables with different numbers and types of columns, and a single column in common.

+--------+---------+------------+-------------+
| person | beardID | beardStyle | beardLength |
+--------+---------+------------+-------------+

+--------+-------------+----------------+
| person | moustacheID | moustacheStyle |
+--------+-------------+----------------+

I want to fetch all the results that match a given value of the shared column. I can do it using multiple select statements like this:

SELECT * FROM beards WHERE person = "bob"

and

SELECT * FROM moustaches WHERE person = "bob"

But this requires multiple mysql API calls, which seems inefficient. I was hoping I could use UNION ALL to get all the results in a single API call, but UNION requires that the tables have the same number and similar type of columns. I could write a SELECT statement that would manually pad the results from each table by adding columns with NULL values, but that would quickly get unmanageable for a few more tables with a few more columns.

I'm looking for a result set roughly like this:

+--------+---------+------------+-------------+-------------+----------------+
| person | beardID | beardStyle | beardLength | moustacheID | moustacheStyle |
+--------+---------+------------+-------------+-------------+----------------+
| bob    | 1       | rasputin   | 1           |             |                |
+--------+---------+------------+-------------+-------------+----------------+
| bob    | 2       | samson     | 12          |             |                |
+--------+---------+------------+-------------+-------------+----------------+
| bob    |         |            |             | 1           | fu manchu      |
+--------+---------+------------+-------------+-------------+----------------+

Is there a way to achieve this that's fast and maintainable? Or am I better off running a separate query for each table?

Clarification:

I'm not looking for a cartesian product. I don't want a row for every combination of beard-and-moustache, I want a row for every beard and a row for every moustache.

So if there are 3 matching beards and 2 matching moustaches I should get 5 rows, not 6.

A: 

Join on person....

I.e.

Select t1.(asterix), t2.(asterix) FROM beards t1 INNER JOIN moustaches t2 On t2.person = t1.person

brumScouse
Did you try this? INNER JOIN retrieves the cartesian product of the matching records from the two tables. So one result for every combination of beard and moustache. Not what's described in the question at all.
Robert
@Robert: No, an INNER JOIN does not produce a cartesian product.
OMG Ponies
Sorry, I don't understand JOINs very well. What is the difference between INNER JOIN and taking the cartesian product and filtering on the join criteria?
Robert
An INNER JOIN selects selects all the records in relation A and all the records in relation B which have a value in common, and outputs a result set containing these rows.The CARTESIAN product is the result of creating a result set where all of the rows in relations A and B are slung together without rhyme or reason. I.e. for each row in relation A each row in relation B is added to the result set.
brumScouse
So isn't that what an INNER JOIN DOES? For each row in relation A each row in relation B is added to the result set, then the set is filtered so that only rows with a value in common are returned. If there's a difference I'm still not seeing it.
Robert
A: 
SELECT *
FROM   beards
       JOIN moustaches
         ON moustaches.person = beards.person
WHERE  person = "bob"  
Petah
I'm looking to retrieve a row for every beard and a row for every moustache, not a row for every beard-and-moustache.
Robert
A: 

Use:

SELECT b.person, 
       b.beardid,
       b.beardstyle,
       b.beardlength,
       NULL AS mustacheid,
       NULL AS moustachestyle
  FROM BEARDS b
UNION ALL
SELECT m.person,
       NULL,
       NULL,
       NULL,
       m.mustacheid,
       m.moustachestyle
  FROM MOUSTACHES m
OMG Ponies
Same problem as the earlier answers: This returns a row for every combination of beard and moustache, but I want a row for every beard and a row for every moustache.
Robert
@Robert: See update.
OMG Ponies
Thank you but I addressed this approach in the question. It quickly becomes an unmaintainable nightmare when there are multiple tables with multiple columns.
Robert
@Robert: I'm sorry, I'm not aware of any SQL convention or MySQL syntax to address what you're after.
OMG Ponies
A: 

I had fun with this, not sure it's entirely manageable with what more you have to add, but it accomplished the goal.

create table beard (
person varchar(20)
,beardID int
,beardStyle varchar(20)
,beardLength int )

create table moustache(
person varchar(20)
,moustacheID int
,moustacheStyle varchar(20))


insert into beard 
select 'bob', 1, 'rasputin', 1
union select 'bob', 2, 'samson', 12

insert into moustache
select 'bob', 1, 'fu manchu'

declare @facialhair table (
person varchar(20)
,beardID int
,beardStyle varchar(20)
,beardLength int
,moustacheID int
,moustacheStyle varchar(20))

declare @i int
declare @name varchar(20)

set @name = 'bob'
set @i = (select COUNT(*) from beard where person = @name)
        + (select COUNT(*) from moustache where person = @name) 

print @i

while @i > 0
    begin 
        insert into @facialhair (person, beardID, beardStyle, beardLength)
        select person, beardID, beardStyle, beardLength
        from beard
        where person = @name
    set @i = @i-@@ROWCOUNT

        insert into @facialhair (person, moustacheID, moustacheStyle)
        select person, moustacheID, moustacheStyle
        from moustache
        where person = @name
    set @i = @i-@@ROWCOUNT
    end

select *
from @facialhair
Vinnie
+1  A: 

this should be working fine:

SELECT * FROM `beards` b LEFT OUTER JOIN `mustaches` ON (0) WHERE  person = "bob"
UNION ALL
SELECT * FROM `beards` b RIGHT OUTER JOIN `mustaches` ON (0) WHERE  person = "bob"

you don't have to handle the columns by yourself. the left and right outer join do this job. unfortunately mysql doesn't have a full join. that's why you have to do it this way with a union

SELECT * FROM `customer` b LEFT OUTER JOIN `charges` ON (0) LEFT OUTER JOIN `day` ON (0)
UNION
SELECT * FROM `customer` b RIGHT OUTER JOIN `charges` ON (0) LEFT OUTER JOIN `day` ON (0)
UNION
SELECT * FROM `customer` b LEFT OUTER JOIN `charges` ON (0) RIGHT OUTER JOIN `day` ON (0)

this is a local test i made

ITroubs
Brilliant, works like a charm. So if I understand correctly you're using LEFT and RIGHT OUTER JOINs without specifying the join criteria to add columns full of NULLs to the results from each table (so that the result sets have matching tables), then UNIONing the results.
Robert
yep. the joins are just to make shure your result has all the columns fo beards and mustaches and by making the ON statement false it just returns all lines from beards or mustaches depending on the join used
ITroubs
So to add a 3rd table I would add an extra OUTER JOIN ON (0) to each of those SELECTs for the new table, plus UNION ALL and a new select statement OUTER JOINing the new table to those two tables the same way.
Robert
just saw you said exactly what i said XD
ITroubs
i added a three table example to my solution
ITroubs
Beautiful, thank you for that.
Robert
A: 

Hi, I think you would be better by making queries for data in each table.

One of other possibilities is to concatenate data from all columns into one big string (you could choose some sign to separete column's values), then you should be able to use union all clause to combine results from each query - but then you will have to parse each row.. And data types will be lost.

nimwath
and major drawback would be overhead of computation and a limited string size mysql has for group_concat! take a look at my solution. i think that would be the easiest way.
ITroubs
@ITroubs: nice solution I should say
nimwath