{"id":1321,"date":"2011-11-07T16:30:00","date_gmt":"2011-11-07T14:30:00","guid":{"rendered":"https:\/\/www.sqlinthewild.co.za\/?p=1321"},"modified":"2011-11-07T22:42:16","modified_gmt":"2011-11-07T20:42:16","slug":"sql-university-advanced-indexing-sorting-and-grouping","status":"publish","type":"post","link":"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/11\/07\/sql-university-advanced-indexing-sorting-and-grouping\/","title":{"rendered":"SQL University: Advanced indexing &#8211; Sorting and Grouping"},"content":{"rendered":"<p>Good day everyone and welcome to another week of SQL University. I know we\u2019re getting close to the end of the year and everyone\u2019s looking forward to a nice long vacation soaking up the sun at the beach, but a little bit of attention would be nice. Thank you.<\/p>\n<p>This week is Advanced Indexing, and I mean advanced, none of that selectivity, SARGable, predicate stuff that gets repeated all over the place. If you need a refresher on the basics before we get started, the following can be considered pre-requisite reading for this course<\/p>\n<ul>\n<li><a href=\"http:\/\/www.scarydba.com\/2010\/07\/19\/sql-university-indexes-part-the-first\/\">Introduction to Indexes, Part the First<\/a><\/li>\n<li><a href=\"http:\/\/www.scarydba.com\/2010\/07\/21\/sql-university-introduction-to-indexes-part-the-second\/\">Introduction to Indexes, Part the Second<\/a><\/li>\n<li><a href=\"http:\/\/www.scarydba.com\/2010\/07\/23\/sql-university-introduction-to-indexes-part-the-third\/\">Introduction to Indexes, Part the Third<\/a><\/li>\n<li><a href=\"http:\/\/www.scarydba.com\/2011\/04\/04\/sql-universityrecommendations-for-a-clustered-index\/\">Recommendations for a Clustered Index<\/a><\/li>\n<li><a href=\"http:\/\/www.scarydba.com\/2011\/04\/06\/sql-university-index-usage\/\">Index Usage<\/a><\/li>\n<\/ul>\n<p>There\u2019s also some additional background material available for more enthusiastic students:<\/p>\n<ul>\n<li><a href=\"http:\/\/www.sqlservercentral.com\/articles\/Indexing\/68439\/\">Introduction to Indexes<\/a><\/li>\n<li><a href=\"http:\/\/www.sqlservercentral.com\/articles\/Indexing\/68563\/\">Introduction to Indexes: Part 2 \u2013 The clustered index<\/a><\/li>\n<li><a href=\"http:\/\/www.sqlservercentral.com\/articles\/Indexing\/68636\/\">Introduction to Indexes: Part 3 \u2013 The nonclustered index<\/a><\/li>\n<li><a href=\"http:\/\/technet.microsoft.com\/en-us\/sqlserver\/gg508878.aspx\">Index Internals<\/a> (Video)<\/li>\n<li><a href=\"http:\/\/technet.microsoft.com\/en-us\/sqlserver\/gg508879.aspx\">The Clustered Index Debate<\/a> (Video)<\/li>\n<li><a href=\"http:\/\/sqlserverpedia.com\/wiki\/Index_Selectivity_and_Column_Order\">Index Selectivity and Column Order<\/a><\/li>\n<\/ul>\n<p>Right, now that the admin has been handled, let&#8217;s get straight into things. Nothing like starting at the deep end\u2026<\/p>\n<p>Most people would rightly associate indexes with where clause predicates and joins, after all, the main usage of an index is to reduce the rows in consideration for a query as fast as possible. However there\u2019s another portion of your queries that indexes can, if appropriately designed, help with \u2013 grouping and sorting.<\/p>\n<p>Sorting is an extremely expensive operation, especially on large numbers of rows. For the academics in the audience, the algorithmic complexity of sorting is above linear, the time to sort a set of data increases faster than the number of items in the list. The common <a href=\"http:\/\/en.wikipedia.org\/wiki\/Sorting_algorithm\">sorting algorithms<\/a> have an average time complexity of O(n log n). It\u2019s better than O(n<sup>2<\/sup>), but it can still hurt at the higher row counts.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/On2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;\" title=\"O(n^2)\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/On2_thumb.png\" alt=\"O(n^2)\" width=\"214\" height=\"204\" border=\"0\" \/><\/a>\u00a0<a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Onlogn.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border-width: 0px;\" title=\"O(n log n)\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Onlogn_thumb.png\" alt=\"O(n log n)\" width=\"210\" height=\"204\" border=\"0\" \/><\/a><\/p>\n<p>O(n<sup>2<\/sup>) on the left, O(n log n) on the right (Thanks to <a href=\"http:\/\/www.quickmath.com\">QuickMath<\/a>)<\/p>\n<p>Right, the non-academics can wake up now.<\/p>\n<p>The other reason that sorting hurts is that it needs a memory grant to do the sort. If there\u2019s a shortage of memory the query could have to wait a while for the memory to be granted and, if the optimiser mis-estimates the number of rows to be sorted, the memory grant could be insufficient and the sort would have to spill into TempDB. You don\u2019t want that happening.<\/p>\n<p>Finally, sort is a blocking operator in the execution plan (all rows must have been fetched from the source before any output can be returned), and so the client won\u2019t start seeing rows returned until the entire sort has been completed. This can make the query feel like it takes longer than it really does.<\/p>\n<p>Grouping and aggregation are much the same. To aggregate one set of values based on another set of values, SQL has to get all the like values of the grouping columns together so that it can do the aggregation. That sounds suspiciously like a sort doesn\u2019t it?<\/p>\n<p>SQL doesn\u2019t always sort to do aggregations, but the alternative \u2013 hash tables \u2013 isn\u2019t exactly free (homework exercise \u2013 read up on hash tables)<\/p>\n<p>So for both sorting and grouping, the query processor\u2019s job would be a lot easier if there was some way that it could get the data ordered by the sorting or grouping columns without having to do the work of actually sorting. Sounds impossible? No.<\/p>\n<p><!--more--><\/p>\n<p>Enter indexes. The b-tree structure of an index makes it great for quickly finding matching rows, but today we\u2019re more interested in the leaf-level of the index than the upper levels. The leaf level of an index is logically ordered by the index key (not physically ordered, let\u2019s stop that myth right here please).<\/p>\n<p>The leaf level of an index is logically ordered by the index key columns. So, if the index leaf level is read, it can return the data in the order of the index key. If that order is what the query processor needs in order to process a sort or grouping, then there\u2019s no need for an expensive sort to be done, the underlying order can be leveraged.<\/p>\n<p>Now, before someone misquotes me and goes off crying out that data is always returned in the order of the index used, no it is not. If there\u2019s no order by on a query, there\u2019s no guarantee of order regardless of indexes. However if there is an order by, the query optimiser and query processor may be able to utilise the underlying index order and avoid the cost of actually sorting the data.<\/p>\n<p>Ok, enough theory for now, let\u2019s look at some practical examples. We\u2019ll use AdventureWorks here and I\u2019m going to use the TransactionHistoryArchive table (in the Product schema)<\/p>\n<p>Firstly a simple (and silly) example:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT ProductID, TransactionDate, ActualCost\r\n FROM Production.TransactionHistoryArchive\r\n ORDER BY ActualCost<\/pre>\n<p>This one\u2019s not very realistic, but it\u2019s a nice simple one to start with. Give me all the rows in the table ordered by the ActualCost column. There\u2019s no filter here so many would say that indexes can\u2019t help here, and it\u2019s true that an index can\u2019t help with finding rows (because all are required), but it can help.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"SortExample1\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample1_thumb.png\" alt=\"SortExample1\" width=\"484\" height=\"160\" border=\"0\" \/><\/a><\/p>\n<blockquote><p>Table &#8216;TransactionHistoryArchive&#8217;. Scan count 1, logical reads 1419, physical reads 0.<\/p>\n<p>SQL Server Execution Times:<br \/>\nCPU time = 421 ms,\u00a0 elapsed time = 4855 ms.<\/p><\/blockquote>\n<p>421ms of CPU time, with an estimated cost of 93% for the sort. Let\u2019s see if we can make this any better.<\/p>\n<p>If I create an index on ActualCost, that gives SQL the option of scanning the index (scan, because there\u2019s no seek predicate) to retrieve the data in the order of the ActualCost column. I\u2019m going to have to make it a covering index, as there is no way at all that SQL will willingly do key lookups for every single row of the table.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE NONCLUSTERED INDEX idx_TransactionHistoryArchive_ACtualCost\r\n ON Production.TransactionHistoryArchive (ActualCost)\r\n INCLUDE (ProductID, TransactionDate)<\/pre>\n<p>Let\u2019s see how that\u2019s changed the query\u2019s execution.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"SortExample2\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample2_thumb.png\" alt=\"SortExample2\" width=\"364\" height=\"94\" border=\"0\" \/><\/a><\/p>\n<blockquote><p>Table &#8216;TransactionHistoryArchive&#8217;. Scan count 1, logical reads 682, physical reads 0.<\/p>\n<p>SQL Server Execution Times:<br \/>\nCPU time = 47 ms,\u00a0 elapsed time = 4073 ms.<\/p><\/blockquote>\n<p>The reads have dropped slightly, because we\u2019re now scanning a nonclustered index which is smaller than the clustered index, but that\u2019s not the main point here. The CPU usage has dropped by around a factor of 8. 421ms down to 47ms. If this was a critical query that ran several times a minute then this change could make a nice improvement to throughput and overall CPU usage.<\/p>\n<p>That was a silly example, let\u2019s try for something a little more complex:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT\u00a0 ProductID ,\r\nTransactionDate ,\r\nTransactionType ,\r\nQuantity ,\r\nActualCost\r\nFROM Production.TransactionHistoryArchive\r\nWHERE TransactionType = 'S'\r\nAND ReferenceOrderID = 51739\r\nORDER BY TransactionDate<\/pre>\n<p>If we look at the execution plan, there\u2019s already an index in use. There\u2019s an index on ReferenceOrderID, but it\u2019s clearly not as good as it could be.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample3.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"SortExample3\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample3_thumb.png\" alt=\"SortExample3\" width=\"484\" height=\"144\" border=\"0\" \/><\/a><\/p>\n<blockquote><p>Table &#8216;TransactionHistoryArchive&#8217;. Scan count 1, logical reads 222, physical reads 0.<\/p>\n<p>SQL Server Execution Times:<br \/>\nCPU time = 0 ms,\u00a0 elapsed time = 1 ms.<\/p><\/blockquote>\n<p>Well, it\u2019s not exactly taking ages on the CPU (it&#8217;s a tiny resultset), but this can still be better than it is. The key lookup is there because the existing index is only on two columns \u2013 ReferenceOrderID and ReferenceOrderLineID. I can\u2019t modify that index for this query without potentially breaking some other query\u2019s use of it, so I\u2019ll create a new index.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE NONCLUSTERED INDEX idx_TransactionTypeReferenceOrderID\r\nON Production.TransactionHistoryArchive (TransactionType, ReferenceOrderID)\r\nINCLUDE (ProductID, TransactionDate, Quantity, ActualCost)<\/pre>\n<p>Why TransactionType first? Come back on Friday and I\u2019ll be discussing that.<\/p>\n<p>Plan\u2019s now much simpler, the key lookup has gone, and now the sort is the majority of the cost<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample4.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"SortExample4\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample4_thumb.png\" alt=\"SortExample4\" width=\"484\" height=\"122\" border=\"0\" \/><\/a><\/p>\n<p>The key to getting rid of that sort is to understand that a multi-column index is sorted by the entire key. So, if we have an index on Col1, Col2, then for rows where Col1 has the same value, those rows are listed within the index ordered by Col2. In other words, if we read the leaf level of that index it would look like this:<\/p>\n<pre>Col1      Col2\r\nA         1\r\nA         3\r\nA         8\r\nB         4\r\nB         8\r\nB         9\r\nB         14\r\nC         0\r\n\\C         1<\/pre>\n<p>And so on and so on. Hence, if I filtered that by Col1 = B, the resulting rows could be returned ordered by Col2 with no additional work.<\/p>\n<p>So in the above case, if I add the sort column (TransactionDate) as a key column in the index (it\u2019s currently an Include column), then once the filter on TransactionType and ReferenceOrderID is done, the qualifying rows can be read in order of TransactionDate.<\/p>\n<p>Let\u2019s drop the index we created and create one with TransactionDate as an additional key column.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE NONCLUSTERED INDEX idx_TransactionTypeReferenceOrderID\r\nON Production.TransactionHistoryArchive (TransactionType, ReferenceOrderID, TransactionDate)\r\nINCLUDE (ProductID, Quantity, ActualCost)<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample5.png\"><img loading=\"lazy\" decoding=\"async\" style=\"display: inline; border-width: 0px;\" title=\"SortExample5\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/SortExample5_thumb.png\" alt=\"SortExample5\" width=\"364\" height=\"100\" border=\"0\" \/><\/a><\/p>\n<p>Success, the sort has gone!<\/p>\n<p>One last example, then we\u2019ll take a brief look at group by before finishing up for the day.<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT\u00a0 ProductID ,\r\nTransactionDate ,\r\nTransactionType ,\r\nQuantity ,\r\nActualCost\r\nFROM Production.TransactionHistoryArchive\r\nWHERE ActualCost &gt; 2500\r\nORDER BY TransactionDate<\/pre>\n<p>Can I use the same technique here to remove the need for a sort?<\/p>\n<p>Let&#8217;s look at that simplistic index that I described earlier and see how the inequality would play out.<\/p>\n<pre>Col1      Col2\r\nA         1\r\nA         3\r\nA         8\r\nB         4\r\nB         8\r\nB         9\r\nB         14\r\nC         0\r\nC         1<\/pre>\n<p>If I filter that for Col1 &gt; &#8216;A&#8217;, are the resultant rows ordered by Col2?<\/p>\n<p>No, because the filter on Col1 is an inequality, a filter that returns multiple different values of Col1, the results aren&#8217;t ordered by the second column and hence we can&#8217;t\u00a0 use an index here to both support the filter and the order.<\/p>\n<p>Given this one, we could create an index to support the filter and have SQL sort the rows that qualified or we could create an index to support the order by and have SQL scan that and filter out rows that don&#8217;t match.<\/p>\n<p>In general, the first option is the one that will be best in the majority of cases. The primary use of an index is to locate rows and filter resultsets. There are cases where the alternative may be appropriate, if the vast majority of the rows in the table qualify for the filter then it may be more optimal to support the sort and let SQL scan and filter. Definitely not the usual case though.<\/p>\n<p>That about wraps up sorts, now for a quick look at group by.<\/p>\n<p>As mentioned earlier, SQL has two ways to process grouping, it can do what is called a Stream Aggregate or it can use a hash table and do a Hash Aggregate. Stream Aggregate requires that the resultset be sorted in the order of the grouping columns. Well, given that fact and that we&#8217;ve spend a lot of time showing how to use indexes to support a sort, this should be quick and easy.<\/p>\n<p>Let&#8217;s dive straight into some examples, because the theory is the same as for the sort described earlier<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT ProductID, SUM(ActualCost) AS TotalCostPerProduct\r\nFROM Production.TransactionHistoryArchive\r\nGROUP BY ProductID<\/pre>\n<p>This currently executes as a hash aggregate.<\/p>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Grouping1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"Grouping1\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Grouping1_thumb.png\" alt=\"Grouping1\" width=\"364\" height=\"84\" border=\"0\" \/><\/a><\/p>\n<blockquote><p>Table &#8216;Worktable&#8217;. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0.<\/p>\n<p>Table &#8216;TransactionHistoryArchive&#8217;. Scan count 1, logical reads 1419, physical reads 0.<\/p>\n<p>SQL Server Execution Times:<\/p>\n<p>CPU time = 47 ms,\u00a0 elapsed time = 46 ms.<\/p><\/blockquote>\n<p>If we want a stream aggregate, then we need to get the rows entering the aggregation ordered by ProductID. SQL is not going to willingly sort the entire resultset (the hash aggregate is cheaper), so the only way we&#8217;re going to get a stream aggregate (other than with a hint) is by adding an index so that the data can be read from the index already ordered. There&#8217;s no filter here so it&#8217;s a straightforward index<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">CREATE NONCLUSTERED INDEX idx_TransactionHistoryArchive_ProductID\r\nON Production.TransactionHistoryArchive (ProductID)\r\nINCLUDE (ActualCost)<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Grouping2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"Grouping2\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Grouping2_thumb.png\" alt=\"Grouping2\" width=\"364\" height=\"73\" border=\"0\" \/><\/a><\/p>\n<blockquote><p>Table &#8216;TransactionHistoryArchive&#8217;. Scan count 1, logical reads 482, physical reads 0.<\/p>\n<p>SQL Server Execution Times:<\/p>\n<p>CPU time = 31 ms,\u00a0 elapsed time = 34 ms.<\/p><\/blockquote>\n<p>Not a massive improvement, but the principle is there.<\/p>\n<p>Now, for homework, take these two queries and see firstly if it is possible to create an index to support both the filter and the group by and, if so, identify what that index is.<\/p>\n<p><strong>Question 1<\/strong><\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT\u00a0 ReferenceOrderID\r\nProductID ,\r\nSUM(Quantity) AS TotalQuantity ,\r\nSUM(ActualCost) AS TotalCost\r\nFROM Production.TransactionHistoryArchive\r\nWHERE TransactionType = 'S'\r\n GROUP BY ReferenceOrderID, ProductID<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Homework1.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"Homework1\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Homework1_thumb.png\" alt=\"Homework1\" width=\"484\" height=\"78\" border=\"0\" \/><\/a><\/p>\n<p><strong>Question 2<\/strong><\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">SELECT ReferenceOrderID ,MIN(ActualCost)|\r\nFROM Production.TransactionHistoryArchive AS tha\r\nWHERE TransactionDate &gt; '2004-01-01'\r\nGROUP BY ReferenceOrderID<\/pre>\n<p><a href=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Homework2.png\"><img loading=\"lazy\" decoding=\"async\" style=\"background-image: none; padding-left: 0px; padding-right: 0px; display: inline; padding-top: 0px; border: 0px;\" title=\"Homework2\" src=\"https:\/\/www.sqlinthewild.co.za\/wp-content\/uploads\/2011\/11\/Homework2_thumb.png\" alt=\"Homework2\" width=\"484\" height=\"78\" border=\"0\" \/><\/a><\/p>\n<p>Edit: And (as a late clarification) assume that the filter on transaction date is not always the same date.<\/p>\n<p>Enough for today. Same time, same place Wednesday for a look at indexes on part of a table.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Good day everyone and welcome to another week of SQL University. I know we\u2019re getting close to the end of the year and everyone\u2019s looking forward to a nice long vacation soaking up the sun at the beach, but a&#8230; <a class=\"read-more-button\" href=\"https:\/\/www.sqlinthewild.co.za\/index.php\/2011\/11\/07\/sql-university-advanced-indexing-sorting-and-grouping\/\">(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_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":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[24,15,16],"tags":[],"class_list":["post-1321","post","type-post","status-publish","format-standard","hentry","category-indexes","category-sql-server","category-syndication"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p7h6n-lj","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1321","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=1321"}],"version-history":[{"count":7,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1321\/revisions"}],"predecessor-version":[{"id":1335,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/posts\/1321\/revisions\/1335"}],"wp:attachment":[{"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/media?parent=1321"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/categories?post=1321"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.sqlinthewild.co.za\/index.php\/wp-json\/wp\/v2\/tags?post=1321"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}