{"id":1669,"date":"2013-01-25T13:00:00","date_gmt":"2013-01-25T18:00:00","guid":{"rendered":"http:\/\/3.209.169.194\/blogs\/jonathan\/?p=1669"},"modified":"2017-04-13T12:55:33","modified_gmt":"2017-04-13T16:55:33","slug":"ctes-window-functions-and-views","status":"publish","type":"post","link":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/","title":{"rendered":"Common Table Expressions (CTEs), Window Functions, and Views"},"content":{"rendered":"<p>Common Table Expressions (CTEs), window functions, and views are all common things in SQL Server development these days. When CTEs and window functions were introduced in SQL Server 2005, a lot of developers jumped in and began using them to solve problems and simplify code. While these tools can be a great benefit in SQL Server, they can also be the cause of significant performance problems in certain scenarios. I recently engaged in some performance tuning with a client after a code change was deployed to their production server that resulted in CPU usage of nearly 100% across all 32 logical processors. The resulting performance problems in their application made this a critical situation for the client. The fix required a few hours rewriting a couple of the modified stored procedures to reduce the CPU usage back down to the normal range of 10-20%.<\/p>\n<table width=\"710\" border=\"0\" cellspacing=\"0\" cellpadding=\"2\">\n<tbody>\n<tr>\n<td style=\"text-align: center;\" align=\"center\" width=\"50%\"><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image.png\"><img fetchpriority=\"high\" decoding=\"async\" alt=\"\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png\" width=\"354\" height=\"243\" \/><\/a><\/td>\n<td style=\"text-align: center;\" align=\"center\" width=\"50%\"><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image1.png\"><img decoding=\"async\" alt=\"\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb1.png\" width=\"354\" height=\"243\" \/><\/a><\/td>\n<\/tr>\n<tr>\n<td style=\"text-align: center;\" align=\"center\" width=\"50%\">CPU Usage Initially<\/td>\n<td style=\"text-align: center;\" align=\"center\" width=\"50%\">CPU Usage After Rewrites<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The root cause of the problem was something I consider to be a coding anti-pattern, using a CTE with a window function inside of a view.\u00a0 As we\u2019ll see in this post, mixing the three of these together can result in significant performance problems from the need to materialize the intermediate results of the view before filtering can be applied.<\/p>\n<h2>Setting up The Problem<\/h2>\n<p>To demonstrate the problem I\u2019ll first create a new table in AdventureWorks2012 to show the impact on performance as the number of rows increases in our test table. This table will be based on the HumanResources.EmployeePayHistory table and can be generated with the <a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/setup-cte-problems-in-adventureworks2012.sql\">script attached to this post<\/a>.<\/p>\n<ul>\n<li>To create the table with 316 rows, comment out the second INSERT statement entirely<\/li>\n<li>To create the table with 11,916 rows, change the GO for the second INSERT to be GO 40<\/li>\n<li>To create the table with 116,316 rows, change the GO for the second INSERT to be GO 400<\/li>\n<li>To create the table with 1,160,316, change the GO for the second INSERT to be GO 4000<\/li>\n<\/ul>\n<p>With my table created, I\u2019ll create a view to return the current pay value for the employees as follows:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nCREATE VIEW dbo.EmployeeCurrentPayData\r\nAS\r\n\r\nWITH PayChanges AS(\r\nSELECT\r\n    per.BusinessEntityID,\r\n    per.FirstName,\r\n    per.LastName,\r\n    per.MiddleName,\r\n    emp.JobTitle,\r\n    pay.Rate,\r\n    pay.PayFrequency,\r\n    ROW_NUMBER() OVER (PARTITION BY per.BusinessEntityID, JobTitle, PayFrequency ORDER BY pay.RateChangeDate DESC) AS RevisionID\r\nFROM Person.Person AS per\r\nINNER JOIN HumanResources.Employee AS emp\r\n    ON per.BusinessEntityID = emp.BusinessEntityID\r\nLEFT JOIN HumanResources.EmployeePayHistoryEnlarged AS pay\r\n    ON emp.BusinessEntityID = pay.BusinessEntityID)\r\nSELECT\r\n    BusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\nFROM PayChanges\r\nWHERE RevisionID = 1;\r\n\r\nGO\r\n<\/pre>\n<p>Then I\u2019ll create a stored procedure for my application to be able to get the current pay value for a specific employee by the BusinessEntityID for the employee.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nCREATE PROCEDURE dbo.GetEmployeeCurrentPay_View (@BusinessEntityID INT)\r\nAS\r\nBEGIN\r\n\r\nSELECT\r\n    BusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\nFROM dbo.EmployeeCurrentPayData\r\nWHERE BusinessEntityID = @BusinessEntityID;\r\n\r\nEND\r\nGO\r\n<\/pre>\n<p>If I run the stored procedure for any employee\u2019s BusinessEntityID the results will always be a single row.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nDECLARE @BusinessEntityID INT = 250;\r\nEXECUTE dbo.GetEmployeeCurrentPay_View @BusinessEntityID;\r\n<\/pre>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image2.png\"><img decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb2.png\" width=\"601\" height=\"76\" border=\"0\" \/><\/a><\/p>\n<p>For a table with 316 rows of pay history, the execution plan will have the following shape:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb3.png\" width=\"644\" height=\"132\" border=\"0\" \/><\/a><\/p>\n<p>The execution plan shows that the tables were all scanned and the CTE was computed before the filter predicate on BusinessEntityID was applied. I can see this in the tool-tip for the Filter operator and by the number of input rows versus output rows for the Filter.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb4.png\" width=\"623\" height=\"484\" border=\"0\" \/><\/a><\/p>\n<p>With a small number of rows, this\u00a0doesn&#8217;t\u00a0show up as being a significant problem as shown by the STATISTICS IO and STATISTICS TIME output for the execution.<\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [10]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 14 ms, elapsed time = 14 ms.\r\n\r\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 897, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Employee'. Scan count 1, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 0 ms,  elapsed time = 4 ms.\r\n<\/pre>\n<p>The total execution time was just 4ms with 316 rows. If I scale up our test table to 11,916 rows, the plan changes to using a Merge Join with the EmployeePayHistoryEnlarged table, but I can still see that the CTE was fully computed before the filtering on BusinessEntityID was applied.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image5.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb5.png\" width=\"644\" height=\"133\" border=\"0\" \/><\/a><\/p>\n<p>Performance wise, the stored procedure executes in 85ms and only uses 31ms of CPU time as shown by the execution statistics.<\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [9]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 0 ms, elapsed time = 9 ms.\r\n\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 1, logical reads 86, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 897, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Employee'. Scan count 1, logical reads 9, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 31 ms,  elapsed time = 85 ms.\r\n <\/pre>\n<p>The real problems start to occur when the number of rows is increased to 116,316. The increased cost of computing the CTE entirely results in an execution plan that uses parallelism.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image6.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb6.png\" width=\"644\" height=\"126\" border=\"0\" \/><\/a><\/p>\n<p>Testing this stored procedure on its own\u00a0wouldn&#8217;t\u00a0make you think that there was a performance problem lying in wait for a production system. Looking at the execution statistics, the procedure executed in 240ms, but the important item to look at is the shift in proportion of CPU time. Since the query used parallelism, I now have significantly more CPU time, by a factor of 3 in the 4vCPU VM I tested this on, which is the result of multiple threads processing the execution.<\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [10]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 16 ms, elapsed time = 16 ms.\r\n\r\nTable 'Employee'. Scan count 25, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 25, logical reads 319, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 25, logical reads 949, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 643 ms,  elapsed time = 240 ms.\r\n<\/pre>\n<p>The problem with this specific procedure is that it\u00a0doesn&#8217;t\u00a0just get executed once, it gets executed thousands of times every couple of seconds, so the cumulative effect of the CPU time required to compute the CTE completely becomes significant enough to run a 32-core server at nearly 100% CPU utilization as shown in the images at the top of the post.<\/p>\n<p>The problem only gets worse as the number of rows in the table increases, as shown by the execution plan and execution statistics for 1,160,316 rows.<\/p>\n<p><strong>Note:<\/strong> Depending on the hardware that the tests are run on, the plan might be different for these tests. For example the first plan below was generated with 4 vCPUs while the second plan using the Hash Join and Bitmap optimization was generated on the same server but with the -Pn startup parameter to start SQLOS with 24 logical schedulers, as showed in my post <a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/sql-server-and-soft-numa\/\">SQL Server and Soft NUMA<\/a>. In either case parallelism is used and the CPU time is roughly three times the execution time.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image7.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb7.png\" width=\"644\" height=\"107\" border=\"0\" \/><\/a><\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [10]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 0 ms, elapsed time = 0 ms.\r\n\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 290, logical reads 7416, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 923, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Employee'. Scan count 5, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 4336 ms,  elapsed time = 1788 ms.\r\n<\/pre>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image8.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb8.png\" width=\"644\" height=\"89\" border=\"0\" \/><\/a><\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [11]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 23 ms, elapsed time = 23 ms.\r\n\r\nTable 'Employee'. Scan count 25, logical reads 22, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 25, logical reads 319, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 290, logical reads 7656, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 6209 ms,  elapsed time = 2235 ms.\r\n<\/pre>\n<h2>How do you fix this?<\/h2>\n<p>Fixing this problem is actually very simple; don\u2019t use the VIEW and move the WHERE clause for the BusinessEntityID column up into the CTE so that it can be used to filter the tables before computation occurs.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nCREATE PROCEDURE dbo.GetEmployeeCurrentPay_NoView (@BusinessEntityID INT)\r\nAS\r\nBEGIN\r\n\r\nWITH PayChanges AS(\r\nSELECT\r\n    per.BusinessEntityID,\r\n    per.FirstName,\r\n    per.LastName,\r\n    per.MiddleName,\r\n    emp.JobTitle,\r\n    pay.Rate,\r\n    pay.PayFrequency,\r\n    ROW_NUMBER() OVER (PARTITION BY per.BusinessEntityID, JobTitle, PayFrequency ORDER BY pay.RateChangeDate DESC) AS RevisionID\r\nFROM Person.Person AS per\r\nINNER JOIN HumanResources.Employee AS emp\r\n    ON per.BusinessEntityID = emp.BusinessEntityID\r\nLEFT JOIN HumanResources.EmployeePayHistoryEnlarged AS pay\r\n    ON emp.BusinessEntityID = pay.BusinessEntityID\r\nWHERE per.BusinessEntityID = @BusinessEntityID)\r\nSELECT\r\n    BusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\nFROM PayChanges\r\nWHERE RevisionID = 1;\r\n\r\nEND\r\nGO\r\n<\/pre>\n<p>By pushing the WHERE clause up into the CTE, with 316 rows we get an execution plan that shows only a couple of rows are being returned from the tables during execution.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image9.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb9.png\" width=\"644\" height=\"194\" border=\"0\" \/><\/a><\/p>\n<p>If I look at the tool-tip for the Clustered Index Seek on the EmployeePayHistoryEnlarged table, I\u2019ll see the Seek Predicate being applied to the BusinessEnityID column to filter the results.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image10.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb10.png\" width=\"644\" height=\"476\" border=\"0\" \/><\/a><\/p>\n<p>The execution statistics for the rewritten procedure also shows the reduction in logical reads and the execution time.<\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [9]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 4 ms, elapsed time = 4 ms.\r\n\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 1, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Employee'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 0 ms,  elapsed time = 4 ms.\r\n<\/pre>\n<p>I also get plan stability with this rewrite, regardless of the number of rows I have in our EmployeePayHistoryEnlarged table. At 1,160,316 rows, the execution plan is still the same, except that I can see more rows being returned from EmployeePayHistoryEnlarged to be able to identify the most recent pay rate.<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image11.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb11.png\" width=\"644\" height=\"238\" border=\"0\" \/><\/a><\/p>\n<p>The execution statistics also shows the reduction in logical reads, and the lower CPU time and execution time for the procedure.<\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [9]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 0 ms, elapsed time = 0 ms.\r\n\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 1, logical reads 25, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Employee'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 16 ms,  elapsed time = 152 ms.\r\n<\/pre>\n<h2>What was Actually the Problem?<\/h2>\n<p>Since I have three different coding aspects at play here, you are probably wondering what exactly was the root problem? If you\u00a0aren&#8217;t\u00a0wondering that, you\u00a0aren&#8217;t\u00a0alone, I\u00a0didn&#8217;t\u00a0either, but after doing a review of the article, my colleague Joe Sack pointed out that it was unclear where the problem actually was. I have to agree with Joe; so far I haven\u2019t really shown definitively where the problem is. Is it the fact that I have a CTE within a view? Let\u2019s find out. First I\u2019ll rewrite a new view that only contains a CTE, but still provides the same result as the original test query, using a sub-query to determine the MAX(RateChangeDate) from the EmployeePayHistoryEnlarged table for the BusinessEntityID and PayFrequency.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\n\/* Drop the view before creating it *\/\r\nIF OBJECT_ID(N'EmployeeCurrentPayData_NoWF') IS NOT NULL\r\nBEGIN\r\n    DROP VIEW dbo.EmployeeCurrentPayData_NoWF;\r\nEND\r\nGO\r\n\r\n\/* Create the problem view for the tests *\/\r\nCREATE VIEW dbo.EmployeeCurrentPayData_NoWF\r\nAS\r\nWITH PayChanges AS(\r\nSELECT\r\n    per.BusinessEntityID,\r\n    per.FirstName,\r\n    per.LastName,\r\n    per.MiddleName,\r\n    emp.JobTitle,\r\n    pay.Rate,\r\n    pay.PayFrequency\r\nFROM Person.Person AS per\r\nINNER JOIN HumanResources.Employee AS emp\r\n    ON per.BusinessEntityID = emp.BusinessEntityID\r\nLEFT JOIN HumanResources.EmployeePayHistoryEnlarged AS pay\r\n    ON emp.BusinessEntityID = pay.BusinessEntityID\r\nWHERE RateChangeDate = (SELECT MAX(RateChangeDate)\r\n                        FROM HumanResources.EmployeePayHistoryEnlarged AS pay2\r\n                        WHERE pay.BusinessEntityID = pay2.BusinessEntityID\r\n                          AND pay.PayFrequency = pay.PayFrequency)\r\n)\r\nSELECT\r\n    BusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\nFROM PayChanges;\r\nGO\r\n\r\n\/* Drop the stored procedure using the view before creating it *\/\r\nIF OBJECT_ID(N'GetEmployeeCurrentPay_ViewNoWF') IS NOT NULL\r\nBEGIN\r\n    DROP PROCEDURE dbo.GetEmployeeCurrentPay_ViewNoWF;\r\nEND\r\nGO\r\n\r\n\/* Create the stored procedure that uses the view to tes the problem *\/\r\nCREATE PROCEDURE dbo.GetEmployeeCurrentPay_ViewNoWF (@BusinessEntityID INT)\r\nAS\r\nBEGIN\r\n    SELECT\r\nBusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\n    FROM dbo.EmployeeCurrentPayData_NoWF\r\n    WHERE BusinessEntityID = @BusinessEntityID;\r\nEND\r\nGO\r\n<\/pre>\n<p>Now a simple test by running the new procedure and reviewing the execution plan for 1,160,316 rows:<\/p>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image12.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb12.png\" width=\"596\" height=\"75\" border=\"0\" \/><\/a><\/p>\n<pre class=\"brush: plain; gutter: false; highlight: [9]; title: ; wrap-lines: false; notranslate\" title=\"\">\r\nSQL Server parse and compile time:\r\n   CPU time = 12 ms, elapsed time = 12 ms.\r\n\r\nTable 'Employee'. Scan count 0, logical reads 2, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'EmployeePayHistoryEnlarged'. Scan count 1, logical reads 6, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\nTable 'Person'. Scan count 0, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\r\n\r\n SQL Server Execution Times:\r\n   CPU time = 0 ms,  elapsed time = 102 ms.\r\n<\/pre>\n<p>I think it is pretty apparent that using a CTE in a view\u00a0isn&#8217;t\u00a0the problem. Since I now know that the issue\u00a0isn&#8217;t\u00a0the CTE within the view, how about the window function inside of the CTE? To test this, I can take the view definition, add a filter to the query against the CTE for the BusinessEntityID, and look at the resulting plan and performance:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\r\nDECLARE @BusinessEntityID INT = 250;\r\n\r\nWITH PayChanges AS(\r\nSELECT\r\n    per.BusinessEntityID,\r\n    per.FirstName,\r\n    per.LastName,\r\n    per.MiddleName,\r\n    emp.JobTitle,\r\n    pay.Rate,\r\n    pay.PayFrequency,\r\n    ROW_NUMBER() OVER (PARTITION BY per.BusinessEntityID, JobTitle, PayFrequency ORDER BY pay.RateChangeDate DESC) AS RevisionID\r\nFROM Person.Person AS per\r\nINNER JOIN HumanResources.Employee AS emp\r\n    ON per.BusinessEntityID = emp.BusinessEntityID\r\nLEFT JOIN HumanResources.EmployeePayHistoryEnlarged AS pay\r\n    ON emp.BusinessEntityID = pay.BusinessEntityID)\r\nSELECT\r\n    BusinessEntityID,\r\n    FirstName,\r\n    LastName,\r\n    MiddleName,\r\n    JobTitle,\r\n    Rate,\r\n    PayFrequency\r\nFROM PayChanges\r\nWHERE RevisionID = 1\r\n  AND BusinessEntityID = @BusinessEntityID;\r\n<\/pre>\n<p><a href=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image13.png\"><img loading=\"lazy\" decoding=\"async\" style=\"padding-left: 0px; padding-right: 0px; padding-top: 0px; border: 0px;\" title=\"image\" alt=\"image\" src=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb13.png\" width=\"644\" height=\"108\" border=\"0\" \/><\/a><\/p>\n<p>Since I have the same plan for 1,160,316 rows as my original problem, it&#8217;s clear that the issue is the fact that the CTE has to compute the entire result set before it can filter on the BusinessEntityID. So knowing this, why would I even mention views in this post? It\u2019s quite simple, the reason I generally see problems like this occur is that the developer\/DBA wanted to simplify the code across multiple objects rather than have the same CTE fully expanded in each of the objects that needed to access the data in this manner. This is the same reason that user defined functions get used as much as they do despite being slower than set-based solutions that require more code in each of the objects that would have used the function for simplicity.<\/p>\n<p>I could test a window function inside a view, but\u00a0I&#8217;ve\u00a0already shown above that the problem is actually related to the window function inside of the CTE and where the predicate evaluation actually is being applied.<\/p>\n<h2>Summary<\/h2>\n<p>While using a CTE inside of a view\u00a0isn&#8217;t\u00a0the actual performance problem, and instead the window function inside of the CTE with filtering after computation is the cause of the performance issue. The desire to use a view to simplify code typically leads to the coding practice which results in the performance problem occurring. By changing the coding pattern to remove the view, and then pushing the WHERE clause up into the CTE definition, only the rows required will be read and computed, reducing the I\/O strain on the server, CPU usage, and providing a much more scalability to your code.<\/p>\n<p>This is the first in a series of examples I&#8217;ll be blogging about showing some of the problems I come across while solving performance issues for our clients.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Common Table Expressions (CTEs), window functions, and views are all common things in SQL Server development these days. When CTEs and window functions were introduced in SQL Server 2005, a lot of developers jumped in and began using them to solve problems and simplify code. While these tools can be a great benefit in SQL [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,49,38,39],"tags":[],"class_list":["post-1669","post","type-post","status-publish","format-standard","hentry","category-database-administration","category-performance-tuning","category-sql-server-2008","category-sql-server-2012"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.4 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias<\/title>\n<meta name=\"description\" content=\"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.\" \/>\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\/jonathan\/ctes-window-functions-and-views\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias\" \/>\n<meta property=\"og:description\" content=\"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/\" \/>\n<meta property=\"og:site_name\" content=\"Jonathan Kehayias\" \/>\n<meta property=\"article:published_time\" content=\"2013-01-25T18:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2017-04-13T16:55:33+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png\" \/>\n<meta name=\"author\" content=\"Jonathan Kehayias\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jonathan Kehayias\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/\"},\"author\":{\"name\":\"Jonathan Kehayias\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/#\\\/schema\\\/person\\\/01c10d94f3648654ef706d5e6305f69c\"},\"headline\":\"Common Table Expressions (CTEs), Window Functions, and Views\",\"datePublished\":\"2013-01-25T18:00:00+00:00\",\"dateModified\":\"2017-04-13T16:55:33+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/\"},\"wordCount\":2849,\"commentCount\":19,\"image\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/wp-content\\\/uploads\\\/2013\\\/01\\\/image_thumb.png\",\"articleSection\":[\"Database Administration\",\"Performance Tuning\",\"SQL Server 2008\",\"SQL Server 2012\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/\",\"url\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/\",\"name\":\"SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/wp-content\\\/uploads\\\/2013\\\/01\\\/image_thumb.png\",\"datePublished\":\"2013-01-25T18:00:00+00:00\",\"dateModified\":\"2017-04-13T16:55:33+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/#\\\/schema\\\/person\\\/01c10d94f3648654ef706d5e6305f69c\"},\"description\":\"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#primaryimage\",\"url\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/wp-content\\\/uploads\\\/2013\\\/01\\\/image_thumb.png\",\"contentUrl\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/wp-content\\\/uploads\\\/2013\\\/01\\\/image_thumb.png\",\"width\":354,\"height\":243},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/ctes-window-functions-and-views\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Database Administration\",\"item\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/category\\\/database-administration\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Common Table Expressions (CTEs), Window Functions, and Views\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/#website\",\"url\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/\",\"name\":\"Jonathan Kehayias - The Rambling DBA\",\"description\":\"The Rambling DBA\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/www.sqlskills.com\\\/blogs\\\/jonathan\\\/#\\\/schema\\\/person\\\/01c10d94f3648654ef706d5e6305f69c\",\"name\":\"Jonathan Kehayias\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g\",\"caption\":\"Jonathan Kehayias\"},\"sameAs\":[\"http:\\\/\\\/3.209.169.194\\\/blogs\\\/jonathan\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias","description":"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.","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\/jonathan\/ctes-window-functions-and-views\/","og_locale":"en_US","og_type":"article","og_title":"SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias","og_description":"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.","og_url":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/","og_site_name":"Jonathan Kehayias","article_published_time":"2013-01-25T18:00:00+00:00","article_modified_time":"2017-04-13T16:55:33+00:00","og_image":[{"url":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png","type":"","width":"","height":""}],"author":"Jonathan Kehayias","twitter_misc":{"Written by":"Jonathan Kehayias","Est. reading time":"14 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#article","isPartOf":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/"},"author":{"name":"Jonathan Kehayias","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/#\/schema\/person\/01c10d94f3648654ef706d5e6305f69c"},"headline":"Common Table Expressions (CTEs), Window Functions, and Views","datePublished":"2013-01-25T18:00:00+00:00","dateModified":"2017-04-13T16:55:33+00:00","mainEntityOfPage":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/"},"wordCount":2849,"commentCount":19,"image":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#primaryimage"},"thumbnailUrl":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png","articleSection":["Database Administration","Performance Tuning","SQL Server 2008","SQL Server 2012"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/","url":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/","name":"SQL Server CTEs, Window Functions, and Views - Jonathan Kehayias","isPartOf":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#primaryimage"},"image":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#primaryimage"},"thumbnailUrl":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png","datePublished":"2013-01-25T18:00:00+00:00","dateModified":"2017-04-13T16:55:33+00:00","author":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/#\/schema\/person\/01c10d94f3648654ef706d5e6305f69c"},"description":"Looks into performance problems related to Common Table Expressions (CTEs) with Window Functions and the patterns that result in this type of design.","breadcrumb":{"@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#primaryimage","url":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png","contentUrl":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-content\/uploads\/2013\/01\/image_thumb.png","width":354,"height":243},{"@type":"BreadcrumbList","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/ctes-window-functions-and-views\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/"},{"@type":"ListItem","position":2,"name":"Database Administration","item":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/category\/database-administration\/"},{"@type":"ListItem","position":3,"name":"Common Table Expressions (CTEs), Window Functions, and Views"}]},{"@type":"WebSite","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/#website","url":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/","name":"Jonathan Kehayias - The Rambling DBA","description":"The Rambling DBA","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/#\/schema\/person\/01c10d94f3648654ef706d5e6305f69c","name":"Jonathan Kehayias","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/86630e27f5deecc5c393ea57fc7c3b6a068949f4fd6b5309f81de5a276f12855?s=96&d=mm&r=g","caption":"Jonathan Kehayias"},"sameAs":["http:\/\/3.209.169.194\/blogs\/jonathan"]}]}},"_links":{"self":[{"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/posts\/1669","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/comments?post=1669"}],"version-history":[{"count":0,"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/posts\/1669\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/media?parent=1669"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/categories?post=1669"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlskills.com\/blogs\/jonathan\/wp-json\/wp\/v2\/tags?post=1669"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}