78

SQLite only has INNER and LEFT JOIN.

Is there a way to do a FULL OUTER JOIN with SQLite?

Palec
  • 12,743
  • 8
  • 69
  • 138
Yada
  • 30,349
  • 24
  • 103
  • 144
  • Related: [SQLite3 Simulate RIGHT OUTER JOIN with LEFT JOINs and UNION](https://stackoverflow.com/q/9147025/8583692) – Mahozad Feb 23 '23 at 10:18

5 Answers5

104

Yes. See the Join (SQL) > Outer join > Full outer join article on Wikipedia.

SELECT employee.*, department.*
FROM   employee 
       LEFT JOIN department 
          ON employee.DepartmentID = department.DepartmentID
UNION ALL
SELECT employee.*, department.*
FROM   department
       LEFT JOIN employee
          ON employee.DepartmentID = department.DepartmentID
WHERE  employee.DepartmentID IS NULL
Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
  • 1
    The Wikipedia example is forming a `UNION` of three queries, where your example only has two. Can you please explain the difference? – Graham Borland Mar 22 '12 at 12:33
  • 4
    @GrahamBorland: The code in my answer was taken from Wikipedia. I think Wikipedia has been edited since I posted. The difference is that they use an inner join and two selects to get the non-matching records (one for each table). The query in my answer combines the inner join and one of the selects into a left join. It's (A)+(B)+(C) compared to (A+B)+(C). – Mark Byers Mar 22 '12 at 13:44
  • 1
    It this still the way with the latest SQLite (3.7.x) or can a standard OUTER JOIN be used? – 01es Aug 30 '12 at 06:38
  • 12
    If you use UNION ALL, it is crucial that one of the two selects excludes the 'inner join' rows, such as the WHERE condition shown. Alternatively, you could use plain UNION and have its duplicate elimination avoid repeating the 'inner join' rows. – Jonathan Leffler Mar 21 '13 at 01:49
  • @01es According to the [current SQLite doc page](https://www.sqlite.org/syntax/join-operator.html), `FULL` and `RIGHT` still aren't valid `OUTER JOIN` operators. – starturtle Sep 20 '16 at 09:32
  • @JonathanLeffler Isn't that the purpose of the WHERE clause in the second query, to eliminate all matching records of the first query, and only those where records exist in the second, but not the first? – mydoghasworms May 31 '17 at 09:29
  • This does not work for general FULL OUTER JOIN ON an arbitrary condition, which is INNER JOIN ON rows UNION ALL the NULL-extended unjoined rows of the input tables. Also attempts that check for NULL in columns they didn't add don't work because an output NULL could have come from input. – philipxy Mar 13 '23 at 22:58
19

FULL OUTER JOIN is natively supported starting from SQLite 3.39.0:

2.1. Determination of input data (FROM clause processing)

enter image description here

A "FULL JOIN" or "FULL OUTER JOIN" is a combination of a "LEFT JOIN" and a "RIGHT JOIN". Extra rows of output are added for each row in left dataset that matches no rows in the right, and for each row in the right dataset that matches no rows in the left. Unmatched columns are filled in with NULL.


Demo:

CREATE TABLE t1 AS
SELECT 1 AS id, 'A' AS col UNION
SELECT 2 AS id, 'B' AS col;

CREATE TABLE t2 AS
SELECT 1 AS id, 999 AS val UNION
SELECT 3 AS id, 100 AS val;

Query:

SELECT *
FROM t1
FULL JOIN t2
  ON t1.id = t2.id;

db<>fiddle demo

Lukasz Szozda
  • 162,964
  • 23
  • 234
  • 275
  • For some reason, I get null values in the columns I joined on when I do this. This is despite none of these columns containing null values. I would expect NULL to show up in unshared columns between two tables... but the columns I'm JOINing ON... Gotta be a bug, right? – MrChadMWood May 11 '23 at 16:24
  • 1
    @MrChadMWood Join columns could be NULL on output. Demo from answer: https://dbfiddle.uk/YDRM0-9F Please note `id` is not null in both t1/t2 tables but in the result there are NULL in `id` column. – Lukasz Szozda May 11 '23 at 16:27
  • 1
    I think you just saved me hours of mindless wondering, thanks! I forget that the ID column will show up twice (one from each table), and it is possible to just fill the NULL values in one ID column with the values from the other ID column. I wasn't even selecting the second ID column, so I was really confused. I think I expected all of this to happen automatically for some reason. – MrChadMWood May 11 '23 at 16:37
11

Running with Jonathan Leffler's comment in Mark Byers' answer, here's an alternative answer which uses UNION instead of UNION ALL:

SELECT Table1.*, Table2.* FROM Table1 LEFT JOIN Table2 ON Table1.Column1A = Table2.Column2A
UNION
SELECT Table1.*, Table2.* FROM Table2 LEFT JOIN Table1 ON Table1.Column1A = Table2.Column2A

Notes

  1. UNION removes duplicate rows from the result set so there's no need for an extra WHERE Table1.Column1A IS NULL on the right hand side of the UNION as is required with UNION ALL.
  2. You can use LEFT OUTER JOIN instead of LEFT JOIN if you wish. There's no difference between the two.
Adil Hussain
  • 30,049
  • 21
  • 112
  • 147
  • 3
    I think WHERE employee.DepartmentID IS NULL is a must, although you don't mind to get duplications row – Xenione May 20 '15 at 09:55
  • 1
    If I am not mistaken, this should have the same result, the UNION will match duplicate records, but is less efficient than the UNION ALL. – Michael Erickson Dec 12 '16 at 23:56
  • 2
    UNION (_without_ ALL) removes duplicate rows – cowbert Aug 17 '17 at 03:55
  • 4
    `SELECT *` doesn't produce correct output if columns have the same name. `SELECT table_name_1.*, table_name_2.*` works for me. – Cris Luengo May 09 '18 at 21:58
  • Thanks for calling that out @CrisLuengo I've updated my answer accordingly. – Adil Hussain Mar 13 '23 at 19:26
  • @Xenione: I've added a note to my answer to explain why a clause like `WHERE employee.DepartmentID IS NULL` is not required. – Adil Hussain Mar 13 '23 at 19:28
  • This does not work for general FULL OUTER JOIN ON an arbitrary condition, which can contain duplicates, and which is INNER JOIN ON rows UNION ALL the NULL-extended unjoined rows of the input tables. Also attempts that check for NULL in columns they didn't add don't work because an output NULL could have come from input. – philipxy Mar 13 '23 at 23:01
  • Please put what is needed for your post in your post, not just at a link, & relate to your post. Comments are ephemeral. Though other posts are less likely deleted or edited to invalidate what you are referring to in it, a reader should not have to read it all & figure out what part is relevant & how. – philipxy Mar 13 '23 at 23:11
  • 1
    (Even this code, with its special case of an equality condition, can return the wrong result when a row is a duplicate in an input table or is in both input tables.) – philipxy Mar 13 '23 at 23:20
1

For people, searching for an answer to emulate a Distinct Full Outer Join: Due to the fact, that SQLite does neither support a Full Outer Join, nor a Right Join, i had to emulate a distinct full outer join / an inverted inner join (however you might call it). The following Venn diagram shows the expected output:

Venn Diagram: Distinct Full Outer Join


To receive this expected output, i combined two Left Join clauses (the example refers to two identical built tables with partially differing data. I wanted to output only the data which does either appear in table A OR in table B).

SELECT A.flightNumber, A.offblockTime, A.airspaceCount, A.departure, A.arrival FROM D2flights A
    LEFT JOIN D1flights B
        ON A.flightNumber = B.flightNumber
        WHERE B.flightNumber IS NULL
UNION 
SELECT A.flightNumber, A.offblockTime, A.airspaceCount,  A.departure, A.arrival FROM D1flights A
    LEFT JOIN D2flights B
        ON A.flightNumber = B.flightNumber
        WHERE B.flightNumber IS NULL

The SQLite statement above returns the expected result in one query. It appears, that the UNION clause does also order the output via the flightNumber column.

The code has been tested with SQLite version 3.32.2

  • This is not FULL OUTER JOIN, which can contain duplicates, and is INNER JOIN rows UNION ALL the NULL-extended unjoined rows of the input tables. Also attempts that check for NULL in columns they didn't add don't work because an output NULL could have come from input. Also SQL tables are bags not sets so Venn diagrams are inappropriate without complex justification that makes them not helpful. You can see this if you say what the areas represent including for when there are duplicate rows. And for when sets are input & output the circles aren't A & B, they are A left join B & A right join B. – philipxy Mar 13 '23 at 22:52
  • [Venn Diagram for Natural Join](https://stackoverflow.com/a/55642928/3404097) – philipxy Mar 13 '23 at 23:06
0

I will belatedly pitch in my 2 cents. Consider the 2 simple tables people1 and people2 below:

   id   name age
0   1    teo  59
1   2   niko  57
2   3  maria  54 


    id   name weight
0   1    teo    186
1   2  maria    125
2   3    evi    108

First, we create a temporaty view, v_all, where we join with UNION the two opposite LEFT JOINS as below:

CREATE TEMP VIEW v_all AS
              SELECT p1.name AS name1, p1.age,
                    p2.name AS name2, p2.weight
              FROM people1 p1
              LEFT JOIN people2 AS p2 
              USING (name)
              UNION
              SELECT p1.name AS name1, p1.age,
                  p2.name AS name2, p2.weight
              FROM people2 AS p2
              LEFT JOIN people1 AS p1
              USING (name);

However, we end up with 2 name columns,name1 and name2, which may have a null value or equal values. What we want is to combine name1 and name2 in a single column name. We can do that with a CASE query as below:

SELECT age,weight,
                CASE
                  WHEN name1 IS NULL
                    THEN name2
                  WHEN name2 IS NULL
                    THEN name1
                  WHEN name1=name2
                    THEN name1
                END name
              FROM v_all

And we finally end up with:

      name weight   age
0    evi    108  None
1  maria    125    54
2   niko   None    57
3    teo    186    59

Of course you could combine the two in a single query, without having to create a temp view. I avoided doing so, in order to highlight the insufficiency of just 2 left joins and a union, which is what i have seen so far recommended.

  • This does not work for general FULL OUTER JOIN ON an arbitrary condition, which can contain duplicates, and which is INNER JOIN ON rows UNION ALL the NULL-extended unjoined rows of the input tables. Also attempts that check for NULL in columns they didn't add don't work because an output NULL could have come from input. – philipxy Mar 13 '23 at 23:03