0

Two tables...

Table 1:
Name Eyes Hair
---- ---- ----
Able Blue Cyan
Bill Cyan Blue
Sara Blue Blue

Table 2:
Name Hand Foot
---- ---- ----
Carl Left Right
Sara Right Left

Can MySQL perform a "joined" or "unioned" or whatever query that can produce:

Result:
Name Eyes Hair Hand Foot
---- ---- ---- ---- ----
Able Blue Cyan null null
Bill Cyan Blue null null
Carl null null Left Right
Sara Blue Blue Right Left
Drew
  • 24,851
  • 10
  • 43
  • 78
Fred
  • 47
  • 6
  • Shout out to @Drew because I was having the hardest time reading that. – Jhecht Apr 24 '16 at 00:21
  • 1
    Check UNION operator, and add the nulls you need in the selects to match the number of columns and types – Gonzalo.- Apr 24 '16 at 00:23
  • 1
    what happens when Sara is in both tables? You probably need a better description – Drew Apr 24 '16 at 00:26
  • Great question Drew. Please see edited question. – Fred Apr 24 '16 at 00:53
  • I will give it a shot. In the future, just put a @ in front of the name of the person. Otherwise they are not notified – Drew Apr 24 '16 at 02:04
  • thanks @Drew, newbie... – Fred Apr 24 '16 at 02:12
  • Thanks @Drew for the guick, accurate answer, and for actually running the query! P.S. I am looking for some DB schema design consulting. If you are interested, or know of another good resource, please let me know... – Fred Apr 24 '16 at 16:00
  • if I don't run it I rarely give it. drewpierce747@gmail.com or any one here too – Drew Apr 24 '16 at 16:03

4 Answers4

1

This looks like a "full outer" join operation. Unfortunately, MySQL doesn't provide syntax to for this operation in a single SELECT statement.

Fortunately, it is possible to emulate a full outer join, using two SELECT statements.

Assuming that the `name` column is the primary key, or at least guaranteed to be unique...

There are essentially three possible conditions for a given name:

  • a row exists in both (the unfortunately named) table1 and table2 (e.g. Sara)
  • a row exists in table1 but not table2 (e.g. Bill)
  • a row exists in table2 but not table1 (e.g. Carl)

(The fourth condition, name value doesn't exist in either table1 or table2, we're not going to concern ourselves with, since there's no row source for e.g. Javier.)

To get rows that satisfy the first condition, we can use an INNER JOIN

The second condition, we can include those rows in the same SELECT by converting the INNER JOIN to a LEFT OUTER JOIN.

The third condition, we will need a separate SELECT, which can use an anti-join pattern. (We could also use a NOT EXISTS or a NOT IN, but this particular specification is one that anti-join is perfectly suited to.)

We can use a UNION ALL set operation to combine the results from the two SELECT statements.

We'll get the rows first, and then work on the expressions to get the columns

For the first query

SELECT t1.name 
  FROM table1 t1
  LEFT
  JOIN table2 t2
    ON t2.name = t1.name 

For the second query

SELECT a2.name 
  FROM table2 a2
  LEFT
  JOIN table1 a1
    ON a1.name = a2.name
 WHERE a1.name IS NULL

The trick here (the anti-join pattern) is the condition in the WHERE clause, that excludes any rows that found a match. So we are left with rows from a2 that didn't have a matching row in a1.

To use a UNION ALL set operation to combine those, the number of columns and datatypes of the columns have to match. Filling out the expressions in the SELECT lists, and adding an ORDER BY clause, we get something like this:

(not tested)

 ( 
   SELECT t1.name 
        , t1.eyes
        , t1.hair
        , t2.hand
        , t2.foot
     FROM table1 t1
     LEFT
     JOIN table2 t2
       ON t2.name = t1.name
 )
 UNION ALL
 (
   SELECT a2.name 
        , a1.eyes
        , a1.hair
        , a2.hand
        , a2.foot
     FROM table2 a2
     LEFT
     JOIN table1 a1
       ON a1.name = a2.name
    WHERE a1.name IS NULL
 )
 ORDER BY `name`

Note that to get those ordered, we add parenthesis around each SELECT and follow the last one with an ORDER BY clause.

Again, in MySQL this requires a minimum of two SELECT statements.

There are other query patterns that can return an equivalent result, but those will require more than two SELECT.

spencer7593
  • 106,611
  • 15
  • 112
  • 140
0

Since there's no way I can test this, please be aware this might not work the first time you try it, and that I would need for you to post up the error you get so that I know (or you could create a SqlFiddle with some dummy data), but you should be able to do so like this:

Select Name, Eyes, Hair, Null as Hand, Null as Foot from Table1
Union
Select Name, Null as Eyes, Null as Hair, Hand, Foot from Table2

However, I think the best thing for you to do is to reconsider the way your database is set up currently. I can almost guarantee you'll run into problems.

Jhecht
  • 4,407
  • 1
  • 26
  • 44
  • This is close. But this will return two rows for Sara. It would be possible to wrap this query in parens, and use it as an inline view in another query. The outer query would select from this, do a "GROUP BY name". To get the other columns values, we would use aggregate expressions, for example MAX(eyes) AS eyes. The MAX function will return a non-null value before it returns a NULL (MAX never considers a NULL to be "greater" than a non-NULL.) – spencer7593 Apr 24 '16 at 03:24
0

Schema

insert table1 (name,eyes,hair) values
('Able','Blue','Cyan'),
('Bill','Cyan','Blue'),
('Sara','Blue','Blue');

insert table2 (name,hand,foot) values
('Carl','Left','Right'),
('Sara','Right','Left');

Query

Select Name, Eyes, Hair, Null as Hand, Null as Foot from table1
where not exists (select * from table2 where table2.name=table1.name) 
union 
Select Name, Null as Eyes, Null as Hair, Hand, Foot from table2
where not exists (select * from table1 where table1.name=table2.name) 
union 
select t1.Name,t1.Eyes,t1.Hair,t2.Hand,t2.Foot 
from table1 t1 
join table2 t2 
on t2.Name=t1.Name;

Results

+------+------+------+-------+-------+
| Name | Eyes | Hair | Hand  | Foot  |
+------+------+------+-------+-------+
| Able | Blue | Cyan | NULL  | NULL  |
| Bill | Cyan | Blue | NULL  | NULL  |
| Carl | NULL | NULL | Left  | Right |
| Sara | Blue | Blue | Right | Left  |
+------+------+------+-------+-------+
4 rows in set (0.00 sec)

The query as I did it has 3 parts to the union. The first is that in table1 not in table2. The second is vice-versa. The third part is that in common (join). For the join, the t1 and t2 represent simple aliases for the table names.

For the first two parts of the union I used a not exists clause as opposed to a NOT IN clause as the latter is something I never use due to being over paranoid of dangerous NOT IN results. If one doesn't know their data extremely well, NOT IN can return unexpected results and waste a ton of time debugging in situations of data nullability.

Drew
  • 24,851
  • 10
  • 43
  • 78
  • For improved performance, the third SELECT statement could be rewritten as an outer join (instead of an inner join). That would return a result that includes rows from the first SELECT, so that first SELECT could be eliminated. Also, if \`name\` is unique in table1 and table2, the UNION can be replaced with UNION ALL, to avoid the operations required to remove duplicates.) – spencer7593 Apr 24 '16 at 19:32
  • Yep I figured an emulated full outer could reduce it to 2. Thx – Drew Apr 24 '16 at 19:48
  • Without support for a FULL [OUTER] JOIN syntax for the operation, it's not possible to return the resultset with a single select. It requires at least two SELECT statements. The pattern in this answer uses three SELECT. There's nothing wrong with using three,. but we might get better performance with a pattern that uses just two. – spencer7593 Apr 24 '16 at 19:56
-1
SELECT FROM table1
FULL OUTER JOIN table2 
ON table1.name = table2.name
WHERE table1.name = null
OR table2.name = null
Gerard Frijters
  • 358
  • 3
  • 11
  • Unfortunately MySQL doesn't support a FULL OUTER JOIN operation. Also, the conditions in the WHERE clause will never evaluate to TRUE, because equality comparison to a null will always yields NULL. And there needs to be at least one expression between SELECT and FROM. A valiant effort to be sure. But I don't think this answers the question asked. – spencer7593 Apr 24 '16 at 03:11