{"id":97917,"date":"2023-09-25T00:00:29","date_gmt":"2023-09-25T00:00:29","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=97917"},"modified":"2026-04-15T18:54:08","modified_gmt":"2026-04-15T18:54:08","slug":"mysql-joins","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/mysql\/mysql-joins\/","title":{"rendered":"MySQL Joins Explained: INNER, LEFT, RIGHT, FULL OUTER, and CROSS JOIN"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\" id=\"h-executive-summary\">Executive Summary<\/h2>\n\n\n\n<p><strong>MySQL joins combine rows from two or more tables based on related column values. This article covers all five MySQL join types: INNER JOIN (returns only matching rows from both tables), LEFT OUTER JOIN (all rows from the left table, matched rows from the right), RIGHT OUTER JOIN (all rows from the right table, matched rows from the left), FULL OUTER JOIN (all rows from both tables, matched and unmatched &#8211; simulated in MySQL using UNION), and CROSS JOIN (every row from the left table combined with every row from the right, producing a Cartesian product). Each type is explained with working MySQL 8 examples using a sample travel database.<\/strong><\/p>\n\n\n<p><strong>This article is part of Robert Sheldon's continuing series on Learning MySQL. To see all of the items in the series, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/mysql-coding-basics\/\">click here<\/a>.<\/strong><\/p>\n\n\n\n\n<p>Tables in a MySQL database are commonly related to one another, often in multiple ways, and it is only by linking the tables together that you can derive meaningful information from the data. To connect these tables together, you can use the <code>JOIN<\/code> clause, which you include in your <code>SELECT<\/code>, <code>DELETE<\/code>, or <code>UPDATE<\/code> statements. The clause provides a structure for connecting the data from multiple tables, letting you specify which tables to join and under what conditions to join them.<\/p>\n\n\n\n<p>MySQL supports three basic types of joins: inner joins, outer joins, and cross joins. Outer joins can be further broken down to left outer joins and right outer joins. You can also use left and right joins together to create full outer joins. In this article, I explain how to add joins to your <code>SELECT<\/code> statements and provide examples that demonstrate how they work. Each example retrieves data from the <code>manufacturers<\/code> and <code>airplanes<\/code> tables in the <code>travel<\/code> database, which you\u2019ve seen in previous articles in this series.<\/p>\n\n\n\n<p>Note: The examples in this article are based on a local instance of MySQL that hosts a very simple database. The last section of the article\u2014\u201cAppendix: Preparing your MySQL environment\u201d\u2014provides information about how I set up my environment and includes a SQL script for creating the database I used when building these examples.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-mysql-inner-joins\">MySQL inner joins<\/h2>\n\n\n\n<p>An inner join can retrieve matching data from multiple tables based on one or more columns that are common to both tables. For example, the <code>manufacturers<\/code> and <code>airplanes<\/code> tables in the <code>travel<\/code> database each contain the <code>manufacturer_id<\/code> column. You can create an inner join that links the data in the two tables together based on the values in those columns. The join will return all rows with matching <code>manufacturer_id<\/code> values. The following figure shows how you might visualize an inner join between the two tables.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-1.jpeg\" alt=\"\" class=\"wp-image-97918\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The left circle represents the data in the <code>manufacturers<\/code> table, and the right circle represents the data in the <code>airplanes<\/code> table. The area in which they intersect is where the <code>manufacturer_id<\/code> values in the <code>manufacturers<\/code> table are equal to the <code>manufacturer_id<\/code> values in the <code>airplanes<\/code> table.<\/p>\n\n\n\n<p><em>Note: if you are well acquainted with Venn diagrams, you know they generally work with complete sets of data. They help visualize which rows will be returned in the join operation but note that only the column values in the join condition are part of the intersection (for an INNER JOIN operation) and the other combinations that will be included. <\/em><\/p>\n\n\n\n<p>An inner join returns only the matching rows from the two tables and excludes all other rows. You\u2019ll see this in action shortly, but first take a look at the following syntax, which represents a <code>SELECT<\/code> statement and its <code>INNER<\/code> <code>JOIN<\/code> clause at their most basic:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT select_list\nFROM tbl_1 [[AS] tbl_1_alias]\n  [INNER] JOIN tbl_2 [[AS] tbl_2_alias]\n  [ON tbl_1_col = tbl_2_col];<\/pre>\n\n\n\n<p>The first two lines in the syntax are similar to most other <code>SELECT<\/code> statements. They define the <code>SELECT<\/code> clause and the initial part of the <code>FROM<\/code> clause. The third line represents the start of <code>JOIN<\/code> clause, which is actually a subclause in the <code>FROM<\/code> clause. The <code>JOIN<\/code> clause specifies the name of the second table. This is the table that will be joined to the first table.<\/p>\n\n\n\n<p>The fourth line in the syntax shows the <code>ON<\/code> clause. Although the clause is technically optional, it is typically included when defining an inner join because it will behave as if the join criteria is always the Boolean value of <code>True<\/code>. Later in the article, I\u2019ll discuss joins that don\u2019t include the <code>ON<\/code> clause on purpose, but for now, assume that you\u2019ll be including it all of your queries that you expect to be an <code>INNER JOIN<\/code>. The clause defines a search condition that determines how the tables are joined.<\/p>\n\n\n\n<p>The syntax shows how the <code>ON<\/code> clause is commonly structured. It defines a value equality expression which states that a column in the first table must equal a column in the second table. You can create other types of expressions, but for this article, we\u2019ll focus only on value equality expressions because those are the most common.<\/p>\n\n\n\n<p>The following example demonstrates how the various syntax elements plug into an actual <code>SELECT<\/code> statement:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type, a.icao_code\nFROM manufacturers AS m INNER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id;<\/pre>\n\n\n\n<p>After the <code>SELECT<\/code> clause, the statement defines a <code>FROM<\/code> clause, which starts by specifying the <code>manufacturers<\/code> table and assigning it the <code>m<\/code> alias. A table alias can be useful when referencing the table\u2019s individual columns in other clauses, such as the <code>SELECT<\/code> clause (and essential if you need to reference the same table twice). Otherwise, you would need to include the entire table name to ensure that there is no ambiguity between columns from different table. Theoretically, you do not need to qualify a name if it is unique among the joined tables, but many database and development teams consider its inclusion a best practice.<\/p>\n\n\n\n<p>The <code>FROM<\/code> clause then goes on to define the <code>INNER<\/code> <code>JOIN<\/code> clause. It identifies <code>airplanes<\/code> as the second table and assigns it the alias <code>a<\/code>. Next comes the <code>ON<\/code> clause and its search condition, which specifies that the <code>m.manufacturer_id<\/code> values must equal the <code>a.manufacturer_id<\/code> values for the rows to be returned. In other words, the clause limits the statement\u2019s results to only those rows with matching <code>manufacturer_id<\/code> values. The following figure shows the data returned by the <code>SELECT<\/code> statement.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1290\" height=\"530\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-2.png\" alt=\"\" class=\"wp-image-97919\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Because the query returns only those rows with matching <code>manufacturer_id<\/code> values, the results do not include manufacturers in the <code>manufacturers<\/code> table for which there are no matching planes in the <code>airplanes<\/code> table, nor do they include planes in the <code>airplanes<\/code> table for which there are no matching manufacturers in the <code>manufacturers<\/code> table.<\/p>\n\n\n\n<p>Now let\u2019s move on to the <code>AS<\/code> keyword, which the example above uses when defining the table aliases. You do not need to include this keyword. For example, you can recast the previous <code>SELECT<\/code> statement as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type, a.icao_code\nFROM manufacturers m INNER JOIN airplanes a\n  ON m.manufacturer_id = a. manufacturer_id;<\/pre>\n\n\n\n<p>You also do not need to include the <code>INNER<\/code> keyword in your <code>JOIN<\/code> clause. When <code>JOIN<\/code> is used without <code>INNER<\/code>, MySQL assumes that you want to perform an inner join. The following example returns the same results as the previous two <code>SELECT<\/code> statements:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type, a.icao_code\nFROM manufacturers m JOIN airplanes a\n  ON m.manufacturer_id = a. manufacturer_id;<\/pre>\n\n\n\n<p>The fact that you do not need to include the <code>INNER<\/code> keyword indicates that MySQL considers an inner join to be the most natural type of join. In fact, MySQL supports something called a \u201cnatural join,\u201d which you can use in place of an inner join when the compared columns have the same name and datatype, as in the following example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type, a.icao_code\nFROM manufacturers m NATURAL JOIN airplanes a;<\/pre>\n\n\n\n<p>The <code>SELECT<\/code> statement returns the same results as the previous statements even though it does not include an <code>ON<\/code> clause. Be aware, however, that a natural join, unlike an inner join, removes duplicate columns, such as those you get with a <code>SELECT<\/code> <code>*<\/code> query. In this case, the natural join returns only one <code>manufacturer_id<\/code> column, rather than one from each table. In contrast, the inner join returns both columns. Also note that if your tables share other column names (name, row_last_modified_time, for example,) a <code>NATURAL JOIN<\/code> will not work properly.<\/p>\n\n\n\n<p>Note: MySQL joins are a complex topic. This article focuses only on creating joins that combine two tables, basing the joins on a single set of matching columns. While you can only join two tables at a time, you can join more than two tables in a statement, and you can base your joins on more than one set of matching columns. You can also define other types of search criteria in your <code>ON<\/code> clauses (other than value equality). For more information about joins, see the MySQL documentation, starting with the <a href=\"https:\/\/dev.mysql.com\/doc\/refman\/8.0\/en\/join.html\">JOIN Clause<\/a> topic.<\/p>\n\n\n\n<p>The examples that we\u2019ve looked at up to this point have included only a <code>SELECT<\/code> clause and a <code>FROM<\/code> clause, which contained the join definition. You can include other clauses, however, just like you can in any <code>SELECT<\/code> statement. For example, the following <code>SELECT<\/code> statement includes a <code>WHERE<\/code> clause and <code>ORDER<\/code> <code>BY<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type, a.max_weight\nFROM manufacturers AS m INNER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id\nWHERE a.max_weight &lt; 10000\nORDER BY a.max_weight DESC;<\/pre>\n\n\n\n<p>The statement now returns only six rows, which are shown in the following figure. As expected, all the returned data meets the condition defined in the <code>WHERE<\/code> clause.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1388\" height=\"380\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-3.png\" alt=\"\" class=\"wp-image-97920\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can also group and aggregate the data returned by your joined tables. The next <code>SELECT<\/code> statement groups the data based on the <code>manufacturer_id<\/code> values in the <code>manufacturers<\/code> table and then aggregates the data in those groups:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  ROUND(AVG(a.max_weight)) AS avg_weight\nFROM manufacturers AS m INNER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id\nGROUP BY m.manufacturer_id\nORDER BY avg_weight DESC;<\/pre>\n\n\n\n<p>In this case, the <code>SELECT<\/code> clause calculates the average <code>max_weight<\/code> value for the planes associated with each manufacturer and assigns the <code>avg_weight<\/code> alias to the generated column. The following figure shows the results returned by the <code>SELECT<\/code> statement.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"954\" height=\"306\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-4.png\" alt=\"\" class=\"wp-image-97921\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Be aware of the cardinality of rows in your output when you are aggregating data. For example, this was the raw data when we joined the tables together:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1290\" height=\"530\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/a-screenshot-of-a-computer-description-automatica-33.png\" alt=\"A screenshot of a computer\n\nDescription automatically generated\" class=\"wp-image-97922\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em>Note: If you count the number of manufacturers using this set of data, you will get 12, while there are just 4. Generally, the table with the key value you are joining on will be the one that you need to use in the <\/em><code>GROUP BY<\/code><em> clause, and the table referencing that table will be the one that you need to use in aggregate functions like <\/em><code>AVG<\/code><em>, <\/em><code>SUM<\/code><em>, <\/em><code>MIN<\/code><em>, <\/em><code>MAX<\/code><em>, etc. <\/em><\/p>\n\n\n\n<p>MySQL also supports the <code>USING<\/code> clause when defining a join condition, which you use in place of the <code>ON<\/code> clause. You can include the <code>USING<\/code> clause only if the matching columns have the same names and are configured with the same data type. This is useful when the same name is used in both tables, but you have additional columns with the same name as well. The <code>USING<\/code> clause is safer to use than a <code>NATURAL JOIN<\/code> in reusable code because it is not susceptible to new, duplicated columns.<\/p>\n\n\n\n<p>The <code>manufacturer_id<\/code> columns in the <code>manufacturers<\/code> and <code>airplanes<\/code> tables meet the requirement, so you can recast the previous <code>SELECT<\/code> statement as follows:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  ROUND(AVG(max_weight)) AS avg_weight\nFROM manufacturers AS m INNER JOIN airplanes AS a\n  USING (manufacturer_id)\nGROUP BY m.manufacturer_id\nORDER BY avg_weight DESC;<\/pre>\n\n\n\n<p>When specifying the column in the <code>USING<\/code> clause, you must enclose it in parentheses. In addition, you should not qualify the column name with a table name or alias as you do in an <code>ON<\/code> clause. You simply specify the column name.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-mysql-left-and-right-outer-joins\">MySQL left and right outer joins<\/h2>\n\n\n\n<p>In some cases, you might want to retrieve the non-matching data from one of the joined tables, along with the matching data from both tables. To do so, you should define an outer join rather than an inner join.<\/p>\n\n\n\n<p>MySQL supports two types of outer joins: left outer joins and right outer joins. The \u201cleft\u201d refers to the left table specified in the <code>JOIN<\/code> clause, and the \u201cright\u201d refers to the right table specified in the clause. In a left outer join, non-matching rows in the left table are included in the results, and in a right outer join, non-matching rows in the right table are included in the results.<\/p>\n\n\n\n<p>For example, the following figure shows you how you might visualize a left outer join between the <code>manufacturers<\/code> and <code>airplanes<\/code> tables.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-6.jpeg\" alt=\"\" class=\"wp-image-97923\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>As you saw with inner joins, the left circle represents the <code>manufacturers<\/code> table, which is the \u201cleft\u201d table, and the right circle represents the <code>airplanes<\/code> table, which is the \u201cright\u201d table. The area in which they intersect is where the <code>manufacturer_id<\/code> values in both tables match. The join also incorporates the non-matching data in the left table, which is indicated by the solid green.<\/p>\n\n\n\n<p>Creating an outer join works much like an inner join. The following syntax shows a basic <code>SELECT<\/code> statement that includes the <code>LEFT<\/code> <code>OUTER<\/code> <code>JOIN<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT select_list\nFROM tbl_1 [[AS] tbl_1_alias]\n  LEFT [OUTER] JOIN tbl_2 [[AS] tbl_2_alias]\n  [ON tbl_1_col = tbl_2_col];<\/pre>\n\n\n\n<p>The syntax is similar to an inner join except for the use of the <code>LEFT<\/code> keyword and optional <code>OUTER<\/code> keyword. The syntax is meant to show only the most basic elements necessary to perform a left outer join. It does not reflect other clauses that you can add to your <code>SELECT<\/code> statements, such as <code>WHERE<\/code> or <code>GROUP<\/code> <code>BY<\/code>. For example, the following query creates a left outer join with the <code>manufacturers<\/code> table specified as the left table:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, a.plane, \n  a.engine_type, a.max_weight, a.wingspan\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id\nORDER BY a.plane;<\/pre>\n\n\n\n<p>In addition to the <code>JOIN<\/code> clause, the also includes an <code>ORDER<\/code> <code>BY<\/code> clause that sorts the data by the <code>plane<\/code> column, in ascending order. The <code>SELECT<\/code> statement returns the result set shown in the following figure.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1694\" height=\"610\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-7.png\" alt=\"\" class=\"wp-image-97924\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>In addition to the matching rows, the statement returns all rows from the <code>manufacturers<\/code> table for which there is no matching data in the <code>airplanes<\/code> table. You can identify these rows by the <code>NULL<\/code> values in the columns from the <code>airplanes<\/code> table. In this case, the first three rows have no matching data. However, the rest of the rows in the left table match rows in the right table, so those results look much like you saw with the inner joins.<\/p>\n\n\n\n<p>MySQL also lets you include a <code>USING<\/code> clause in your outer join statements, just like you can your inner joins. For example, you can recast the previous <code>SELECT<\/code> statement to include a <code>USING<\/code> clause, which returns the same results:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, a.plane, \n  a.engine_type, a.max_weight, a.wingspan\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  USING (manufacturer_id)\nORDER BY a.plane;<\/pre>\n\n\n\n<p>A right outer join works just like a left outer join except that it returns non-matching rows from the right table rather than the left table, as illustrated in the following figure. Once again, the area in which the two circles intersect is where the <code>manufacturer_id<\/code> values in the two tables match. The solid part of the right circle represents the non-matching data in the right table.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-8.jpeg\" alt=\"\" class=\"wp-image-97925\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>A right outer join is simply the reverse of a left outer join. They can be used interchangeably by reversing the table order. In fact, MySQL documentation recommends that you stick with left joins for all your outer joins: \u201c<code>RIGHT<\/code> <code>JOIN<\/code> works analogously to <code>LEFT<\/code> <code>JOIN<\/code>. To keep code portable across databases, it is recommended that you use <code>LEFT<\/code> <code>JOIN<\/code> instead of <code>RIGHT<\/code> <code>JOIN<\/code>.\u201d If you take this approach, you need only reverse the order of the tables when building your queries.<\/p>\n\n\n\n<p>While generally a good approach, despite the MySQL recommendations there might be times when you need to use a right outer join, such as when you are joining multiple tables, or when you\u2019re constructing a full outer join (which I\u2019ll demonstrate shortly). The following syntax shows a basic <code>SELECT<\/code> statement that includes the <code>RIGHT<\/code> <code>OUTER<\/code> <code>JOIN<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT select_list\nFROM tbl_1 [[AS] tbl_1_alias]\n  RIGHT [OUTER] JOIN tbl_2 [[AS] tbl_2_alias]\n  [ON tbl_1_col = tbl_2_col];<\/pre>\n\n\n\n<p>The syntax differs from the left outer join only in the use of the <code>RIGHT<\/code> keyword rather than the <code>LEFT<\/code> keyword. The <code>SELECT<\/code> statement in the following example demonstrates what this looks like:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id AS man_tbl_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id AS air_tbl_id\nFROM manufacturers AS m \n  RIGHT OUTER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id\nORDER BY a.plane;<\/pre>\n\n\n\n<p>In this example, I included the <code>manufacturer_id<\/code> column from both tables, providing a different alias for each one. This makes it easier to distinguish between the two columns and to see which <code>manufacturer_id<\/code> values exist in the <code>airplanes<\/code> table that do not exist in the <code>manufacturers<\/code> table. The following figure shows the results returned by the <code>SELECT<\/code> statement.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1300\" height=\"702\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-9.png\" alt=\"\" class=\"wp-image-97926\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>As the figure demonstrates, the <code>airplanes<\/code> table contains six non-matching rows, which are indicated by the <code>NULL<\/code> values in the <code>man_tbl_id<\/code> and <code>manufacturer<\/code> columns, both of which come from the <code>manufacturers<\/code> table.<\/p>\n\n\n\n<p><em>Note that the examples for this article are not necessarily best practices in database design. However, to demonstrate some of the different join types, it was necessary to have rows in each table that did not relate to one another.<\/em><\/p>\n\n\n\n<p>In some cases, you might need to perform an outer join that returns only the non-matching rows in one of the tables. For example, you might want to know which rows in the <code>airplanes<\/code> table reference <code>manufacturer_id<\/code> values that do not exist in the <code>manufacturers<\/code> table, as illustrated in the following figure.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-10.jpeg\" alt=\"\" class=\"wp-image-97927\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can retrieve this information by adding a <code>WHERE<\/code> clause to your <code>SELECT<\/code> statement that checks the data for nullability. In effect, you\u2019re taking advantage of the <code>NULL<\/code> values returned by your query when there are no corresponding matches. For example, the following <code>SELECT<\/code> statement is the same as the previous one except that it includes a <code>WHERE<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers AS m \n  RIGHT OUTER JOIN airplanes AS a\n  ON m.manufacturer_id = a. manufacturer_id\nWHERE m.manufacturer IS NULL\nORDER BY a.plane;<\/pre>\n\n\n\n<p>The <code>WHERE<\/code> clause uses the <code>IS<\/code> <code>NULL<\/code> operator to check whether the <code>manufacturer_id<\/code> column contains a <code>NULL<\/code> value. If it does, the expression evaluates to true and the row is returned, giving us the results shown in the following figure.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"938\" height=\"362\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-11.png\" alt=\"\" class=\"wp-image-97928\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Checking for <code>NULL<\/code> values in this way provides a handy method for identifying what might be anomalous data, which could be the result of a bulk-loading operation, merging tables from a legacy system, a bug at the application or data level, or some other process.<\/p>\n\n\n\n<p>Where filtering by <code>NULL<\/code> finds the values that don&#8217;t match, be careful about filtering rows in the <code>WHERE<\/code> clause unless you realize what you are doing. For example, executing the following:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, a.plane, \n  a.engine_type, a.max_weight, a.wingspan\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  USING (manufacturer_id)\nWHERE a.manufacturer_id = 104 -- where clause filter\nORDER BY a.plane;<\/pre>\n\n\n\n<p>Will just return the rows with <code>manufacturer = 104<\/code>. This, in effect, turns this into an <code>INNER JOIN<\/code>, because all of the mismatched rows from the <code>LEFT OUTER JOIN<\/code> will be eliminated because their <code>manufacturer_id<\/code> is <code>NULL<\/code> and hence, does not equal <code>104<\/code>. However, change your join to:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, a.plane, \n  a.engine_type, a.max_weight, a.wingspan\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  ON m.manufacturer_id = a.manufacturer_id\n     AND a.manufacturer_id = 104\nORDER BY a.plane;<\/pre>\n\n\n\n<p>And the output is changed again, but perhaps not exactly what you wanted as now every manufacturer is returned, but only rows that match the <code>a.manufacturer_id = 104<\/code> predicate are returned.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"496\" height=\"183\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/a-screenshot-of-a-computer-description-automatica-34.png\" alt=\"A screenshot of a computer\n\nDescription automatically generated\" class=\"wp-image-97929\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-mysql-full-outer-joins\">MySQL full outer joins<\/h2>\n\n\n\n<p>A full outer join returns all data from the two connected tables. In addition to the matching rows from both tables, the join returns unmatched rows in the left table and unmatched rows in the right table, as illustrated in the following figure. In this case, the result set includes manufacturers with no corresponding planes in the <code>airplanes<\/code> table and planes with no corresponding manufacturers in the <code>manufacturing<\/code> table, along with the matching data from both tables.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-13.jpeg\" alt=\"\" class=\"wp-image-97930\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>MySQL does not support full outer joins like some database systems. You can get around this by using a <code>UNION<\/code> operator to join a left outer join statement and a right outer join statement, as in the following example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  USING (manufacturer_id)\nUNION ALL\nSELECT m.manufacturer, a.plane, a.engine_type\nFROM manufacturers AS m \n  RIGHT OUTER JOIN airplanes AS a\n  USING (manufacturer_id);<\/pre>\n\n\n\n<p>The individual <code>SELECT<\/code> statements work just like the earlier examples of left and right outer joins. The <code>UNION<\/code> <code>ALL<\/code> operator joins the two statements together to return a single result set, which is shown in the following figure.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1140\" height=\"1150\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-14.png\" alt=\"\" class=\"wp-image-97931\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The results include both matching rows and non-matching rows, which are indicated by the <code>NULL<\/code> values. However, because the two statements are joined together, the results also include duplicate rows. For example, the Airbus planes are each listed twice, as are all the other planes. You can eliminate the duplicates by using the <code>UNION<\/code> operator without the <code>ALL<\/code> qualifier, as in the following example:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer, a.plane, a.engine_type\nFROM manufacturers AS m \n  LEFT OUTER JOIN airplanes AS a\n  USING (manufacturer_id)\nUNION\nSELECT m.manufacturer, a.plane, a.engine_type\nFROM manufacturers AS m \n  RIGHT OUTER JOIN airplanes AS a\n  USING (manufacturer_id);<\/pre>\n\n\n\n<p>Now the statement returns only distinct rows, whether the data is matching or non-matching, as shown in the following figure:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1128\" height=\"782\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-15.png\" alt=\"\" class=\"wp-image-97932\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Although MySQL doesn\u2019t provide a specific clause for creating full outer joins, such as you\u2019ll find in SQL Server or other database systems, there might be times when you need a more complete picture than what either a left outer join or right outer join can provide on its own. Using a <code>UNION<\/code> operator to combine the two outer joins is a handy way to accomplish this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-mysql-cross-joins\">MySQL cross joins<\/h2>\n\n\n\n<p>Another type of join that MySQL supports is the cross join, which matches each row in the left table to each row in the right table to produce what is referred as a <em>Cartesian product<\/em>. The following figure illustrates a cross join between the <code>manufacturers<\/code> table and the <code>airplanes<\/code> table.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1129\" height=\"829\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-16.jpeg\" alt=\"\" class=\"wp-image-97933\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>The figure shows the various ways that each value pair can be matched. For example, the <code>101<\/code> value in the <code>manufacturers<\/code> table is matched to every value in the <code>airplanes<\/code> table, and the <code>1001<\/code> value in the <code>airplanes<\/code> table is matched to every value in the <code>manufacturers<\/code> table. This process continues for each value in both tables until all values have been matched up. The following syntax shows a basic <code>SELECT<\/code> statement that includes the <code>CROSS<\/code> <code>JOIN<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT select_list\nFROM tbl_1 [[AS] tbl_1_alias]\n  CROSS JOIN tbl_2 [[AS] tbl_2_alias]\n  [ON tbl_1_col = tbl_2_col];<\/pre>\n\n\n\n<p>Except for the <code>CROSS<\/code> keyword, the syntax elements should all look familiar to you. That said, the cross join is not as clear-cut as it might appear. Notice that the syntax, like the previous join examples, indicates that the <code>ON<\/code> clause is optional. A cross join statement returns a Cartesian product only if it does not include this clause. If the clause is included, the statement behaves much like a regular inner join.<\/p>\n\n\n\n<p>Some sources imply that a MySQL cross join is strictly limited to producing a Cartesian product, but this is clearly not the case, as indicated by its support for the <code>ON<\/code> clause (or <code>USING<\/code> clause). According to MySQL documentation, \u201c<code>JOIN<\/code>, <code>CROSS<\/code> <code>JOIN<\/code>, and <code>INNER<\/code> <code>JOIN<\/code> are syntactic equivalents (they can replace each other). In standard SQL, they are not equivalent. <code>INNER<\/code> <code>JOIN<\/code> is used with an <code>ON<\/code> clause, <code>CROSS<\/code> <code>JOIN<\/code> is used otherwise.\u201d<\/p>\n\n\n\n<p>Let\u2019s look at a few examples to better understand how the cross join works. The following <code>SELECT<\/code> statement defines a cross join without including an <code>ON<\/code> clause or <code>USING<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers AS m \n  CROSS JOIN airplanes AS a\nORDER BY m.manufacturer_id, a.plane_id;<\/pre>\n\n\n\n<p>The statement returns a Cartesian product for the <code>manufacturers<\/code> and <code>airplanes<\/code> tables. The <code>manufacturers<\/code> table contains seven rows, and the <code>airplanes<\/code> table contains 18 rows. Because a cross join matches each row in the first table to each row in the second table, you can multiply the two row counts together (7 x 18) to calculate the total number of rows that should be included in the Cartesian product. In this case, the total is 126, which is the number of rows returned by the <code>SELECT<\/code> statement.<\/p>\n\n\n\n<p>The following figure shows a small portion of these results. Notice that there is an Airbus row for each plane, even if that plane is not manufactured by Airbus. The same is true for all manufacturers. The cross join simply combines the individual rows together.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1506\" height=\"994\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-17.png\" alt=\"\" class=\"wp-image-97934\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>This in itself should seem straightforward enough, and by all accounts, this is how you should use the cross join\u2014to return a Cartesian product. However, because <code>CROSS<\/code> <code>JOIN<\/code> and <code>INNER<\/code> <code>JOIN<\/code> are syntactic equivalents, you can achieve the same results with an inner join that does not include an <code>ON<\/code> clause or <code>USING<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers AS m \n  INNER JOIN airplanes AS a\nORDER BY m.manufacturer_id, a.plane_id;<\/pre>\n\n\n\n<p>The statement returns the same Cartesian product as the previous example. However, this is not the only way to return these results. You can instead specify the two table names, separated by a comma, and drop the <code>JOIN<\/code> clause altogether:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers m, airplanes a\nORDER BY m.manufacturer_id, a.plane_id;<\/pre>\n\n\n\n<p>Once again, you\u2019ll end up with the same Cartesian product that was returned by the previous two examples.<\/p>\n\n\n\n<p><em>Note: MySQL documentation warns that the precedence of a comma operator is less than the actual <\/em><code><em>JOIN<\/em><\/code><em> keywords. This might be an issue if you mix join types in a statement that combines more than two tables. <\/em><\/p>\n\n\n\n<p>In addition, the comma operator can be used only to generate a Cartesian product. It cannot be used with an <code>ON<\/code> or <code>USING<\/code> clause. This is not the case for a cross join, which can include either of these clauses, just like an inner join. The following examples shows a cross join that contains a <code>USING<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers AS m \n  CROSS JOIN airplanes AS a\n  USING (manufacturer_id)\nORDER BY m.manufacturer_id;<\/pre>\n\n\n\n<p>By adding the <code>USING<\/code> clause, the statement now returns only 12 rows, rather than 126. The results are shown in the following figure.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1522\" height=\"526\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2023\/08\/word-image-97917-18.png\" alt=\"\" class=\"wp-image-97935\"\/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>You can return the same results with the following inner join statement, which also incorporates the <code>USING<\/code> clause:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">SELECT m.manufacturer_id, m.manufacturer, \n  a.plane_id, a.plane, a.manufacturer_id\nFROM manufacturers AS m \n  INNER JOIN airplanes AS a\n  USING (manufacturer_id)\nORDER BY m.manufacturer_id;<\/pre>\n\n\n\n<p>Despite the fact that <code>CROSS<\/code> <code>JOIN<\/code> and <code>INNER<\/code> <code>JOIN<\/code> are syntactic equivalents, the general consensus is to use cross joins when you want to work directly with the Cartesian product and use inner joins when you want to qualify the join with an <code>ON<\/code> or <code>USING<\/code> clause. This doesn\u2019t preclude the use of other <code>SELECT<\/code> clauses in your cross join statements (such as <code>WHERE<\/code> or <code>ORDER<\/code> <code>BY<\/code>), but it does serve as general guideline for differentiating between the two when you want to generate a Cartesian product.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-getting-started-with-mysql-joins\">Getting started with MySQL joins<\/h2>\n\n\n\n<p>The topic of MySQL joins is a complex one, as I noted earlier. A single article is not nearly enough to cover all the various ways you can use joins to combine data from multiple tables. Although the article provides you with a jumping-off point, it by no means covers every aspect of join creation. You can, for example, combine different types of joins into a single query, define joins in your <code>DELETE<\/code> and <code>UPDATE<\/code> statements, join three or more tables in a single statement, or base your joins on two or more sets of matching columns. To do all this, however, you need a good foundation on which to build, and this article might help you get started with that process.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-appendix-preparing-your-mysql-environment\">Appendix: Preparing your MySQL environment<\/h2>\n\n\n\n<p>For the examples for this article, I used a Mac computer that was set up with a local instance of MySQL 8.0.29 (Community Server edition). I also used MySQL Workbench to interface with MySQL. Through Workbench, I created the <code>travel<\/code> database, added the <code>manufacturers<\/code> and <code>airplanes<\/code> tables, and inserted test data into the tables.<\/p>\n\n\n\n<p>If you want to try out the examples for yourself, start by running the following script against your MySQL instance:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">DROP DATABASE IF EXISTS travel;\nCREATE DATABASE travel;\nUSE travel;\nCREATE TABLE manufacturers (\n  manufacturer_id INT UNSIGNED NOT NULL,\n  manufacturer VARCHAR(50) NOT NULL,\n  create_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  last_update TIMESTAMP NOT NULL \n    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (manufacturer_id) );\nCREATE TABLE airplanes (\n  plane_id INT UNSIGNED NOT NULL,\n  plane VARCHAR(50) NOT NULL,\n  manufacturer_id INT UNSIGNED NOT NULL,\n  engine_type VARCHAR(50) NOT NULL,\n  engine_count TINYINT NOT NULL,\n  max_weight MEDIUMINT UNSIGNED NOT NULL,\n  wingspan DECIMAL(5,2) NOT NULL,\n  plane_length DECIMAL(5,2) NOT NULL,\n  parking_area INT GENERATED ALWAYS AS ((wingspan * plane_length)) STORED,\n  icao_code CHAR(4) NOT NULL,\n  create_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  last_update TIMESTAMP NOT NULL \n    DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  PRIMARY KEY (plane_id));<\/pre>\n\n\n\n<p>The script creates the <code>travel<\/code> database and adds the <code>manufacturers<\/code> and <code>airplanes<\/code> tables. In previous articles, I had defined a primary key on the <code>airplanes<\/code> table that referenced the <code>manufacturers<\/code> table. For this article, I did not include the foreign key so it would be easier to demonstrate various join operations.<\/p>\n\n\n\n<p>After you create the tables, you should run the following <code>INSERT<\/code> statements:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">INSERT INTO manufacturers (manufacturer_id, manufacturer)\nVALUES (101,'Airbus'), (102,'Beagle Aircraft Limited'), (103,'Beechcraft'),\n  (104,'Boeing'), (105,'Cessna'), (106,'Embraer'), (107,'Gulfstream');\nINSERT INTO airplanes \n  (plane_id, plane, manufacturer_id, engine_type, engine_count, \n    wingspan, plane_length, max_weight, icao_code)\nVALUES\n  (1001,'A340-600',101,'Jet',4,208.17,247.24,837756,'A346'),\n  (1002,'A350-800 XWB',101,'Jet',2,212.42,198.58,546700,'A358'),\n  (1003,'A350-900',101,'Jet',2,212.42,219.16,617295,'A359'),\n  (1004,'A.109 Airedale',102,'Piston',1,36.33,26.33,2750,'AIRD'),\n  (1005,'A.61 Terrier',102,'Piston',1,36,23.25,2400,'AUS6'),\n  (1006,'B.121 Pup',102,'Piston',1,31,23.17,1600,'PUP'),\n  (1007,'Baron 56 TC Turbo Baron',103,'Piston',2,37.83,28,5990,'BE56'),\n  (1008,'Bonanza 33 (F33A)',103,'Piston',1,33.5,26.67,3500,'BE33'),\n  (1009,'Bonanza 35 (G35)',103,'Piston',1,32.83,25.17,3125,'BE35'),\n  (1010,'747-8F',104,'Jet',4,224.42,250.17,987000,'B748'),\n  (1011,'747-SP',104,'Jet',4,195.67,184.75,696000,'B74S'),\n  (1012,'757-300',104,'Jet',2,124.83,178.58,270000,'B753'),\n  (1013,'PA-28R-200 Cherokee Arrow',121,'Piston',1,30.00,23.50,2600,'P28R'),\n  (1014,'PA-18-150 Super Cub',121,'Piston',1,35.29,22.50,1750,'PA18'),\n  (1015,'PA-24-180 Comanche',121,'Piston',1,36.00,24.79,2550,'PA24'),\n  (1016,'M20D Master',136,'Piston',1,35.00,23.25,2500,'M20P'),\n  (1017,'M20F Executive 21',136,'Piston',1,36.00,24.00,2740,'M20P'),\n  (1018,'M20L PFM',136,'Piston',1,36.42,26.75,2900,'M20P');<\/pre>\n\n\n\n<p>The <code>INSERT<\/code> statements first populate the <code>manufacturers<\/code> table and then the <code>airplanes<\/code> table. The statement for each table intentionally includes data without corresponding records in the other table. For example, the <code>manufacturers<\/code> table includes manufacturers with no products in the <code>airplanes<\/code> table, and the <code>airplanes<\/code> table includes products for manufacturers that do not exist in the <code>manufacturers<\/code> table. These inclusions will help us test various types of join conditions.<\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: MySQL joins<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is the difference between INNER JOIN and OUTER JOIN in MySQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>INNER JOIN returns only rows where there is a match in both tables. If a row in the left table has no matching row in the right table (or vice versa), it is excluded from the result. OUTER JOINs include non-matching rows: LEFT OUTER JOIN (or LEFT JOIN) returns all rows from the left table and matched rows from the right &#8211; unmatched right-table columns appear as NULL. RIGHT OUTER JOIN returns all rows from the right table and matched rows from the left. FULL OUTER JOIN returns all rows from both tables (not natively supported in MySQL &#8211; simulate with UNION of LEFT and RIGHT joins).<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. When should I use a LEFT JOIN instead of an INNER JOIN in MySQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use LEFT JOIN when you want to include all rows from the left (primary) table even if they have no matching rows in the right table. Common use cases: finding customers who have no orders (WHERE right_table.id IS NULL after a LEFT JOIN), retrieving all products whether or not they have associated inventory records, or reporting across tables where the relationship is optional rather than required. Use INNER JOIN when you only want rows with a confirmed relationship in both tables and have no interest in unmatched rows.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do I do a FULL OUTER JOIN in MySQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>MySQL does not natively support FULL OUTER JOIN syntax (unlike PostgreSQL and SQL Server). Simulate it with UNION: SELECT * FROM table_a LEFT JOIN table_b ON table_a.id = table_b.id UNION SELECT * FROM table_a RIGHT JOIN table_b ON table_a.id = table_b.id; UNION removes duplicates between the two result sets, giving the equivalent of a full outer join. UNION ALL would include duplicate matching rows from both queries &#8211; use UNION (deduplicating) for the correct FULL OUTER JOIN equivalent.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What is a CROSS JOIN in MySQL and when is it useful?<\/h3>\n            <div class=\"faq-answer\">\n                <p>A CROSS JOIN returns every combination of rows from two tables &#8211; also called a Cartesian product. If table_a has 5 rows and table_b has 4 rows, the CROSS JOIN returns 20 rows. Use CROSS JOIN when you intentionally want all combinations: generating a combination matrix, creating a calendar of all date\/time combinations, or testing with all possible parameter pairings. Avoid accidental CROSS JOINs from forgotten JOIN conditions &#8211; they can return millions of rows and cause significant performance issues.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>A complete guide to MySQL joins: INNER JOIN for matching rows, LEFT and RIGHT OUTER JOINs for non-matching rows, FULL OUTER JOIN simulation, and CROSS JOIN for Cartesian products &#8211; with T-SQL examples and setup scripts.&hellip;<\/p>\n","protected":false},"author":221841,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[53,145792],"tags":[],"coauthors":[6779],"class_list":["post-97917","post","type-post","status-publish","format-standard","hentry","category-featured","category-mysql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/97917","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221841"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=97917"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/97917\/revisions"}],"predecessor-version":[{"id":109832,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/97917\/revisions\/109832"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=97917"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=97917"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=97917"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=97917"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}