{"id":107569,"date":"2025-08-26T11:00:00","date_gmt":"2025-08-26T11:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=107569"},"modified":"2026-04-14T09:39:55","modified_gmt":"2026-04-14T09:39:55","slug":"the-poor-neglected-full-outer-join","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/the-poor-neglected-full-outer-join\/","title":{"rendered":"Unlocking the Power of FULL OUTER JOIN in SQL: Performance, Use Cases &amp; Examples"},"content":{"rendered":"\n<p><strong><code>FULL OUTER JOIN<\/code> returns every row from both tables in a join &#8211; matching rows where keys align and filling unmatched columns with <code>NULL<\/code> values where they don&#8217;t. Unlike <code>INNER JOIN<\/code>, which returns only rows with a match on both sides, or <code>LEFT JOIN<\/code>, which prioritizes one table, <code>FULL OUTER JOIN<\/code> gives you a complete picture of both datasets simultaneously. It is the right tool when merging partially-overlapping data, building exception reports to identify missing records, or tracking changes across two snapshots of the same dataset.<\/strong><\/p>\n\n\n\n<p>The <code>JOIN<\/code> statement is one of the most common operations <a href=\"https:\/\/aws.amazon.com\/what-is\/sql\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL<\/a> developers perform. Yet in a world ruled by<a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/postgresql\/understanding-sql-join-types\/\" target=\"_blank\" rel=\"noreferrer noopener\"> Inner and Left Joins,<\/a> the poor Full Outer Join is like Cinderella before the ball \u2013 allowed out on only the rarest of occasions. In an (unscientific) survey I once performed of SQL developers at one <a href=\"https:\/\/www.50pros.com\/fortune500\" target=\"_blank\" rel=\"noreferrer noopener\">Fortune 100<\/a> firm, half of the developers had never used a Full Outer Join in production code, and the rest had used it in a tiny handful of occasions.<\/p>\n\n\n\n<p>The <code>FULL OUTER JOIN<\/code> (hereafter &#8216;FOJ&#8217;) is generally considered as a tool for rare corner cases such as&nbsp;comparing or merging different table versions, but the objective of this article is to show that its uses go beyond this, even as far as optimizing query performance in cases that most people would never consider. The FOJ is definitely an underappreciated tool. Cinderella has some tricks up her sleeve.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-understanding-sql-join-types\">Understanding SQL Join Types<\/h2>\n\n\n\n<p>Let&#8217;s start with a quick review of the various join types \u2026 and how better to do that than with Venn diagrams? The image below illustrates a basic key value join between two tables:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"483\" height=\"456\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-7.png\" alt=\"A basic key value join between two tables.\" class=\"wp-image-107570\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-7.png 483w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-7-300x283.png 300w\" sizes=\"auto, (max-width: 483px) 100vw, 483px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>An <code>INNER JOIN<\/code> on two tables returns all values common between them. <em>(The &#8216;values&#8217; in this case are those specified in the join condition).<\/em><\/p>\n\n\n\n<p>A <code>LEFT JOIN<\/code> adds non-matching row from Table A, whereas a <code>RIGHT JOIN<\/code> yields the mirror image of that. Finally, the <code>FULL OUTER JOIN<\/code> returns everything: all rows from both tables are included, whether or not they match.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-when-to-use-full-outer-join\"><strong>When to Use FULL OUTER JOIN<\/strong><\/h2>\n\n\n\n<p>The most common use of the FOJ is in merging tables that contain some degree of overlap. Let&#8217;s consider a simple example: a school system database holds parents in one table and teachers in another. We want to combine these into an email list containing both &#8212; but some parents are also teachers, and some teachers are also parents. Because some rows exist in both tables, we can&#8217;t simply <code>UNION<\/code> the two result sets together. A <code>UNION DISTINCT<\/code> might work, but not only is this expensive performance-wise for larger tables, it requires that all retrieved columns be identical, which may not be the case.<\/p>\n\n\n\n<p>With a FOJ, though, this becomes easy. Our (unrealistically simple) example tables look like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE teachers (\n   email varchar(256), \n   workphone varchar(256) \n);\nCREATE TABLE parents( \n   email varchar(256), \n   cellphone varchar(256) \n);\n<\/pre><\/div>\n\n\n\n<p>We&#8217;ll assume two contacts are identical if their emails match, so our join condition is on email. We also want a contact phone number, which for teachers should be their work number, and for parents, their cell number:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT t.workphone, t.email, p.email, p.cellphone\nFROM Teachers t\nFULL OUTER JOIN Parents p ON t.email = p.email\n<\/pre><\/div>\n\n\n\n<p><em>(If you wish to experiment with this yourself, Appendix I contains a script that creates these tables and loads sample data)<\/em>.<\/p>\n\n\n\n<p>Structuring the query this way works, but gives a lot of null values &#8212; any row that doesn&#8217;t exist in both tables will contain nulls on one side:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"970\" height=\"205\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-8.png\" alt=\"Any row that doesn't exist in both tables will contain NULLs on one side.\" class=\"wp-image-107571\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-8.png 970w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-8-300x63.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-8-768x162.png 768w\" sizes=\"auto, (max-width: 970px) 100vw, 970px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>If this is a problem, we can eliminate it by using <code>COALESCE<\/code> to &#8220;fold together&#8221; the left and right halves of the result set:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT COALESCE(t.workphone,p.cellphone), COALESCE(t.email, p.email)\nFROM Teachers t\nFULL OUTER JOIN Parents p ON t.email = p.email\n<\/pre><\/div>\n\n\n\n<p>This yields a much more compact result set:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"491\" height=\"209\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-9.png\" alt=\"Using COALESCE to 'fold together' the left and right halves of the result set, yields a much more compact results set.\" class=\"wp-image-107572\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-9.png 491w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-9-300x128.png 300w\" sizes=\"auto, (max-width: 491px) 100vw, 491px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Fast, simple and concise: how can you not appreciate the <code>FULL OUTER JOIN<\/code>?<br><\/p>\n\n\n\n<section id=\"my-first-block-block_fc166de10c17e510974d5d6207fe964e\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">The fast and easy way to write, format and debug SQL<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            SQL Prompt, now with AI capabilities,  works alongside you to make your SQL coding fast, accurate and effortless.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/products\/sql-prompt\/\" class=\"btn btn--secondary btn--lg\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-real-world-examples-of-full-outer-join-in-action\"><strong>Real-World Examples of FULL OUTER JOIN in Action<\/strong><\/h2>\n\n\n\n<p>Now let&#8217;s look at a problem for which you might not consider a FOJ. Imagine a table of vehicles held in an extended-stay parking lot. Once per day an attendant goes through the lot and inventories all the vehicles, recording the date and tag number. Our table looks like this:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE parked_vehicles (\n  day          DATE,\n  veh_tag_no   VARCHAR(32) \n);\n<\/pre><\/div>\n\n\n\n<p>The task is to write a query that returns all the vehicles that either entered or exited the lot today.&nbsp; In other words, we want two sets of rows:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>All vehicles present yesterday who are not there today<\/li>\n\n\n\n<li>All vehicles present today who were not there yesterday<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The problem, of course, is that we lack vehicle enter and exit dates \u2013 they must be calculated from changes in the data. A simple problem &#8230; but how best do we solve it?<\/p>\n\n\n\n<p>Being tidy, structured SQL programmers, we begin the query with two <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-server-cte-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">Common Table Expressions (CTEs)<\/a>, one defining the &#8220;present yesterday&#8221; and the other the &#8220;present today&#8221; list:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">WITH yesterday AS (\nSELECT * FROM parked_vehicles WHERE day = CURRENT_DATE - '1 DAY'::INTERVAL\n),\ntoday AS (\nSELECT * FROM parked_vehicles WHERE day = CURRENT_DATE\n)\n{...query continues \u2026.}\n<\/pre><\/div>\n\n\n\n<p>Now what? We want a join that gives us all the rows from both tables <em>except <\/em>where they overlap. In Venn Diagram terms, we want this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"343\" height=\"238\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-10.png\" alt=\"In Venn Diagram terms, we want this.\" class=\"wp-image-107573\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-10.png 343w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-10-300x208.png 300w\" sizes=\"auto, (max-width: 343px) 100vw, 343px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p>Using the canonical set operator<code> EXCEPT<\/code>, we would complete the query as:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT veh_tag_no FROM yesterday\n   EXCEPT SELECT veh_tag_no FROM today\nUNION ALL (\nSELECT veh_tag_no FROM today\n  EXCEPT SELECT veh_tag_no FROM yesterday );\n<\/pre><\/div>\n\n\n\n<p>This works, but is very slow, as it must make two passes through the table then combine the results. <em>(Note: since the two <code>EXCEPTs<\/code> are mutually exclusive, we can use <code>UNION ALL<\/code> rather than the slower <code>UNION DISTINCT<\/code>).<\/em> <\/p>\n\n\n\n<p>Also, with this syntax we can directly select only the vehicle tag number &#8212; to get the day column, we need to wrap the <code>EXCEPTs<\/code> in a join, making the query not only cumbersome, but hard to read as well.<\/p>\n\n\n\n<p>Many developers would use an alternate syntax: maintaining the union, but replacing the <code>EXCEPTs<\/code> with a <code>NOT EXISTS<\/code> subquery:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT * FROM yesterday y\n  WHERE NOT EXISTS (SELECT 1 FROM today t WHERE t.veh_tag_no = y.veh_tag_no)\nUNION ALL (\nSELECT * FROM today t\n  WHERE NOT EXISTS (SELECT 1 FROM yesterday y WHERE y.veh_tag_no = t.veh_tag_no) );\n<\/pre><\/div>\n\n\n\n<p>Again, it works, but is no faster than our first try &#8212; most SQL dialects will produce the same query plan for this as the one above. If our tables are large, a two-pass solution can be unacceptably slow.<\/p>\n\n\n\n<p><code>FULL OUTER JOIN<\/code> to the rescue! Look again at our Venn diagram above. What we want is a query that returns Section A and B only, excluding the Section C overlap. We can do this in a single pass using a <code>FULL OUTER JOIN<\/code>, with a <code>WHERE<\/code> clause to exclude the overlapping rows:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">\u2026SELECT *\nFROM yesterday y\nFULL OUTER JOIN today t ON y.veh_tag_no = t.veh_tag_no\n  WHERE y.veh_tag_no IS NULL or t.veh_tag_no IS NULL\n<\/pre><\/div>\n\n\n\n<p>As we saw above, a simple <code>SELECT<\/code> * returns two sets of columns: one from each table, and one of which will always be <code>NULL<\/code>. If that&#8217;s inconvenient, we can use the same &#8220;folding&#8221; technique to eliminate the null values:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">\u2026SELECT COALESCE(y.day,t.day), COALESCE(y.veh_tag_no,t.veh_tag_no)\nFROM yesterday y\nFULL OUTER JOIN today t ON y.veh_tag_no = t.veh_tag_no\nWHERE y.veh_tag_no IS NULL or t.veh_tag_no IS NULL\n<\/pre><\/div>\n\n\n\n<p>Using the random data from the script in Appendix I, this executes nearly twice as fast in <a href=\"https:\/\/www.microsoft.com\/en-gb\/sql-server\/sql-server-downloads\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server<\/a> as the <code>UNION<\/code> approaches above &#8212; a significant improvement. The execution plans for each query reveal why: the FOJ query performs a single hash join on the table to itself, whereas the <code>UNION<\/code> query must perform this hash join twice (scanning our base table four times in total), then merge the results:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"647\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-11-1024x647.png\" alt=\"Query 1 (Union of Excepts) versus Query 2 (Full Outer Join).\" class=\"wp-image-107575\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-11-1024x647.png 1024w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-11-300x189.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-11-768x485.png 768w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-11.png 1039w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><a id=\"_msocom_1\"><\/a><\/p>\n\n\n\n<p>The amount of data retrieved at each step, along with the presence of indexes, etc, will impact the actual performance difference &#8211; but there should be no case where the FOJ is slower than a <code>UNION<\/code>. Simpler is always better.<\/p>\n\n\n\n<section id=\"my-first-block-block_5ad6ca942ab12ac82746491829f8e851\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Enjoying this article?<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Subscribe to the Simple Talk newsletter to get selected articles, event information, podcasts and other industry content delivered straight to your inbox every two weeks.                                    <\/div>\n            <\/div>\n                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-exception-reporting\"><strong>Exception Reporting<\/strong><\/h2>\n\n\n\n<p>Another case where the FOJ is useful is in identifying broken or missing links in data. For our last example, consider a pair of tables in an auto enthusiasts&#8217; database, to match owners to the vehicles they possess:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE owners (\nowner_id INT PRIMARY KEY,\nowner_name VARCHAR(64)\n);\nCREATE TABLE autos (\nauto_id INT PRIMARY KEY,\nowner_id INT NOT NULL,\ndescrip VARCHAR(64)\n);\n<\/pre><\/div>\n\n\n\n<p>To be valid, each owner must have at least one registered vehicle, and each auto must also have an owner. Normally, referential integrity like this would be enforced via a database constraint, but let&#8217;s assume this didn&#8217;t happen for some reason, such as the data being imported from an external source. Our task is to write a query that returns both error types: owners without vehicles, and vehicles without owners.<\/p>\n\n\n\n<p>This is another case where your first instinct likely is to be to <code>UNION<\/code> together the results of two queries: one for each error type. But again, a FOJ is faster and simpler. We&#8217;ll use a <code>WHERE<\/code> clause to include only the error cases, and, as we did above, use the <code>COALESCE<\/code> function to eliminate nulls. The actual query is very simple:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">SELECT\n COALESCE(a.auto_id, o.owner_id) AS \"ID\",\n COALESCE('Missing Owner: ' || a.descrip, 'No Vehicles: ' || o.owner_name) AS \"Error\"\nFROM owners o\nFULL OUTER JOIN autos a ON o.owner_id = a.owner_id\n WHERE a.auto_id IS NULL or o.owner_id IS NULL\n<\/pre><\/div>\n\n\n\n<p>And on our small batch of test data, it yields the following output:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"459\" height=\"181\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-12.png\" alt=\"We've used the COALESCE function to eliminate nulls and, on our small batch of test data, it yields the output shown in the image.\" class=\"wp-image-107576\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-12.png 459w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2025\/08\/image-12-300x118.png 300w\" sizes=\"auto, (max-width: 459px) 100vw, 459px\" \/><\/figure>\n\n\n\n<p><\/p>\n\n\n\n<p><em><strong>Note:<\/strong> this is the first time in this article we&#8217;ve had a 1:M (one to many) relationship between joined tables. That&#8217;s fine: FOJ works perfectly well in this scenario. If that\u2019s hard to visualize, think of a FOJ as a <code>LEFT<\/code> and <code>RIGHT<\/code> join combined: it returns all rows meeting the <code>JOIN<\/code> condition, as well as non-matched rows from both tables participating in the join.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-conclusion-give-full-outer-join-a-chance\"><strong>Conclusion<\/strong>: Give FULL OUTER JOIN a Chance<\/h2>\n\n\n\n<p><code>FULL OUTER JOIN<\/code> isn&#8217;t simply some historical anomaly of SQL, included only for completeness. Cinderella&#8217;s shoe may have only fit one foot, but the FOJ has many use cases for which it is indisputably the best tool for the job. It deserves more respect &#8212; and use &#8212; than it gets.<\/p>\n\n\n\n<p>To tease Part II of this series, note that our Venn diagrams above apply only for joins where the join condition contains an equal sign. When we join on a different operator &#8212; the so-called &#8220;non-equi join&#8221; &#8212; the staid, conservative <code>JOIN<\/code> statement breaks out the tequila and gets wild.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-appendix-i-sample-script-sql-server-format\">Appendix I &#8211; Sample Script (SQL Server Format)<\/h2>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">-- ----------------------------------------------\n-- Pt 1: Merging Data\n-- ---------------------------------------------- \n\n-- Create Tables\nCREATE TABLE teachers (\n   email varchar(256), \n   workphone varchar(256) \n);\nCREATE TABLE parents( \n   email varchar(256), \n   cellphone varchar(256) \n);\n\n-- Insert sample data\nINSERT INTO teachers VALUES ('deweyfinn@zzzmail.com', '201-555-1594');\nINSERT INTO teachers VALUES ('gabrielkotter@nulldomain.com', '207-555-8627');\nINSERT INTO parents VALUES ('PollyParent@cia.gov', '409-555-3109');\nINSERT INTO parents VALUES ('parentofthree@whitehouse.gov', '741-555-6590');\nINSERT INTO teachers VALUES ('teacherandparent@nulldomain.org', '303-555-8765');\nINSERT INTO parents VALUES ('teacherandparent@nulldomain.org', '303-555-8765');\n\n-- test query (standard version)\nselect t.workphone, t.email, p.email, p.cellphone\nFROM teachers t\nFULL OUTER JOIN parents p ON t.email = p.email\n\n-- test query (folded version)\nselect COALESCE(t.workphone,p.cellphone) AS phone, COALESCE(t.email, p.email) AS email\nFROM teachers t\nFULL OUTER JOIN parents p ON t.email = p.email\n\n-- ----------------------------------------------\n-- Pt 2: Optimizing a Query with FOJ\n-- ----------------------------------------------\n\n-- Create Table(s)\nCREATE TABLE parked_vehicles (\n  day\t\tDATE NOT NULL,\n  veh_tag_no\tVARCHAR(32) NOT NULL\n);\n\n-- Insert 90 days of random data. Tag #s are composed of eight random chars.\nINSERT INTO parked_vehicles\n  SELECT TOP 2500000 DATEADD(D,-ROUND(RAND(CHECKSUM(NEWID())) * 90,0),GETDATE()),\n  SUBSTRING(CONVERT(varchar(255),newid()),1,8)\nFROM sys.objects AS O\nCROSS JOIN sys.objects AS O2\nCROSS JOIN sys.objects AS O3;\n\n-- Query Form 1: EXCEPT\nWITH yesterday AS (\nSELECT * FROM parked_vehicles WHERE day = CAST(DATEADD(d,-1,GETDATE()) AS DATE)\n),\ntoday AS (\nSELECT * FROM parked_vehicles WHERE day = CAST(GETDATE() AS DATE)\n)\nSELECT veh_tag_no FROM yesterday\n  EXCEPT SELECT veh_tag_no FROM today\nUNION ALL (\n SELECT veh_tag_no FROM today\n  EXCEPT SELECT veh_tag_no FROM yesterday );\n\n\n-- Query Form 2: Sub-selects\nWITH yesterday AS (\nSELECT * FROM parked_vehicles WHERE day = CAST(DATEADD(d,-1,GETDATE()) AS DATE)\n),\ntoday AS (\nSELECT * FROM parked_vehicles WHERE day = CAST(GETDATE() AS DATE)\n)\nSELECT * FROM yesterday y\n  WHERE NOT EXISTS (SELECT 1 FROM today t WHERE t.veh_tag_no = y.veh_tag_no)\nUNION ALL (\nSELECT * FROM today t\n  WHERE NOT EXISTS (SELECT 1 FROM yesterday y WHERE y.veh_tag_no = t.veh_tag_no) );\n\n-- Query Form 3: Full Outer Join\nWITH yesterday AS (\n  SELECT * FROM parked_vehicles WHERE day = CAST(DATEADD(d,-1,GETDATE()) AS DATE)\n),\ntoday AS (\n  SELECT * FROM parked_vehicles WHERE day = CAST(GETDATE() AS DATE)\n)\nSELECT COALESCE(y.day,t.day), COALESCE(y.veh_tag_no, t.veh_tag_no)\n  FROM yesterday y\nFULL OUTER JOIN today t ON y.veh_tag_no = t.veh_tag_no\n  WHERE y.veh_tag_no IS NULL or t.veh_tag_no IS NULL\n\n\n-- ----------------------------------------------\n-- Pt 3: Exception Reporting\n-- ---------------------------------------------- \n\n-- Create table(s)\nCREATE TABLE owners (\n owner_id INT PRIMARY KEY,\n owner_name VARCHAR(64)\n);\nCREATE TABLE autos (\n auto_id INT PRIMARY KEY,\n owner_id INT NOT NULL,\n descrip VARCHAR(64)\n);\n\n-- Sample Data\nINSERT INTO owners VALUES (1,'Rick Blayne');\nINSERT INTO owners VALUES (2,'Victor Lazlo');\nINSERT INTO owners VALUES (3,'Louis Renault');\nINSERT INTO owners VALUES (4,'Ilsa Lund');\n\nINSERT INTO autos VALUES (101,1,'Porsche 911 GT3');\nINSERT INTO autos VALUES (102,3,'Ferrari 250 Testa Rossa');\nINSERT INTO autos VALUES (103,5,'Duesenberg Model SJ');\nINSERT INTO autos VALUES (104,6,'Studebaker Avanti');\n\n-- Test query \nSELECT *\nFROM owners o\nFULL OUTER JOIN autos a ON o.owner_id = a.owner_id\n  WHERE a.auto_id IS NULL or o.owner_id IS NULL\n\n-- Test query (folded version)\nSELECT\n  COALESCE(a.auto_id, o.owner_id) AS \"ID\",\n  COALESCE('Missing Owner: ' + a.descrip, 'No Vehicles: ' + o.owner_name) AS \"Error\"\nFROM owners o\nFULL OUTER JOIN autos a ON o.owner_id = a.owner_id\n  WHERE a.auto_id IS NULL or o.owner_id IS NULL<\/pre><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p><a id=\"_msocom_1\"><\/a><\/p>\n\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Understanding FULL OUTER JOIN in SQL Server<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is a FULL OUTER JOIN in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <div>A FULL OUTER JOIN returns all rows from both tables, matching rows where possible and filling unmatched columns with NULL values.<\/div>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How is it different from INNER JOIN or LEFT JOIN?<\/h3>\n            <div class=\"faq-answer\">\n                <div>Unlike INNER JOIN (which returns only matching rows) or LEFT JOIN (which returns all rows from the left table), FULL OUTER JOIN includes every row from both tables.<\/div>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. When should I use a FULL OUTER JOIN?<\/h3>\n            <div class=\"faq-answer\">\n                <div>Use it when you need a complete view of data from two tables, including non-matching rows &#8211; for example, merging customer and order lists to find gaps.<\/div>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. Does FULL OUTER JOIN impact performance?<\/h3>\n            <div class=\"faq-answer\">\n                <div>Yes, it can be slower on large datasets because it processes all rows from both tables. Indexing and filtering can help optimize performance.<\/div>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Can FULL OUTER JOIN be combined with WHERE clauses?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes, you can apply WHERE conditions, but be careful &#8211; they may filter out NULLs and defeat the purpose of a FULL OUTER JOIN.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Discover how the FULL OUTER JOIN in SQL can simplify queries, improve performance, and solve real-world data problems with practical examples.&hellip;<\/p>\n","protected":false},"author":343663,"featured_media":107579,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,143524],"tags":[4150,4151],"coauthors":[159110],"class_list":["post-107569","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-sql-server","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107569","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\/343663"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=107569"}],"version-history":[{"count":8,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107569\/revisions"}],"predecessor-version":[{"id":109559,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/107569\/revisions\/109559"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/107579"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=107569"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=107569"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=107569"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=107569"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}