{"id":888,"date":"2010-05-20T00:00:00","date_gmt":"2010-05-20T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/operator-of-the-week-spools-eager-spool\/"},"modified":"2021-08-16T15:02:16","modified_gmt":"2021-08-16T15:02:16","slug":"operator-of-the-week-spools-eager-spool","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/learn\/operator-of-the-week-spools-eager-spool\/","title":{"rendered":"Operator of the Week &#8211; Spools, Eager Spool"},"content":{"rendered":"<div id=\"pretty\">\n<h2>Spool Operators<\/h2>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool1.JPG\" alt=\"1034-Spool1.JPG\" \/><br \/>\nEager Spool<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool2.JPG\" alt=\"1034-Spool2.JPG\" \/><br \/>\nLazy Spool<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool3.JPG\" alt=\"1034-Spool3.JPG\" \/><br \/>\nRow Count Spool<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool4.JPG\" alt=\"1034-Spool4.JPG\" \/><br \/>\nTable Spool<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool5.JPG\" alt=\"1034-Spool5.JPG\" \/><br \/>\nNon-Clustered Index Spool<\/p>\n<p class=\"start\">This week we&#8217;ll be featuring the Spool showplan operator. There are five types of Spool operations, each with its own behavior, and idiosyncrasies, but they all share in common the way that they save their intermediate query results on the <b>TempDB<\/b> database, and use this temporary area to search a value.<\/p>\n<p>There are many tricks that Query Optimizer uses to avoid any logical problems and to perform queries better: The spool operators are a good example of this. This month, we&#8217;ll see all these types of Spool operations, one by each week.<\/p>\n<p>A spool reads the data and saves it on <b>TempDB<\/b>. This process is used whenever the Optimiser knows that the <a href=\"http:\/\/www.simple-talk.com\/sql\/t-sql-programming\/13-things-you-should-know-about-statistics-and-the-query-optimizer\/\">density<\/a> of the column is high and the intermediate result is very complex to calculate. If this is the case, SQL makes the computation once, and stores the result in the temporary space so it can search it later.<\/p>\n<p>The spool operators are always used together with another operator. As it stores values, it needs to know what these values are, and so it must receive them from another operator. For instance, one spool can be used together with a <b>clustered index scan<\/b> in order to save the rows read by the scan. These rows can then be read back to an <b>update<\/b>, or a<b> select <\/b>operator.<\/p>\n<p>A quite simple graphic representation about what I said above could be the following picture:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool6.JPG\" alt=\"1034-Spool6.JPG\" \/><\/p>\n<p>In the example represented by these icons above, the order of execution is: <b>Clustered Index Scan<\/b> sends rows to <b>Spool<\/b> and the <b>Select <\/b>reads all of these rows directly from the Spool.<\/p>\n<h2>Eager Spool<\/h2>\n<p>We&#8217;ll start with the Eager Spool Operator. The role of the Eager Spool is to catch <b>all<\/b> the rows received from another operator and store these rows in <b>TempDB<\/b>. The word &#8220;eager&#8221; \u00a0means that the operator will read ALL rows from the previously operator at one time. In other words, it will take the entire input, storing each row received.<\/p>\n<p>In our simple sample (SCAN -&gt; EAGER SPOOL -&gt; SELECT) the eager will works like this: When the scanned rows are passed to Spool then it gets all the rows, \u00a0not one row at the time you&#8217;ll notice but the entire scan, and keeps them in a hidden temporary table.<\/p>\n<p>Knowing that, we could say that The <b>Eager Spool<\/b> is a blocking operator. I believe that I haven&#8217;t yet written about the difference between blocking operators and non-blocking operators. Let&#8217;s open a big parenthesis here.<\/p>\n<p>There are two categories of showplan operators: The &#8220;non-blocking&#8221; operators and the &#8220;blocking&#8221; operators or &#8220;stop-and-go&#8221;:<\/p>\n<ul>\n<li>Non-blocking operators are those that read one row from their input and return the output for each read row. In the method known as GetRow(), the operator executes its function and returns the row to the next operator as soon as the first row has been read.\n<p>The &#8220;nested loop&#8221; operator is a good example of this &#8216;non-blocking&#8217; behavior: When the first row is read; SQL needs to perform a Join with the outer table, if the outer table joins with the inner table than a row is returned. This process repeats until the end of the table. To each received row, SQL tries to join the rows and return the required values.<\/li>\n<li>A blocking operator needs to read all the rows from its input to perform some action and then return the data. A classic sample of a blocking operator is the <b>Sort <\/b>operator, it needs to read all rows, sort the data and then return the ordered rows. The execution of the query will wait until all rows to be read and ordered, before continuing with the command.<\/li>\n<\/ul>\n<h2>The Halloween Problem<\/h2>\n<p>There is \u00a0a very interesting classic computing problem, called &#8220;The Halloween Problem&#8221;. Microsoft Engineers take advantage of the blocking <b>eager spool<\/b> operator to avoid this problem.<\/p>\n<p>In order to illustrate this, I will start with the following code to create a table called &#8220;Funcionarios&#8221; which means Employees in Portuguese.<\/p>\n<p>This script will create the table with three columns, <b>ID<\/b>, <b>Nome<\/b> (Name) and <b>Salario<\/b> (Salary), and populate them with some garbage data.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">USE TempDB \r\nGO \r\nSET NOCOUNT ON \r\nIF OBJECT_ID('Funcionarios') IS NOT NULL \r\n\u00a0 DROP TABLE Funcionarios \r\nGO \r\nCREATE TABLE Funcionarios(ID Int IDENTITY(1,1) PRIMARY KEY, \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Nome VarChar(30), \r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Salario Numeric(18,2));\r\nGO\r\n\u00a0\r\nDECLARE @I SmallInt \r\nSET @I = 0 \r\nWHILE @I &lt; 1000 \r\nBEGIN \r\n\u00a0 INSERT INTO Funcionarios(Nome, Salario) \r\n\u00a0 SELECT 'Fabiano', ABS(CONVERT(Numeric(18,2), (CheckSUM(NEWID()) \/ 500000.0))) \r\n\u00a0 SET @I = @I + 1 \r\nEND \r\nCREATE NONCLUSTERED INDEX ix_Salario ON Funcionarios(Salario)\r\nGO\r\n<\/pre>\n<p>This is what the data looks like.<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool7.JPG\" alt=\"1034-Spool7.JPG\" \/><\/p>\n<p>Ok: Now that we have run the script, we can return to where we left off when describing the operator Eager Spool.<\/p>\n<p>To show the functionality of the Eager Spool we&#8217;ll go back in time a bit, to the time when I was just a project being planned, but some other geeks working intensively with databases.<\/p>\n<p>It was Halloween; the cold winter&#8217;s night was black as pitch, (I really don&#8217;t know if was winter, but I thought it would sound more thrilling) and the wind howled in the trees. It was 1976 and the children was demanding &#8220;tricks or treat&#8221; in the houses. The full moon shone and illuminated the whole city when suddenly, some clouds crossed the moon making the night even more dark and gloomy. It was possible to smell and taste the tension in the air like a stretched rubber band so close to burst. People walking in the street felt that someone was observing them, and when they looked closely in their back, they see two red eyes waiting and looking out for a prey unprotected.<\/p>\n<p>Was that their imagination? Or just the wrong night to work with databases?<\/p>\n<p>Meanwhile in a place not far away, an update was started on a database by a skeleton staff, to update the salary by 10% of all employees who earned less than $ 25.000,00 dollars.\u00a0 Their feelings of impending doom increased as the query failed to complete in the expected time. When, at length, it did, they found to their horror that every employee had their pay increased to $ 25.000,00. It was the stuff of DBA nightmares.<\/p>\n<p>So begins the story of a problem known as &#8220;Halloween Problem&#8221;, IBM engineers were the first to find the problem, but several databases have suffered <a href=\"http:\/\/en.wikipedia.org\/wiki\/Halloween_Problem\">similar problems over the years<\/a>, including our lovely SQL Server as we can see <a href=\"https:\/\/blogs.msdn.microsoft.com\/craigfr\/2008\/02\/27\/halloween-protection\/\">here<\/a>. The Halloween problem happens when the write cursor interferes with the read cursor. The selection of the rows to update is affected by the actual update process. This can happen when there is a index on the particular column being updated. When the update is made, \u00a0the same row can be updated several times.<\/p>\n<p>All updates are executed in two steps, the first is the read step, and the second is the update. A read cursor identifies the rows to be updated and a write cursor performs the actual updates.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">UPDATE Funcionarios SET Salario = 0 \r\nWHERE ID = 10 \r\n<\/pre>\n<p>For the execution of this query, the first step is to locate the records that need updating, and then the second step needs to update these records. If there is an index on the column being modified (<b>Salario<\/b>) then this index also needs to be updated.<\/p>\n<p>As we know, all non-clustered indexes need to be updated when a value changes that is used in the index.<\/p>\n<p>The problem can happen if SQL Server chooses to read the data using this index during the read step. This value can then change when the second step is being performed.<\/p>\n<p>Let&#8217;s see some examples using the same type of query that was being used when the problem was first found by the IBM engineers.<\/p>\n<p>Suppose that I want to give 10% of increase salary to all employees that earn less than R$ 2000,00 reais (the Brazilian currency). I could run the following query:<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">UPDATE Funcionarios SET Salario = Salario * 1.1 \r\nFROM Funcionarios \r\nWHERE Salario &lt; 2000 \r\n<\/pre>\n<p>We have the following <b>graphical execution plan<\/b>:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool8.JPG\" alt=\"1034-Spool8.JPG\" \/><\/p>\n<p><b>Text execution plan:<\/b><\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">|--Clustered Index Update(OBJECT:([Funcionarios].[PK]), OBJECT:([Funcionarios].[ix_Salario]), SET:([Funcionarios].[Salario] = [Expr1003]))\r\n\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1016]=[Expr1016]))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1016]=CASE WHEN [Expr1007] THEN (0) ELSE (1) END))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Top(ROWCOUNT est 0)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(numeric(18,2),[Funcionarios].[Salario]*(1.1),0), [Expr1007]=CASE WHEN [Funcionarios].[Salario] = CONVERT_IMPLICIT(numeric(18,2),[Funcionarios].[Salario]*(1.1),0) THEN (1) ELSE (0) END))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Clustered Index Scan(OBJECT:([Funcionarios].[PK]), WHERE:([Funcionarios].[Salario]&lt;(2000.00)) ORDERED)\r\n<\/pre>\n<p>For now, let us just to look at the steps that I mentioned. In the <b>Clustered Index Scan<\/b>, we see that SQL selects all the rows that will be updated, and the <b>Clustered Index Update<\/b> then updates these rows in the cluster index (PK) and the non-clustered index <i>ix_salario<\/i>.<\/p>\n<p>Well, if we follow this logic, what do we predict will happen? Let&#8217;s take this a step at a time to see how this will work.<\/p>\n<p>We know that the clustered index is ordered by ID column: SQL Server is using this index to look at what rows needs to be updated, so we have the following:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool9.JPG\" alt=\"1034-Spool9.JPG\" \/><\/p>\n<p>The first selected row is the ID = 3<\/p>\n<p>After we select the row, we need to update the &#8220;<b>salario<\/b>&#8221; column.<\/p>\n<p>So, we could perform the update of the salary with the actual value * 1.1.<\/p>\n<p>After the update we have the following:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool10.JPG\" alt=\"1034-Spool10.JPG\" \/><\/p>\n<p>Note that the salary earned a 10% increase, from 80.90 to 88.99. We then continue by selecting the next row, ID = 5, and this process will then continue until the end of the selected rows.<\/p>\n<p>So far, we can see that the use of this clustered index generates no error. If \u00a0SQL Server continues with this logic until the end of the process, then all rows will be updated correctly. \u00a0What if SQL chose, instead, to use the non-clustered index <i>ix_salario<\/i> to read the rows that will be updated (first step of the update), what will happen then?<\/p>\n<p>Let&#8217;s use the same illustration that we used above. But this time we&#8217;ll force the use of the non-clustered index <i>ix_salario<\/i>.<\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">UPDATE Funcionarios SET Salario = Salario * 1.1 \r\nFROM Funcionarios WITH(INDEX=ix_Salario) \r\nWHERE Salario &lt; 2000 \r\n<\/pre>\n<p><b>Graphical execution plan:<\/b><\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool11.JPG\" alt=\"1034-Spool11.JPG\" \/><\/p>\n<p><b>Text execution plan:<\/b><\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">|--Clustered Index Update(OBJECT:([Funcionarios].[PK]), OBJECT:([Funcionarios].[ix_Salario]), SET:([Funcionarios].[Salario] = [Expr1003]))\r\n\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1016]=[Expr1016]))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1016]=CASE WHEN [Expr1007] THEN (0) ELSE (1) END))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Top(ROWCOUNT est 0)\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(numeric(18,2),[Funcionarios].[Salario]*(1.1),0), [Expr1007]=CASE WHEN [Funcionarios].[Salario] = CONVERT_IMPLICIT(numeric(18,2),[Funcionarios].[Salario]*(1.1),0) THEN (1) ELSE (0) END))\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Table Spool\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 |--Index Seek(OBJECT:([Funcionarios].[ix_Salario]), SEEK:([Funcionarios].[Salario] &lt; (2000.00)) ORDERED FORWARD)\r\n<\/pre>\n<p>You&#8217;ll see from the execution plan that, this time, after reading the data using the index <i>ix_Salario<\/i>, SQL Server uses a Blocking operator called Table Spool(Eager Spool). As I mentioned earlier in this article, when the Eager is called the first time, it will read all data and then move to the next operator. In our example, the Eager Spool writes the data returned from the index <i>ix_Salario <\/i>into a temporary table. Later, the updates do not read <i>ix_salario <\/i>any more; instead all reads are performed using the Eager Spool.<\/p>\n<p>You may would be wondering, &#8216;<i>Hey Fabiano where is the Halloween problem?<\/i>&#8216; We will get there now.<\/p>\n<p>Let&#8217;s suppose that SQL Server hadn&#8217;t used the Eager Spool operator. Let&#8217;s assume that we have the same execution plan above but without the Eager Spool operator.<\/p>\n<p>If SQL Server hadn&#8217;t used the Eager Spool to read the data, it would have read rows directly from the index <i>ix_Salario.<\/i> It will read the first row, update the value with 10%, that get the next row and so on.<\/p>\n<p>We know the index <i>ix_Salario <\/i>is ordered by column <b>Salario<\/b>, so let&#8217;s draw the things again to see what will happen.<\/p>\n<p>The data returned to the Index would be:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool12.JPG\" alt=\"1034-Spool12.JPG\" \/><\/p>\n<p>The first row is the ID 763, when the SQL Server updates the row the data in the index will be the following:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool13.JPG\" alt=\"1034-Spool13.JPG\" \/><\/p>\n<p>Now the next row is the ID 468.<\/p>\n<p>Using this data we will not have a problem. But tell me one thing, if the data was distributed like that?<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool14.JPG\" alt=\"1034-Spool14.JPG\" \/><\/p>\n<p>Well, that would be a very dangerous problem, Let&#8217;s go again, get the first row, ID = 763. Update this value with 10%, the data in the index will be the following:<\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1034-Spool15.JPG\" alt=\"1034-Spool15.JPG\" \/><\/p>\n<p>Now, get the next row, which was what? The ID 763 again? Yes, take it easy; let&#8217;s understand why the row 763 ended up in the second row. Wasn&#8217;t it in the first row?<\/p>\n<p>Yes it was, but when SQL Server updated the <b>Salario<\/b> column value by 10% it updated the non-clustered index <i>ix_Salario <\/i>(see into the execution plan, the clustered index updates <i>ix_Salario <\/i>and the PK) too, which meant that data was repositioned in the non-clustered index. The index needs to keep the data physically sorted by <b>salario<\/b>. Once a value changes, it resets this value in the balanced tree.<\/p>\n<p>The outcome would be that the employee with ID = 763 has the salary increased by 20% (well that could be good, since he calls Fabiano). The problem found at IBM, the engineers said the end of the query all employees were earning $ 25,000.00. And then they had to start to understand what had happened.<\/p>\n<p>The team at Microsoft that develops the Query Processor uses blocking operators to ensure that the read data will be the same regardless whether there is a later update. In our query example, SQL uses the SQL Eager Spool: When it needs to read the rows for the second time, it will not use the index <i>ix_Salario <\/i>but will read from the Spool. The spool has a copy of rows of index <i>ix_Salario <\/i>in <b>TempDB<\/b> database.<\/p>\n<p>Well, we saw that the Eager Spool can be used to avoid the problem known as &#8220;Halloween problem&#8221;, but it can also be used in your queries by the Query Optimizer whenever it reckons that \u00a0it pays to create a copy of the data. Keep eye to see more about the Spool operators, Next week we will describe the Lazy Spool operator.<\/p>\n<p>That&#8217;s all folks, I see you next week with more &#8220;Showplan Operators&#8221;.<\/p>\n<div class=\"note\">\n<p class=\"note\">If you missed last week&#8217;s thrilling Showplan Operator, BookMark\/Key Lookup, you can <a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/learn-sql-server\/showplan-operator-of-the-week-bookmarkkey-lookup\/\">see it here.<\/a><\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p> For the fifth part of Fabiano&#8217;s mission to describe the major Showplan Operators used by SQL Server&#8217;s Query Optimiser, he introduces the spool operators and particularly the Eager Spool, explains blocking and non-blocking and then describes how the Halloween Problem is avoided.&hellip;<\/p>\n","protected":false},"author":65554,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143525],"tags":[4178,5177,4149,5084,5162,5192,5193,4150,4151],"coauthors":[6809],"class_list":["post-888","post","type-post","status-publish","format-standard","hentry","category-learn","tag-bi","tag-fabiano-amorim","tag-learn-sql-server","tag-optimiser","tag-showplan","tag-showplan-operators","tag-spool","tag-sql","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/888","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/65554"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=888"}],"version-history":[{"count":5,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/888\/revisions"}],"predecessor-version":[{"id":72932,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/888\/revisions\/72932"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=888"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=888"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=888"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=888"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}