{"id":474,"date":"2017-09-01T14:44:53","date_gmt":"2017-09-01T14:44:53","guid":{"rendered":"http:\/\/www.gnial.com.br\/gnialhelp\/?p=474"},"modified":"2019-11-26T10:40:12","modified_gmt":"2019-11-26T13:40:12","slug":"mysql-not-in-vs-not-exists-vs-left-join-is-null","status":"publish","type":"post","link":"http:\/\/www.gnial.com.br\/gnialhelp\/mysql-not-in-vs-not-exists-vs-left-join-is-null\/","title":{"rendered":"MySQL &#8211; NOT IN vs. NOT EXISTS vs. LEFT JOIN \/ IS NULL"},"content":{"rendered":"<h2 id=\"post-3081\">NOT IN vs. NOT EXISTS vs. LEFT JOIN \/ IS NULL: MySQL<\/h2>\n<div class=\"main\">\n<blockquote><p>Which method is best to select values present in one table but missing in another one?<\/p>\n<p>This:<\/p>\n<div id=\"highlighter_287034\" class=\"syntaxhighlighter \">\n<div class=\"bar\"><\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.*<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"color2\">LEFT<\/code>\u00a0<code class=\"color1\">JOIN<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">t_right r<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">ON<\/code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">r.value = l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">r.value\u00a0<\/code><code class=\"keyword\">IS<\/code>\u00a0<code class=\"color1\">NULL<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p>, this:<\/p>\n<div id=\"highlighter_282959\" class=\"syntaxhighlighter \">\n<div class=\"bar\"><\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.*<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">l.value\u00a0<\/code><code class=\"color1\">NOT<\/code>\u00a0<code class=\"color1\">IN<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">(<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_right r<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">7.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">)<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p>or this:<\/p>\n<div id=\"highlighter_532833\" class=\"syntaxhighlighter \">\n<div class=\"bar\"><\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.*<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"color1\">NOT<\/code>\u00a0<code class=\"plain\">EXISTS<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">(<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"color1\">NULL<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_right r<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">7.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">r.value = l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">8.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">)<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<\/blockquote>\n<p>Finally, it&#8217;s\u00a0<strong>MySQL<\/strong>\u00a0time.<\/p>\n<p>As always, we will create the sample tables:<\/p>\n<p><span id=\"more-3081\"><\/span><br \/>\n<a href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#\"><strong>Table creation details<\/strong><\/a><\/p>\n<p>Table\u00a0<code>t_left<\/code>\u00a0contains\u00a0<strong>100,000<\/strong>\u00a0rows with\u00a0<strong>10,000<\/strong>\u00a0distinct values.<\/p>\n<p>Table\u00a0<code>t_right<\/code>\u00a0contains\u00a0<strong>1,000,000<\/strong>\u00a0rows with\u00a0<strong>10,000<\/strong>\u00a0distinct values.<\/p>\n<p>There are\u00a0<strong>10<\/strong>\u00a0rows in\u00a0<code>t_left<\/code>\u00a0with values not present in\u00a0<code>t_right<\/code>.<\/p>\n<p>In both tables the field\u00a0<code>value<\/code>\u00a0is indexed.<\/p>\n<h3>LEFT JOIN \/ IS NULL<\/h3>\n<div id=\"highlighter_805677\" class=\"syntaxhighlighter \">\n<div class=\"bar\">\n<div class=\"toolbar\"><a class=\"item viewSource\" title=\"view source\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#viewSource\">view source<\/a><a class=\"item printSource\" title=\"print\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#printSource\">print<\/a><a class=\"item about\" title=\"?\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#about\">?<\/a><\/div>\n<\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.id, l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"color2\">LEFT<\/code>\u00a0<code class=\"color1\">JOIN<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">t_right r<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">ON<\/code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">r.value = l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">r.value\u00a0<\/code><code class=\"keyword\">IS<\/code>\u00a0<code class=\"color1\">NULL<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p><a href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#\"><strong>View query results and execution plan<\/strong><\/a><\/p>\n<p>The query returns correct results in\u00a0<strong>0.73 seconds<\/strong>.<\/p>\n<p>If we look into the\u00a0<strong>Extra<\/strong>\u00a0part of the execution plan, we will see an interesting thing there:<\/p>\n<p><code>Using where; Using index; Not exists<\/code><\/p>\n<p>What does it mean?<\/p>\n<p><a href=\"http:\/\/dev.mysql.com\/doc\/refman\/5.1\/en\/using-explain.html\"><strong>MySQL<\/strong>\u00a0documentation<\/a>\u00a0mentions this:<\/p>\n<blockquote><p><code>Not exists<\/code><\/p>\n<p><strong>MySQL<\/strong>\u00a0was able to do a\u00a0<code>LEFT JOIN<\/code>\u00a0optimization on the query and does not examine more rows in this table for the previous row combination after it finds one row that matches the\u00a0<code>LEFT JOIN<\/code>criteria. Here is an example of the type of query that can be optimized this way:<\/p>\n<div id=\"highlighter_669431\" class=\"syntaxhighlighter \">\n<div class=\"bar\">\n<div class=\"toolbar\"><a class=\"item viewSource\" title=\"view source\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#viewSource\">view source<\/a><a class=\"item printSource\" title=\"print\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#printSource\">print<\/a><a class=\"item about\" title=\"?\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#about\">?<\/a><\/div>\n<\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">*<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t1<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"color2\">LEFT<\/code>\u00a0<code class=\"color1\">JOIN<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">t2<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">ON<\/code>\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t1.id = t2.id<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">t2.id\u00a0<\/code><code class=\"keyword\">IS<\/code>\u00a0<code class=\"color1\">NULL<\/code><code class=\"plain\">;<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p>Assume that\u00a0<code>t2.id<\/code>\u00a0is defined as\u00a0<code>NOT NULL<\/code>. In this case,\u00a0<strong>MySQL<\/strong>\u00a0scans\u00a0<code>t1<\/code>\u00a0and looks up the rows in\u00a0<code>t2<\/code>using the values of\u00a0<code>t1.id<\/code>. If\u00a0<strong>MySQL<\/strong>\u00a0finds a matching row in\u00a0<code>t2<\/code>, it knows that\u00a0<code>t2.id<\/code>\u00a0can never be\u00a0<code>NULL<\/code>, and does not scan through the rest of the rows in\u00a0<code>t2<\/code>\u00a0that have the same id value. In other words, for each row in\u00a0<code>t1<\/code>,\u00a0<strong>MySQL<\/strong>\u00a0needs to do only a single lookup in\u00a0<code>t2<\/code>, regardless of how many rows actually match in\u00a0<code>t2<\/code>.<\/p><\/blockquote>\n<p>This is exactly our case.<\/p>\n<p><strong>MySQL<\/strong>, as well as all other systems except\u00a0<strong>SQL Server<\/strong>, is able to optimize\u00a0<code>LEFT JOIN \/ IS NULL<\/code>\u00a0to return\u00a0<code>FALSE<\/code>\u00a0as soon the matching value is found, and it is the only system that cared to document this behavior.<\/p>\n<p>The assumption that\u00a0<code>t2.id<\/code>\u00a0should be defined as\u00a0<code>NOT NULL<\/code>, however, is too strong, since a successfull\u00a0<code>JOIN<\/code>\u00a0on equality condition implies that the value found is\u00a0<code>NOT NULL<\/code>.<\/p>\n<p>Since\u00a0<strong>MySQL<\/strong>\u00a0is not capable of using\u00a0<code>HASH<\/code>\u00a0and\u00a0<code>MERGE<\/code>\u00a0join algorithms, the only\u00a0<code>ANTI JOIN<\/code>\u00a0it is capable of is the\u00a0<code>NESTED LOOPS ANTI JOIN<\/code>, which is exactly what we see in the query plan (despite the fact that\u00a0<strong>MySQL<\/strong>\u00a0doesn&#8217;t call it that). However, this behavior is what an\u00a0<code>ANTI JOIN<\/code>\u00a0does: it checks the values from the left table against only one of each distinct values in the right table, skipping the duplicates.<\/p>\n<h3>NOT IN<\/h3>\n<div id=\"highlighter_630720\" class=\"syntaxhighlighter \">\n<div class=\"bar\">\n<div class=\"toolbar\"><a class=\"item viewSource\" title=\"view source\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#viewSource\">view source<\/a><a class=\"item printSource\" title=\"print\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#printSource\">print<\/a><a class=\"item about\" title=\"?\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#about\">?<\/a><\/div>\n<\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.id, l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">value\u00a0<\/code><code class=\"color1\">NOT<\/code>\u00a0<code class=\"color1\">IN<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">(<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_right<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">7.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">)<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p><a href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#\"><strong>View query results and execution plan<\/strong><\/a><\/p>\n<p>This query is as fast as the\u00a0<code>LEFT JOIN \/ NOT NULL<\/code>, however its plan looks quite different.<\/p>\n<p>First, it mentions a\u00a0<code>DEPENDENT SUBQUERY<\/code>\u00a0instead of a second table (which was used in the\u00a0<code>LEFT JOIN \/ IS NULL<\/code>). Nominally, is a dependent subquery indeed, since we don&#8217;t have a join here but rather a\u00a0<code>SELECT<\/code>\u00a0from a single table with predicate in the\u00a0<code>WHERE<\/code>\u00a0clause.<\/p>\n<p>Second, the description part of the plan mentions this:<\/p>\n<p><code>&lt;exists&gt;(&lt;index_lookup&gt;(&lt;cache&gt;(`20090918_anti`.`l`.`value`) in t_right on ix_right_value))<\/code><\/p>\n<p>What is that?<\/p>\n<p>This is of course our good old friend, the\u00a0<code>ANTI JOIN<\/code>.\u00a0<strong>MySQL<\/strong>\u00a0applies\u00a0<code>EXISTS<\/code>\u00a0optimization to the subquery: it uses the index scan over\u00a0<code>ix_right_value<\/code>\u00a0and returns as soon as it finds (or not finds) a row.<\/p>\n<p><code>NOT IN<\/code>\u00a0is different in how it handles\u00a0<code>NULL<\/code>\u00a0values. However, since both\u00a0<code>t_left.value<\/code>\u00a0and\u00a0<code>t_right.value<\/code>\u00a0are defined as\u00a0<code>NOT NULL<\/code>, no\u00a0<code>NULL<\/code>\u00a0value can ever be returned by this predicate, and\u00a0<strong>MySQL<\/strong>\u00a0takes this into account.<\/p>\n<p>Essentially, this is exactly the same plan that\u00a0<code>LEFT JOIN \/ IS NULL<\/code>\u00a0uses, despite the fact these plans are executed by the different branches of code and they look different in the results of\u00a0<code>EXPLAIN<\/code>. The algorithms are in fact the same in fact and the queries complete in same time.<\/p>\n<h3>NOT EXISTS<\/h3>\n<div id=\"highlighter_965595\" class=\"syntaxhighlighter \">\n<div class=\"bar\">\n<div class=\"toolbar\"><a class=\"item viewSource\" title=\"view source\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#viewSource\">view source<\/a><a class=\"item printSource\" title=\"print\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#printSource\">print<\/a><a class=\"item about\" title=\"?\" href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#about\">?<\/a><\/div>\n<\/div>\n<div class=\"lines\">\n<div class=\"line alt1\"><code class=\"number\">1.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">l.id, l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">2.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_left l<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">3.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"color1\">NOT<\/code>\u00a0<code class=\"plain\">EXISTS<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">4.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">(<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">5.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">SELECT<\/code>\u00a0\u00a0<code class=\"plain\">value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">6.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">FROM<\/code>\u00a0\u00a0\u00a0\u00a0<code class=\"plain\">t_right r<\/code><\/span><\/span><\/div>\n<div class=\"line alt1\"><code class=\"number\">7.<\/code><span class=\"content\"><span class=\"block\"><code class=\"keyword\">WHERE<\/code>\u00a0\u00a0\u00a0<code class=\"plain\">r.value = l.value<\/code><\/span><\/span><\/div>\n<div class=\"line alt2\"><code class=\"number\">8.<\/code><span class=\"content\"><span class=\"block\"><code class=\"plain\">)<\/code><\/span><\/span><\/div>\n<\/div>\n<\/div>\n<p><a href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/#\"><strong>View query results and execution plan<\/strong><\/a><\/p>\n<p>This query of course produces the same results.<\/p>\n<p>Execution plan, again, is different.\u00a0<strong>MySQL<\/strong>\u00a0is the only system that produces three different plans for three different methods.<\/p>\n<p>The plan does not differ much:\u00a0<strong>MySQL<\/strong>\u00a0does know what an index lookup is and what\u00a0<code>EXISTS<\/code>\u00a0is and it does combine them together.<\/p>\n<p><code>EXISTS<\/code>\u00a0in\u00a0<strong>MySQL<\/strong>\u00a0is optimized so that it returns as soon as the first value is found. So this query in fact is an\u00a0<code>ANTI JOIN<\/code>\u00a0as well as first two queries are.<\/p>\n<p>This query, however, is a little bit less efficient than the previous two: it takes\u00a0<strong>0.92 s<\/strong>.<\/p>\n<p>This is not much of a performance drop, however, the query takes\u00a0<strong>27%<\/strong>\u00a0more time.<\/p>\n<p>It&#8217;s hard to tell exact reason for this, since this drop is linear and does not seem to depend on data distribution, number of values in both tables etc., as long as both fields are indexed. Since there are three pieces of code in\u00a0<strong>MySQL<\/strong>\u00a0that essentialy do one job, it is possible that the code responsible for\u00a0<code>EXISTS<\/code>\u00a0makes some kind of an extra check which takes extra time.<\/p>\n<h3>Summary<\/h3>\n<p><strong>MySQL<\/strong>\u00a0can optimize all three methods to do a sort of\u00a0<code>NESTED LOOPS ANTI JOIN<\/code>.<\/p>\n<p>It will take each value from\u00a0<code>t_left<\/code>\u00a0and look it up in the index on\u00a0<code>t_right.value<\/code>. In case of an index hit or an index miss, the corresponding predicate will immediately return\u00a0<code>FALSE<\/code>\u00a0or\u00a0<code>TRUE<\/code>, respectively, and the decision to return the row from\u00a0<code>t_left<\/code>\u00a0or not will be made immediately without examining other rows in\u00a0<code>t_right<\/code>.<\/p>\n<p>However, these three methods generate three different plans which are executed by three different pieces of code. The code that executes\u00a0<code>EXISTS<\/code>\u00a0predicate is about\u00a0<strong>30%<\/strong>\u00a0less efficient than those that execute\u00a0<code>index_subquery<\/code>\u00a0and\u00a0<code>LEFT JOIN<\/code>\u00a0optimized to use\u00a0<code>Not exists<\/code>\u00a0method.<\/p>\n<p>That&#8217;s why the best way to search for missing values in\u00a0<strong>MySQL<\/strong>\u00a0is using a\u00a0<code>LEFT JOIN \/ IS NULL<\/code>\u00a0or\u00a0<code>NOT IN<\/code>rather than\u00a0<code>NOT EXISTS<\/code>.<\/p>\n<p>fonte:<\/p>\n<blockquote class=\"wp-embedded-content\" data-secret=\"eUc7KYvdkY\"><p><a href=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/\">NOT IN vs. NOT EXISTS vs. LEFT JOIN \/ IS NULL: MySQL<\/a><\/p><\/blockquote>\n<p><iframe loading=\"lazy\" class=\"wp-embedded-content\" sandbox=\"allow-scripts\" security=\"restricted\" style=\"position: absolute; clip: rect(1px, 1px, 1px, 1px);\" title=\"&#8220;NOT IN vs. NOT EXISTS vs. LEFT JOIN \/ IS NULL: MySQL&#8221; &#8212; EXPLAIN EXTENDED\" src=\"https:\/\/explainextended.com\/2009\/09\/18\/not-in-vs-not-exists-vs-left-join-is-null-mysql\/embed\/#?secret=uBUUMAA2AP#?secret=eUc7KYvdkY\" data-secret=\"eUc7KYvdkY\" width=\"560\" height=\"315\" frameborder=\"0\" marginwidth=\"0\" marginheight=\"0\" scrolling=\"no\"><\/iframe><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>NOT IN vs. NOT EXISTS vs. LEFT JOIN \/ IS NULL: MySQL Which method is best to select values present in one table but missing in another one? This: 1.SELECT\u00a0\u00a0l.* 2.FROM\u00a0\u00a0\u00a0\u00a0t_left l 3.LEFT\u00a0JOIN 4.t_right r 5.ON\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0r.value = l.value 6.WHERE\u00a0\u00a0\u00a0r.value\u00a0IS\u00a0NULL , this: 1.SELECT\u00a0\u00a0l.* 2.FROM\u00a0\u00a0\u00a0\u00a0t_left l 3.WHERE\u00a0\u00a0\u00a0l.value\u00a0NOT\u00a0IN 4.( 5.SELECT\u00a0\u00a0value 6.FROM\u00a0\u00a0\u00a0\u00a0t_right r 7.) or this: 1.SELECT\u00a0\u00a0l.* 2.FROM\u00a0\u00a0\u00a0\u00a0t_left l [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":565,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-474","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","has-thumbnail"],"_links":{"self":[{"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/posts\/474","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/comments?post=474"}],"version-history":[{"count":2,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/posts\/474\/revisions"}],"predecessor-version":[{"id":477,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/posts\/474\/revisions\/477"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/media\/565"}],"wp:attachment":[{"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/media?parent=474"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/categories?post=474"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.gnial.com.br\/gnialhelp\/wp-json\/wp\/v2\/tags?post=474"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}