7

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.

Robert
  • 6,660
  • 5
  • 39
  • 62
  • 1
    While it could be done in most databases with a `FULL JOIN`, and in MySQL with ITroubs' workaround, I seriously doubt whether there is any significant performance gain to be had from this, and you're probably only creating a mess of obscure code your successor won't thank you for. Clear & precise code outweighs a performance gain in the very low percentages in most circumstances. – Wrikken Oct 26 '10 at 21:58

5 Answers5

7

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
  • 11,094
  • 4
  • 27
  • 25
  • 1
    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 Oct 26 '10 at 21:14
  • 2
    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 Oct 26 '10 at 21:16
  • 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 Oct 26 '10 at 21:17
  • just saw you said exactly what i said XD – ITroubs Oct 26 '10 at 21:20
  • 1
    i added a three table example to my solution – ITroubs Oct 26 '10 at 21:22
  • I couldn't get "ON (0)" to work in SQL Server 2008, but "ON 0=0" did work. Is this equivalent? – mindless.panda Jul 19 '11 at 20:32
  • any idea how to write this statement without using `RIGHT OUTER JOIN`. I use SQLAlchemy and it does not support `RIGHT OUTER JOIN` – Nihar Sawant Jul 09 '12 at 13:22
  • Is something like this possible when the two tables are derived tables? – Streamline Aug 24 '17 at 19:45
0

Join on person....

I.e.

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

brumScouse
  • 3,166
  • 1
  • 24
  • 38
  • 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 Oct 26 '10 at 19:50
  • @Robert: No, an INNER JOIN does not produce a cartesian product. – OMG Ponies Oct 26 '10 at 19:59
  • 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 Oct 26 '10 at 20:07
  • 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 Oct 26 '10 at 21:06
  • 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 Oct 26 '10 at 22:04
0
SELECT *
FROM   beards
       JOIN moustaches
         ON moustaches.person = beards.person
WHERE  person = "bob"  
Petah
  • 45,477
  • 28
  • 157
  • 213
  • 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 Oct 26 '10 at 19:53
0

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
  • 3,889
  • 1
  • 26
  • 29
0

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.

rsc
  • 4,234
  • 2
  • 30
  • 28
  • 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 Oct 26 '10 at 20:52
  • @ITroubs: nice solution I should say – rsc Oct 27 '10 at 07:54