{"id":2417,"date":"2019-04-23T17:10:04","date_gmt":"2019-04-23T15:10:04","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=2417"},"modified":"2019-04-23T17:11:51","modified_gmt":"2019-04-23T15:11:51","slug":"no-this-is-not-a-bug-in-t-sql","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2019\/04\/23\/no-this-is-not-a-bug-in-t-sql\/","title":{"rendered":"No, this is not a bug in T-SQL"},"content":{"rendered":"\n<p>(or, Column scope and binding order in subqueries)<\/p>\n\n\n\n<p>I keep seeing this in all sorts of places. People getting an unexpected result when working with a subquery, typically an IN subquery, and assuming that they\u2019ve found a bug in SQL Server.<\/p>\n\n\n\n<p>It\u2019s a bug alright, in that developer\u2019s code though.<\/p>\n\n\n\n<p>Let\u2019s see if anyone can spot the mistake.<\/p>\n\n\n\n<p>We\u2019ll start with a table of orders.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE TABLE Orders (\n  OrderID INT IDENTITY PRIMARY KEY,\n  ClientID INT,\n  OrderNumber VARCHAR(20)\n)\n<\/pre><\/div>\n\n\n<p>There would be more to it in a real system, but this will do for a demo. We\u2019re doing some archiving of old orders, of inactive clients. The IDs of those inactive clients have been put into a temp table<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE TABLE #TempClients (\nClientD INT\n);\n<\/pre><\/div>\n\n\n<p>And, to check before running the actual delete, we run the following:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT * FROM dbo.Orders\nWHERE ClientID IN (SELECT ClientID FROM #TempClients)\n<\/pre><\/div>\n\n\n<p>And it returns the entire Orders table. The IN appears to have been completely ignored. At least the query was checked before doing the delete, that\u2019s saved an unpleasant conversation with the DBA if nothing else.<\/p>\n\n\n\n<p>Anyone spotted the mistake yet?<\/p>\n\n\n\n<p>It\u2019s a fairly simple one, not easy to see in passing, but if I test the subquery alone it should become obvious.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"982\" height=\"252\" data-attachment-id=\"2342\" data-permalink=\"https:\/\/www.sqlinthewild.co.za\/subquerymistake1-png\/\" data-orig-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1.png\" data-orig-size=\"982,252\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"SubqueryMistake1.png\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1-300x77.png\" data-large-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1.png\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1.png\" alt=\"\" class=\"wp-image-2342\" srcset=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1.png 982w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1-300x77.png 300w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1-768x197.png 768w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake1-765x196.png 765w\" sizes=\"auto, (max-width: 982px) 100vw, 982px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image is-resized\"><img loading=\"lazy\" decoding=\"async\" data-attachment-id=\"2344\" data-permalink=\"https:\/\/www.sqlinthewild.co.za\/subquerymistake2-png\/\" data-orig-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2.png\" data-orig-size=\"422,131\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"SubqueryMistake2.png\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2-300x93.png\" data-large-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2.png\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2.png\" alt=\"\" class=\"wp-image-2344\" width=\"317\" height=\"98\" srcset=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2.png 422w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake2-300x93.png 300w\" sizes=\"auto, (max-width: 317px) 100vw, 317px\" \/><\/figure>\n\n\n\n<p>The column name in the temp table is missing an I, probably just a typo, but it has some rather pronounced effects.<\/p>\n\n\n\n<p>The obvious next question is why the select with the subquery in it didn\u2019t fail, after all, the query asks for ClientID from #TempClients, and there\u2019s no such column. However there is a ClientID column available in that query, and it\u2019s in the Orders table. And that\u2019s a valid column for the subquery, because column binding order, when we have subqueries, is first to tables within the subquery, and then, if no match is found, to tables in the outer query.<\/p>\n\n\n\n<p>It has to work this way, otherwise correlated subqueries would not be possible. For example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT c.LegalName,\nc.HypernetAddress\nFROM dbo.Clients AS c\nWHERE EXISTS (SELECT 1 FROM dbo.Shipments s WHERE s.HasLivestock = 1 AND c.ClientID = s.ClientID)\n<\/pre><\/div>\n\n\n<p>In that example, c.ClientID explicitly references the Client table in the outer query. If I left off the c., the column would be bound to the ClientID column in the Shipments table.<\/p>\n\n\n\n<p>Going back to our original example&#8230;<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT * FROM dbo.Orders\nWHERE ClientID IN (SELECT ClientID FROM #TempClients)\n<\/pre><\/div>\n\n\n<p>When the query is parsed and bound, the ClientID column mentioned in the subquery does not match any column from any table within the subquery, and hence it\u2019s checked against tables in the outer query, and it does match a column in the orders table. Hence the query essentially becomes<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT * FROM dbo.Orders\nWHERE ClientID IN (SELECT dbo.Orders.ClientID FROM #TempClients)\n<\/pre><\/div>\n\n\n<p>Which is essentially equivalent to<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT * FROM dbo.Orders\nWHERE 1=1\n<\/pre><\/div>\n\n\n<p>This is one reason why all columns should always, always, always, be qualified with their tables (or table aliases), especially when there are subqueries involved, as doing so would have completely prevented this problem.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT * FROM dbo.Orders o\nWHERE o.ClientID IN (SELECT tc.ClientID FROM #TempClients tc)\n<\/pre><\/div>\n\n\n<p>With the column in the subquery only allowed to be bound to columns within the #TempClients table, the query throws the expected column not found error.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"196\" data-attachment-id=\"2346\" data-permalink=\"https:\/\/www.sqlinthewild.co.za\/subquerymistake3-png\/\" data-orig-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3.png\" data-orig-size=\"1024,196\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"SubqueryMistake3.png\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-300x57.png\" data-large-file=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-1024x196.png\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-1024x196.png\" alt=\"\" class=\"wp-image-2346\" srcset=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3.png 1024w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-300x57.png 300w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-768x147.png 768w, https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2018\/12\/SubqueryMistake3-765x146.png 765w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>And we\u2019re no longer in danger of deleting everything from the orders table, as we would have if that subquery had been part of a delete and not a select.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>(or, Column scope and binding order in subqueries) I keep seeing this in all sorts of places. People getting an unexpected result when working with a subquery, typically an IN subquery, and assuming that they\u2019ve found a bug in SQL&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2019\/04\/23\/no-this-is-not-a-bug-in-t-sql\/\">(Read more)<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[15,17],"tags":[],"class_list":["post-2417","post","type-post","status-publish","format-standard","hentry","category-sql-server","category-t-sql"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-CZ","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2417","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/comments?post=2417"}],"version-history":[{"count":3,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2417\/revisions"}],"predecessor-version":[{"id":2420,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/2417\/revisions\/2420"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=2417"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=2417"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=2417"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}