{"id":541,"date":"2011-08-01T15:04:00","date_gmt":"2011-08-01T15:04:00","guid":{"rendered":"\/blogs\/bobb\/post\/Denali-window-clause-followup-question.aspx"},"modified":"2011-08-01T15:04:00","modified_gmt":"2011-08-01T15:04:00","slug":"denali-window-clause-followup-question","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/","title":{"rendered":"Denali window clause followup question"},"content":{"rendered":"<p>\nSo, quickly on the heels of the first window clause and last_value() question, came a followup:\n<\/p>\n<p>\nOK smartie, why does last_value work fine here? I didn&#39;t have to change from the default window. What gives?\n<\/p>\n<p>\nselect SalesPersonID, SalesOrderID, <br \/>\n&nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as FirstOrderForSalesPerson, <br \/>\n&nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as LastOrderForSalesPerson<br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL<br \/>\norder by SalesPersonID, SalesOrderID\n<\/p>\n<p>\nIt&#39;s close to the last example, except that we&#39;re ordering by SalesPersonID instead of SalesOrderID. And, partitioning by (works like group by except that we keep all the detail rows in each group) SalesPersonID. So why DOES it produce the &quot;right&quot; answer for each SalesPersonID?\n<\/p>\n<p>\nRemember that the query above is equivalent to:\n<\/p>\n<p>\nselect SalesPersonID, SalesOrderID, <br \/>\n&nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; range between unbounded preceding and current row) as FirstOrderForSalesPerson, <br \/>\n&nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; range between unbounded preceding and current row) as LastOrderForSalesPerson<br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL<br \/>\norder by SalesPersonID, SalesOrderID\n<\/p>\n<p>\nAnd it gets the intuitive &quot;right&quot; answer because of the way the &quot;range&quot; windowing spec handles ties. Since the order by clause specifies &quot;SalesPersonID&quot;, all the rows for the same SalesPersonID are ties. The window frame with range includes all rows with the same value (all ties) as the part of the frame. So, in the query above, SalesPersonID X has multiple (tied) rows in the window, range considers all of them.\n<\/p>\n<p>\nContrast to this, using the &quot;rows&quot; windowing spec, which *doesn&#39;t* consider multiple tied rows as part of the window, we&#39;re back to our &quot;weird&quot; answer.\n<\/p>\n<p>\nselect SalesPersonID, SalesOrderID, <br \/>\n&nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rows between unbounded preceding and current row) as FirstOrderForSalesPerson, <br \/>\n&nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rows between unbounded preceding and current row) as LastOrderForSalesPerson<br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL<br \/>\norder by SalesPersonID, SalesOrderID\n<\/p>\n<p>\nA different way to prove to yourself that RANGE considers ties part of the frame and ROWS doesn&#39;t is to use the use the SUM aggregate instead of FIRST\/LAST_VALUE().\n<\/p>\n<p>\n&#8212; all of SumOfTotal for a specific SalesPersonID are equal with RANGE<br \/>\n&#8212; SumOfTotal is equal for each row, each SalesPersonID<br \/>\nselect SalesPersonID, SalesOrderID, TotalDue, <br \/>\n&nbsp;sum(TotalDue) over(partition by SalesPersonID order by SalesPersonID) as SumOfTotal <br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL\n<\/p>\n<p>\n&#8212; you get a &quot;running total&quot; when using ROWS<br \/>\nselect SalesPersonID, SalesOrderID, TotalDue, <br \/>\n&nbsp;sum(TotalDue) over(partition by SalesPersonID order by SalesPersonID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rows between unbounded preceding and current row) as SumOfTotal <br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL\n<\/p>\n<p>\nHowever, notice that the running total isn&#39;t necessarily by SalesOrderID becuase without an &quot;order by&quot; in the OVER clause, the spec doesn&#39;t guarentee ordering by both columns. Note for example, that in the query above, for SalesPersonID 281, SalesOrderID 48327 comes after 48370 (at least it does on my machine, without additional indexes on SalesOrderHeader table).\n<\/p>\n<p>\nIf you truly want running total by SalesOrderID, <strong>in SalesOrderID order<\/strong>, add SalesOrderID to the &quot;ORDER BY&quot; in the OVER clause. When you add SalesOrderID to ORDER BY, now there are no ties in ordering. So either ROWS or RANGE will do the trick.\n<\/p>\n<p>\n&#8212; same answer as next query<br \/>\nselect SalesPersonID, SalesOrderID, TotalDue, <br \/>\n&nbsp;sum(TotalDue) over(partition by SalesPersonID order by SalesPersonID, SalesOrderID<br \/>\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rows between unbounded preceding and current row) as SumOfTotal <br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL\n<\/p>\n<p>\n&#8212; same answer as previous query, uses RANGE by default<br \/>\nselect SalesPersonID, SalesOrderID, TotalDue, <br \/>\n&nbsp;sum(TotalDue) over(partition by SalesPersonID order by SalesPersonID, SalesOrderID) as SumOfTotal <br \/>\nfrom Sales.SalesOrderHeader<br \/>\nwhere SalesPersonID IS NOT NULL\n<\/p>\n<p>\nHope this helps, Bob\n<\/p>\n<p>\n@bobbeauch<\/p>\n","protected":false},"excerpt":{"rendered":"<p>So, quickly on the heels of the first window clause and last_value() question, came a followup: OK smartie, why does last_value work fine here? I didn&#39;t have to change from the default window. What gives? select SalesPersonID, SalesOrderID, &nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as FirstOrderForSalesPerson, &nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as [&hellip;]<\/p>\n","protected":false},"author":7,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[31,40],"tags":[],"class_list":["post-541","post","type-post","status-publish","format-standard","hentry","category-sql-server-2012","category-transact-sql"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v21.9.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Denali window clause followup question - Bob Beauchemin<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Denali window clause followup question - Bob Beauchemin\" \/>\n<meta property=\"og:description\" content=\"So, quickly on the heels of the first window clause and last_value() question, came a followup: OK smartie, why does last_value work fine here? I didn&#039;t have to change from the default window. What gives? select SalesPersonID, SalesOrderID, &nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as FirstOrderForSalesPerson, &nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/\" \/>\n<meta property=\"og:site_name\" content=\"Bob Beauchemin\" \/>\n<meta property=\"article:published_time\" content=\"2011-08-01T15:04:00+00:00\" \/>\n<meta name=\"author\" content=\"Bob Beauchemin\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Bob Beauchemin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/\",\"url\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/\",\"name\":\"Denali window clause followup question - Bob Beauchemin\",\"isPartOf\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/#website\"},\"datePublished\":\"2011-08-01T15:04:00+00:00\",\"dateModified\":\"2011-08-01T15:04:00+00:00\",\"author\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/62bfa986c5b5d28fcffd8b4fc409c73e\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"SQL Server 2012\",\"item\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/category\/sql-server-2012\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Denali window clause followup question\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/#website\",\"url\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/\",\"name\":\"Bob Beauchemin\",\"description\":\"SQL Server Blog\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/62bfa986c5b5d28fcffd8b4fc409c73e\",\"name\":\"Bob Beauchemin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/6f80e6cc667410857fa6a21931dc528b8092f4d112bf7a8ff7c267674d44ee37?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/6f80e6cc667410857fa6a21931dc528b8092f4d112bf7a8ff7c267674d44ee37?s=96&d=mm&r=g\",\"caption\":\"Bob Beauchemin\"},\"sameAs\":[\"http:\/www.sqlskills.com\/blogs\/bobb\/\"],\"url\":\"https:\/\/www.sqlskills.com\/blogs\/bobb\/author\/bobb\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Denali window clause followup question - Bob Beauchemin","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/","og_locale":"en_US","og_type":"article","og_title":"Denali window clause followup question - Bob Beauchemin","og_description":"So, quickly on the heels of the first window clause and last_value() question, came a followup: OK smartie, why does last_value work fine here? I didn&#39;t have to change from the default window. What gives? select SalesPersonID, SalesOrderID, &nbsp;first_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as FirstOrderForSalesPerson, &nbsp;last_value(SalesOrderID) over(partition by SalesPersonID order by SalesPersonID) as [&hellip;]","og_url":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/","og_site_name":"Bob Beauchemin","article_published_time":"2011-08-01T15:04:00+00:00","author":"Bob Beauchemin","twitter_misc":{"Written by":"Bob Beauchemin","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/","url":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/","name":"Denali window clause followup question - Bob Beauchemin","isPartOf":{"@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/#website"},"datePublished":"2011-08-01T15:04:00+00:00","dateModified":"2011-08-01T15:04:00+00:00","author":{"@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/62bfa986c5b5d28fcffd8b4fc409c73e"},"breadcrumb":{"@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/denali-window-clause-followup-question\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.sqlskills.com\/blogs\/bobb\/"},{"@type":"ListItem","position":2,"name":"SQL Server 2012","item":"https:\/\/www.sqlskills.com\/blogs\/bobb\/category\/sql-server-2012\/"},{"@type":"ListItem","position":3,"name":"Denali window clause followup question"}]},{"@type":"WebSite","@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/#website","url":"https:\/\/www.sqlskills.com\/blogs\/bobb\/","name":"Bob Beauchemin","description":"SQL Server Blog","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.sqlskills.com\/blogs\/bobb\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/62bfa986c5b5d28fcffd8b4fc409c73e","name":"Bob Beauchemin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.sqlskills.com\/blogs\/bobb\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/6f80e6cc667410857fa6a21931dc528b8092f4d112bf7a8ff7c267674d44ee37?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/6f80e6cc667410857fa6a21931dc528b8092f4d112bf7a8ff7c267674d44ee37?s=96&d=mm&r=g","caption":"Bob Beauchemin"},"sameAs":["http:\/www.sqlskills.com\/blogs\/bobb\/"],"url":"https:\/\/www.sqlskills.com\/blogs\/bobb\/author\/bobb\/"}]}},"_links":{"self":[{"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/posts\/541","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/users\/7"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/comments?post=541"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/posts\/541\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/media?parent=541"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/categories?post=541"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/bobb\/wp-json\/wp\/v2\/tags?post=541"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}