diff --git a/docs/sphinx/source/reference/Joins.rst b/docs/sphinx/source/reference/Joins.rst index f41a9ca0a4..bf4168c3e3 100644 --- a/docs/sphinx/source/reference/Joins.rst +++ b/docs/sphinx/source/reference/Joins.rst @@ -4,13 +4,11 @@ Joins .. _joins: -Joins combine rows from two or more tables based on related column. FDB Record Layer supports INNER JOIN and joins using comma-separated table references in the FROM clause with join conditions specified in the WHERE clause. +Joins combine rows from two or more tables based on a related column. The FDB Record Layer supports the standard SQL join keywords ``INNER JOIN`` (or just ``JOIN``), ``LEFT OUTER JOIN`` (or ``LEFT JOIN``), and ``RIGHT OUTER JOIN`` (or ``RIGHT JOIN``). As an alternative to the ``INNER JOIN`` syntax, inner joins can also be expressed using comma-separated table references in the ``FROM`` clause with join conditions specified in the ``WHERE`` clause. .. important:: - FDB Record Layer **supports** only one standard SQL JOIN keyword ``INNER JOIN`` (or just ``JOIN``). - - FDB Record Layer **does not support** other standard SQL JOIN keywords (``LEFT JOIN``, ``RIGHT JOIN``, ``OUTER JOIN``, etc.). Use the comma-separated FROM clause instead. + The FDB Record Layer **does not support** the ``FULL OUTER JOIN`` and ``CROSS JOIN`` variants. Basic Join Syntax ================= @@ -18,8 +16,7 @@ Basic Join Syntax Cross Join (Cartesian Product) ------------------------------- -FDB Record Layer **does not support** ``CROSS JOIN`` keyword. -List multiple tables separated by commas instead: +The FDB Record Layer **does not support** the ``CROSS JOIN`` keyword. List multiple tables separated by commas instead: .. code-block:: sql @@ -27,10 +24,10 @@ List multiple tables separated by commas instead: This produces a Cartesian product of all rows from both tables. -Inner Join On Condition ---------------------------- +Inner Join with ON +------------------ -Use ON clause to specify join conditions: +Use the ``ON`` clause to specify join conditions: .. code-block:: sql @@ -38,7 +35,7 @@ Use ON clause to specify join conditions: FROM table1 INNER JOIN table2 ON table1.column = table2.column -This is equivalent to SELECT FROM comma-separated sources with WHERE Clause: +This is equivalent to a ``SELECT`` from comma-separated sources with a ``WHERE`` clause: .. code-block:: sql @@ -46,11 +43,10 @@ This is equivalent to SELECT FROM comma-separated sources with WHERE Clause: FROM table1, table2 WHERE table1.column = table2.column -Inner Join Using(Column) ---------------------------- +Inner Join with USING +--------------------- -The special case is used when joining tables have the same name column(s). -The column(s) are specified in the USING() clause: +The ``USING`` clause is a shorthand for when the joined tables share identically named columns. The columns are specified in the ``USING`` clause: .. code-block:: sql @@ -60,7 +56,7 @@ The column(s) are specified in the USING() clause: SELECT columns FROM a INNER JOIN b USING(c1) -It is equivalent to: +This is equivalent to: .. code-block:: sql @@ -74,7 +70,7 @@ And also equivalent to: SELECT columns FROM a, b WHERE a.c1 = b.c1 -An important feature of ``INNER JOIN USING`` is that it hides duplicated columns from the output: +An important feature of ``INNER JOIN … USING`` is that it hides duplicate columns from the output: .. code-block:: sql @@ -84,7 +80,7 @@ An important feature of ``INNER JOIN USING`` is that it hides duplicated columns SELECT * FROM a INNER JOIN b USING(c1) -In this case ``SELECT *`` returns only three columns: +In this case, ``SELECT *`` returns only three columns: .. list-table:: :header-rows: 0 @@ -93,7 +89,7 @@ In this case ``SELECT *`` returns only three columns: - `c2` - `c3` -However, the joining columns can be accessed directly using qualified names: +However, the joining columns can still be accessed using qualified names: .. code-block:: sql @@ -108,7 +104,7 @@ This returns two identical columns: * - `a.c1` - `b.c1` -``INNER JOIN USING`` maintains the standard order of the output from left to right excluding duplicates: +``INNER JOIN USING`` maintains the standard column order from left to right, excluding duplicates: .. code-block:: sql @@ -118,7 +114,7 @@ This returns two identical columns: SELECT * FROM a INNER JOIN b USING(c1, c5) -Returns 6 columns: all columns from ``a`` (c1, c2, c5, c6) and all columns from ``b`` excluding duplicates (c3, c7): +This returns six columns: all columns from ``a`` (c1, c2, c5, c6) followed by the non-duplicate columns from ``b`` (c3, c7). .. list-table:: :header-rows: 0 @@ -136,7 +132,7 @@ Examples Setup ----- -For these examples, assume we have the following tables: +For these examples, assume the following tables: .. code-block:: sql @@ -203,8 +199,8 @@ Join employees with their departments: * - :json:`"Emily"` - :json:`"Martinez"` -Consecutive Join --------------- +Consecutive Joins +----------------- Join across three tables to find departments and their projects: @@ -226,7 +222,7 @@ Join across three tables to find departments and their projects: * - :json:`"Marketing"` - :json:`"SEO"` -Joining the result of first join (employees and departments) to projects; +The result of the first join (employees and departments) is joined to projects. Join with Subquery ------------------ @@ -281,7 +277,7 @@ Join subqueries that themselves contain joins: * - :json:`"Marketing"` - :json:`"SEO"` -The subquery first joins employees with departments, then the result is joined with projects. +The subquery first joins employees with departments; the result is then joined with projects. Join with CTEs -------------- @@ -305,7 +301,7 @@ Join a table to itself: SELECT * FROM Table1, Table1 WHERE col1 = 10 -This self-join can be used to find relationships within the same table. Use aliases to distinguish between the two references: +A self-join can be used to find relationships within the same table. Use aliases to distinguish between the two references: .. code-block:: sql @@ -317,7 +313,7 @@ This self-join can be used to find relationships within the same table. Use alia Semi-Join with EXISTS ---------------------- -Use EXISTS to implement a semi-join (find rows that have matching rows in another table): +Use ``EXISTS`` to implement a semi-join (find rows that have matching rows in another table): .. code-block:: sql @@ -344,7 +340,7 @@ This finds all employees who have at least one project assigned, without returni Join with User-Defined Functions --------------------------------- -Join results from user-defined functions: +User-defined functions can be used like tables in the ``FROM`` clause and joined with conditions in the ``WHERE`` clause: .. code-block:: sql @@ -352,8 +348,6 @@ Join results from user-defined functions: FROM f1(103, 'b') A, f1(103, 'b') B WHERE A.col1 = B.col1 -User-defined functions can be used like tables in the FROM clause and joined with join conditions in the WHERE clause. - Important Notes =============== @@ -362,9 +356,9 @@ Table Aliases Use aliases to: -- Distinguish between multiple references to the same table -- Shorten long table names -- Reference columns from specific tables in multi-table joins +- Distinguish between multiple references to the same table. +- Shorten long table names. +- Reference columns from specific tables in multi-table joins. .. code-block:: sql @@ -375,14 +369,14 @@ Use aliases to: Join Conditions --------------- -- Join conditions should be specified in the WHERE clause (for comma-separated tables) or the ON clause (for INNER JOIN) -- Use ``AND`` to combine multiple join conditions and filters -- Missing join conditions result in a Cartesian product (all combinations) +- Join conditions should be specified in the ``WHERE`` clause (for comma-separated tables) or the ``ON`` clause (for ``INNER JOIN``, ``LEFT OUTER JOIN``, and ``RIGHT OUTER JOIN``). +- Use ``AND`` to combine multiple join conditions and filters. +- Omitting join conditions produces a Cartesian product (all combinations of rows). See Also ======== -* :doc:`sql_commands/DQL/INNER_JOIN` - INNER JOIN syntax +* :doc:`sql_commands/DQL/JOIN` - JOIN syntax * :doc:`sql_commands/DQL/SELECT` - SELECT statement syntax * :doc:`sql_commands/DQL/WHERE` - WHERE clause filtering * :doc:`Subqueries` - Subqueries and correlated subqueries diff --git a/docs/sphinx/source/reference/sql_commands/DQL.diagram b/docs/sphinx/source/reference/sql_commands/DQL.diagram index 33ebca257c..bbd9cff3ba 100644 --- a/docs/sphinx/source/reference/sql_commands/DQL.diagram +++ b/docs/sphinx/source/reference/sql_commands/DQL.diagram @@ -8,7 +8,14 @@ Diagram( NonTerminal('source'), Optional( Sequence( - Terminal('INNER JOIN'), + Choice(0, + Terminal('INNER JOIN'), + Sequence( + Choice(0, Terminal('LEFT'), Terminal('RIGHT')), + Optional(Terminal('OUTER'), 'skip'), + Terminal('JOIN') + ) + ), NonTerminal('source'), Choice(0, Sequence( diff --git a/docs/sphinx/source/reference/sql_commands/DQL.rst b/docs/sphinx/source/reference/sql_commands/DQL.rst index 22a2aba461..c7c3afc06b 100644 --- a/docs/sphinx/source/reference/sql_commands/DQL.rst +++ b/docs/sphinx/source/reference/sql_commands/DQL.rst @@ -20,7 +20,7 @@ Syntax DQL/SELECT DQL/WITH - DQL/INNER_JOIN + DQL/JOIN DQL/WHERE DQL/GROUP_BY DQL/CASE diff --git a/docs/sphinx/source/reference/sql_commands/DQL/INNER_JOIN.rst b/docs/sphinx/source/reference/sql_commands/DQL/INNER_JOIN.rst deleted file mode 100644 index 545b020f4c..0000000000 --- a/docs/sphinx/source/reference/sql_commands/DQL/INNER_JOIN.rst +++ /dev/null @@ -1,50 +0,0 @@ -========== -INNER JOIN -========== - -.. _inner_join: - -Combines rows from two sources based on a join condition. INNER JOIN returns only the rows where the join condition is satisfied in both tables. - -Syntax -====== - -.. raw:: html - :file: INNER_JOIN.diagram.svg - -The INNER JOIN clause is used in SELECT statements: - -.. code-block:: sql - - SELECT columns - FROM table1 INNER JOIN table2 - ON table1.column = table2.column - -The ``JOIN`` keyword can be used as a shorthand for ``INNER JOIN``: - -Parameters -========== - -``source`` - The table or subquery to join with. - -``ON joinCondition`` - A boolean expression that specifies the join condition. Only rows where this condition evaluates to true are included in the result. - -``USING (column(s))`` - A comma-separated list of column names that exist in both tables. This is shorthand for joining on equality of these columns. The USING clause automatically removes duplicate columns from the result set. - -Returns -======= - -Returns a result set containing rows where the join condition is satisfied. For each matching pair of rows from the two sources, a combined row is produced with columns from both tables. - -For more details see :doc:`../../Joins`. - -See Also -======== - -* :doc:`SELECT` - SELECT statement syntax -* :doc:`WHERE` - WHERE clause filtering -* :doc:`../../Subqueries` - Subqueries and correlated subqueries -* :doc:`WITH` - Common Table Expressions diff --git a/docs/sphinx/source/reference/sql_commands/DQL/JOIN.rst b/docs/sphinx/source/reference/sql_commands/DQL/JOIN.rst new file mode 100644 index 0000000000..5e2bfceb69 --- /dev/null +++ b/docs/sphinx/source/reference/sql_commands/DQL/JOIN.rst @@ -0,0 +1,203 @@ +==== +JOIN +==== + +.. _inner_join: +.. _left_outer_join: +.. _right_outer_join: +.. _outer_join: + +The FDB Record Layer supports ``INNER JOIN``, ``LEFT OUTER JOIN``, and ``RIGHT OUTER JOIN``. All three accept an ``ON`` condition or a ``USING`` column list. The ``FULL OUTER JOIN`` variant is not yet supported. + +INNER JOIN +========== + +An ``INNER JOIN`` combines rows from two sources based on a join condition. It returns only the rows where the join condition is satisfied in both tables. + +Syntax +------ + +.. raw:: html + :file: INNER_JOIN.diagram.svg + +.. code-block:: sql + + SELECT columns + FROM table1 INNER JOIN table2 + ON table1.column = table2.column + +The ``INNER`` keyword is optional: ``JOIN`` can be used as a shorthand for ``INNER JOIN``. + +Parameters +---------- + +``source`` + The table or subquery to join with. + +``ON joinCondition`` + A boolean expression that specifies the join condition. Only rows where this condition evaluates to true are included in the result. + +``USING (column(s))`` + A comma-separated list of column names that exist in both tables. This is shorthand for joining on equality of these columns. The ``USING`` clause automatically removes duplicate columns from the result set. + +Returns +------- + +Returns a result set containing rows where the join condition is satisfied. For each matching pair of rows from the two sources, a combined row is produced with columns from both tables. + +OUTER JOIN +========== + +An ``OUTER JOIN`` combines rows from two sources based on a join condition. Unlike an inner join, an outer join preserves every row from one of the two sides. When no matching row exists on the other side, the columns from that side are filled with ``NULL``. + +The FDB Record Layer supports ``LEFT OUTER JOIN`` and ``RIGHT OUTER JOIN``. A ``LEFT OUTER JOIN`` preserves every row from the left side; a ``RIGHT OUTER JOIN`` preserves every row from the right side. Aside from the order of the produced columns, the two are equivalent: ``A LEFT OUTER JOIN B ON …`` produces the same set of rows as ``B RIGHT OUTER JOIN A ON …``. + +Syntax +------ + +.. raw:: html + :file: OUTER_JOIN.diagram.svg + +.. code-block:: sql + + SELECT columns + FROM table1 LEFT OUTER JOIN table2 + ON table1.column = table2.column + + SELECT columns + FROM table1 RIGHT OUTER JOIN table2 + ON table1.column = table2.column + +The ``OUTER`` keyword is optional: ``LEFT JOIN`` and ``LEFT OUTER JOIN`` are equivalent, as are ``RIGHT JOIN`` and ``RIGHT OUTER JOIN``. + +Parameters +---------- + +``source`` + The table or subquery to join with. + +``ON joinCondition`` + A boolean expression that specifies the join condition. Rows on the preserved side (left for ``LEFT OUTER JOIN``, right for ``RIGHT OUTER JOIN``) that have no match on the other side produce a single output row with ``NULL`` in every column from the other side. + +``USING (column(s))`` + A comma-separated list of column names that exist in both tables. This is shorthand for joining on equality of these columns. As with ``INNER JOIN … USING``, duplicate columns are hidden from the result set. + +Returns +------- + +Returns a result set containing: + +- One combined row for every matching pair (same as an inner join). +- One null-padded row for every row on the preserved side that has *no* match on the other side. + +Every row from the preserved side therefore appears at least once in the output. + +Examples +======== + +For the examples below, assume the following tables: + +.. code-block:: sql + + CREATE TABLE emp ( + id BIGINT, fname STRING, lname STRING, dept_id BIGINT, + PRIMARY KEY(id) + ) + CREATE TABLE dept ( + id BIGINT, name STRING, + PRIMARY KEY(id) + ) + + INSERT INTO emp VALUES + (1, 'Alice', 'Smith', 1), + (2, 'Bob', 'Jones', 1), + (3, 'Carol', 'Lee', 2), + (4, 'Dave', 'Kim', 99) + + INSERT INTO dept VALUES + (1, 'Engineering'), + (2, 'Sales'), + (3, 'Marketing') + +Basic LEFT OUTER JOIN +--------------------- + +Return every employee together with their department. Dave (``dept_id = 99``) has no matching department, so the department name in the result is ``NULL``. + +.. code-block:: sql + + SELECT e.fname, e.lname, d.name + FROM emp e LEFT OUTER JOIN dept d ON e.dept_id = d.id + +.. list-table:: + :header-rows: 1 + + * - :sql:`fname` + - :sql:`lname` + - :sql:`name` + * - :json:`"Alice"` + - :json:`"Smith"` + - :json:`"Engineering"` + * - :json:`"Bob"` + - :json:`"Jones"` + - :json:`"Engineering"` + * - :json:`"Carol"` + - :json:`"Lee"` + - :json:`"Sales"` + * - :json:`"Dave"` + - :json:`"Kim"` + - ``NULL`` + +RIGHT OUTER JOIN +---------------- + +Because ``A LEFT OUTER JOIN B`` and ``B RIGHT OUTER JOIN A`` produce the same set of rows, the query above can equivalently be written as: + +.. code-block:: sql + + SELECT e.fname, e.lname, d.name + FROM dept d RIGHT OUTER JOIN emp e ON e.dept_id = d.id + +Use whichever direction reads more naturally for the query at hand. + +Anti-Join Pattern +----------------- + +Use ``LEFT OUTER JOIN`` with ``WHERE … IS NULL`` on the right side to find only the rows with *no* match. This is a common way to express an anti-join in SQL. + +.. code-block:: sql + + SELECT e.fname, e.lname + FROM emp e LEFT JOIN dept d ON e.dept_id = d.id + WHERE d.id IS NULL + +.. list-table:: + :header-rows: 1 + + * - :sql:`fname` + - :sql:`lname` + * - :json:`"Dave"` + - :json:`"Kim"` + +Chained Joins +------------- + +A ``LEFT OUTER JOIN`` can follow an ``INNER JOIN`` or another ``LEFT OUTER JOIN``: + +.. code-block:: sql + + SELECT e.fname, d.name, p.name + FROM emp e + JOIN dept d ON e.dept_id = d.id + LEFT JOIN project p ON e.id = p.emp_id + +For further examples, see :doc:`../../Joins`. + +See Also +======== + +* :doc:`SELECT` - SELECT statement syntax +* :doc:`WHERE` - WHERE clause filtering +* :doc:`../../Subqueries` - Subqueries and correlated subqueries +* :doc:`WITH` - Common Table Expressions +* :doc:`../../Joins` - General overview of joins, with examples diff --git a/docs/sphinx/source/reference/sql_commands/DQL/OUTER_JOIN.diagram b/docs/sphinx/source/reference/sql_commands/DQL/OUTER_JOIN.diagram new file mode 100644 index 0000000000..1dc390df95 --- /dev/null +++ b/docs/sphinx/source/reference/sql_commands/DQL/OUTER_JOIN.diagram @@ -0,0 +1,21 @@ +Diagram( + Choice(0, Terminal('LEFT'), Terminal('RIGHT')), + Optional( + Terminal('OUTER'), + 'skip' + ), + Terminal('JOIN'), + NonTerminal('source'), + Choice(0, + Sequence( + Terminal('ON'), + NonTerminal('joinCondition') + ), + Sequence( + Terminal('USING'), + Terminal('('), + NonTerminal('column(s)'), + Terminal(')') + ) + ) +)