{"id":723,"date":"2016-09-15T09:15:11","date_gmt":"2016-09-15T09:15:11","guid":{"rendered":"http:\/\/blog.bar-solutions.com\/?p=723"},"modified":"2016-08-31T10:43:30","modified_gmt":"2016-08-31T10:43:30","slug":"whats-the-difference-between-semi-join-and-anti-join","status":"publish","type":"post","link":"https:\/\/blog.bar-solutions.com\/?p=723","title":{"rendered":"What\u2019s the difference between SEMI-JOIN and ANTI-JOIN?"},"content":{"rendered":"<p>Dear Patrick,<\/p>\n<p>What is an ANTI-JOIN? And what is the difference between the SEMI-JOIN and the ANTI-JOIN?<\/p>\n<p>Lillian Sturdey<br \/>\n<!--more--><\/p>\n<p>Dear Lillian,<\/p>\n<p>First of all, both SEMI-JOIN and ANTI-JOIN are not in the SQL syntax but they are more a pattern. You might expect to be able to write something like:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n               SEMI JOIN emp e ON (e.deptno = d.deptno)\r\n             \/<\/pre>\n<p>to get all the departments that have at least one employee.<br \/>\nOr:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n               ANTI JOIN emp e ON (e.deptno = d.deptno)\r\n             \/<\/pre>\n<p>to get the departments with no employees. But all you get is an error saying your command is not properly ended, which can be read as a syntax error.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">ERROR at line 3:\r\nORA-00933: ORA-00933 SQL command not properly ended.<\/pre>\n<p>Maybe your first idea would be to use a normal join to get all the departments with at least one employee:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n               JOIN emp e ON (e.deptno = d.deptno)\r\n             \/<\/pre>\n<p>But this results in a record for every row in the EMP table. And we only wanted every unique department.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">    DEPTNO DNAME          LOC\r\n---------- -------------- -------------\r\n        20 RESEARCH       DALLAS\r\n        30 SALES          CHICAGO\r\n        30 SALES          CHICAGO\r\n        20 RESEARCH       DALLAS\r\n        30 SALES          CHICAGO\r\n        30 SALES          CHICAGO\r\n        10 ACCOUNTING     NEW YORK\r\n        20 RESEARCH       DALLAS\r\n        10 ACCOUNTING     NEW YORK\r\n        30 SALES          CHICAGO\r\n        20 RESEARCH       DALLAS\r\n\r\n    DEPTNO DNAME          LOC\r\n---------- -------------- -------------\r\n        30 SALES          CHICAGO\r\n        20 RESEARCH       DALLAS\r\n        10 ACCOUNTING     NEW YORK\r\n\r\n14 rows selected.<\/pre>\n<p>Well, that\u2019s easy enough, you think, just add a DISTINCT to the statement:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT DISTINCT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n               JOIN emp e ON (e.deptno = d.deptno) \r\n             \/<\/pre>\n<p>Exactly the result we are looking for:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">    DEPTNO DNAME          LOC\r\n---------- -------------- -------------\r\n        20 RESEARCH       DALLAS\r\n        10 ACCOUNTING     NEW YORK\r\n        30 SALES          CHICAGO<\/pre>\n<p>But what if the EMP table contains hundreds, thousands or maybe millions of rows. That would mean the database has to do a lot of work to filter out the distinct values.<br \/>\nA different, and probably better, approach would be to use the SEMI-JOIN pattern. You can use the IN operator like this:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n              FROM dept d\r\n              WHERE d.deptno IN (SELECT e.deptno\r\n                                   FROM emp e)\r\n             \/<\/pre>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">    DEPTNO DNAME          LOC\r\n---------- -------------- -------------\r\n        20 RESEARCH       DALLAS\r\n        30 SALES          CHICAGO\r\n        10 ACCOUNTING     NEW YORK<\/pre>\n<p>This is exactly what we want to see but for big tables this is not the correct way to go. For every record in the dept table all the records in the EMP table are checked. Again, if we have a lot of employees, this means a lot of work for the database.<br \/>\nA better SEMI-JOIN to use is the EXISTS operator:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n              WHERE EXISTS (SELECT 1\r\n                              FROM emp e\r\n                             WHERE e.deptno = d.deptno)\r\n             \/<\/pre>\n<p>Please note that with the current optimizer in the database Oracle will rewrite your query to use the best approach for the task. If the inner table (in our example EMP) is rather small, then the IN approach might be the best, in other cases it might be better to use the EXISTS approach. Where in earlier versions you had to think about which way to go (IN is better for small tables, EXISTS is better for big ones), you can now rely on the optimizer to make the correct choice.<br \/>\nIf you would want to see exactly the opposite of this query, i.e. all departments with no employees, you use an ANTI-JOIN pattern, which is pretty much the same but in this case you use NOT IN or NOT EXISTS. A different approach, which I think is pretty nice is to use an OUTER JOIN and check for the non-existence of values in column for the OUTER JOINED table.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">&#x5B;PATRICK]SQL&gt;SELECT d.deptno, d.dname, d.loc\r\n               FROM dept d\r\n               LEFT OUTER JOIN emp e ON (e.deptno = d.deptno)\r\n              WHERE e.empno IS NULL\r\n             \/<\/pre>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">    DEPTNO DNAME          LOC\r\n---------- -------------- -------------\r\n        40 OPERATIONS     BOSTON<\/pre>\n<p>Hope this gives you a bit more insight in this subject and gives you a better understanding of the wonders of the SQL language. Notice there are many ways to reach the same result, but one approach might be more economical than the other.<\/p>\n<p>Happy Oracle\u2019ing,<br \/>\nPatrick Barel<\/p>\n<p>Hope this answers your question.<br \/>\nHappy Oracle\u2019ing,<br \/>\nPatrick Barel<\/p>\n<table width=600>\n<tr>\n<td width=150 style=\"border: 0px; padding: 10px;\">\n<span style=\"border: 0px; padding: 10px;\"><img decoding=\"async\" src=\"https:\/\/blog.bar-solutions.com\/img\/mailto.png\" width=137 height=123 style=\"border: 0px; padding: 10px;\"\/><\/span>\n<\/td>\n<td width=450 style=\"border: 0px; padding: 5px; align-content: flex-start; vertical-align: top;\">\n<span style=\"border: 0px; padding: 5px; align-content: flex-start; vertical-align: top;\">If you have any comments on this subject or you have a question you want answered, please send an email to <a href=\"mailto:patrick@bar-solutions.com\" style=\"border: 0px; padding: 5px; align-content: flex-start; vertical-align: top;\">patrick[at]bar-solutions[dot]com<\/a>. If I know the answer, or can find it for you, maybe I can help.<\/span>\n<\/td>\n<\/tr>\n<\/table>\n<p><\/p>\n<p>This question has been published in <A href=\"http:\/\/www.otechmag.com\/2014\/otech-magazine-winter-2014-2\/\" target=\"_blank\">OTech Magazine of Winter 2014<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dear Patrick, What is an ANTI-JOIN? And what is the difference between the SEMI-JOIN and the ANTI-JOIN? Lillian Sturdey<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,9],"tags":[],"class_list":["post-723","post","type-post","status-publish","format-standard","hentry","category-oracle","category-sql"],"_links":{"self":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts\/723","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=723"}],"version-history":[{"count":2,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts\/723\/revisions"}],"predecessor-version":[{"id":730,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=\/wp\/v2\/posts\/723\/revisions\/730"}],"wp:attachment":[{"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=723"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=723"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.bar-solutions.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=723"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}