{"id":101600,"date":"2024-04-12T10:34:31","date_gmt":"2024-04-12T10:34:31","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=101600"},"modified":"2026-04-14T11:37:19","modified_gmt":"2026-04-14T11:37:19","slug":"snake-draft-sorting-in-sql-server-part-1","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/snake-draft-sorting-in-sql-server-part-1\/","title":{"rendered":"Snake Draft Sorting in SQL Server: Distributing Workloads Evenly (Part 1)"},"content":{"rendered":"<div><strong>Snake draft sorting is a technique for distributing processing tasks across workers or time windows as evenly as possible &#8211; borrowed from sports fantasy draft order, where picks alternate direction each round to prevent any one team from getting all the best picks consecutively. In SQL Server, the same principle applies to workload distribution: instead of processing tasks in sequential order (which can overload one worker with all the heavyweight tasks), you assign tasks in a serpentine pattern so each worker gets a balanced mix of expensive and cheap operations. This article introduces the T-SQL implementation with a worked index rebuild example.<\/strong><\/div>\n<div>\u00a0<\/div>\n<div style=\"color: black; padding: 4px 8px; margin: 4px 0 12px 0; border: 1px solid #e00; background: #f7f7f7; font-size: 0.9rem;\">Part of a series: [\u00a0 <b style=\"color: #999;\">Part 1<\/b> \u00a0|\u00a0 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/snake-draft-sorting-in-sql-server-part-2\" rel=\"noopener\">Part 2<\/a> \u00a0|\u00a0 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/snake-draft-sorting-in-sql-server-part-3\" rel=\"noopener\">Part 3<\/a> \u00a0|\u00a0 <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/snake-draft-sorting-in-sql-server-part-4\/\" rel=\"noopener\">Part 4<\/a> \u00a0]<\/div>\n<p>I recently had a restore job where I needed to split the work up into multiple parallel processes (which I&#8217;ll refer to here as &#8220;threads&#8221;). I wanted to balance the work so that the duration was something significantly less than the sum of the restore times.<\/p>\n<p>Imagine a job that loops through and restores each database in sequence (let&#8217;s start with just four):<\/p>\n<p><a title=\"Timeline for four restores in sequence\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/serial-backups.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 100%;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/serial-backups.png\" alt=\"Timeline for four restores in sequence\" \/><\/a><\/p>\n<p>The restores are not finished (allowing the next step(s) to start) until the <em>sum<\/em> of the restore times for all four databases.<\/p>\n<p>Instead of one job, imagine <em>four<\/em> jobs that all start at roughly the same time, each restoring <em>one<\/em> of those four databases (assuming the server can handle the extra work):<\/p>\n<p><a title=\"Timeline for same four restores in parallel\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/parallel-backups.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 100%;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/parallel-backups.png\" alt=\"Timeline for same four restores in parallel\" \/><\/a><\/p>\n<p>With this strategy, all of the restores are finished by the time it takes the <em>longest<\/em> restore to finish, instead of the sum of all four restore times. This allows subsequent tasks to start a lot sooner, and frees up the server for other work.<\/p>\n<h3>Well, duh<\/h3>\n<p>This concept is not overly clever or imaginative on my part &#8211; this has been the premise of parallel computing since computing was a thing and, more generally, division of labor has been a strategy throughout recorded history.<\/p>\n<p>That said, the above was a simplistic example.<\/p>\n<p>In reality, we have a lot more databases, in a periodic job where we restore a slew of backups on an internal server (and then perform further processing once they&#8217;re all finished). Since this is a supporting server and not production-facing, it is configured to restore and support all of the production databases, with no other workload, so it has generous headroom for additional work. While I didn&#8217;t want to overwhelm the server, I wanted the entire set of restores to happen faster in parallel than serially. But how could I split the work up? Alphabetical? Random? Those might lead to quite skewed work &#8211; all the biggest databases could be alphabetically together or just randomly placed on the same thread.<\/p>\n<h3>Borrowing from sports<\/h3>\n<p>I play fantasy football. To start each season, there is a draft, where players are randomly assigned draft order. We&#8217;ve started some seasons with a &#8220;snake&#8221; draft, to make the selection of players <em>most<\/em> fair and balanced. A snake draft works like this (a simple example with four players):<\/p>\n<ul>\n<li>First pick in the first round picks last in the second round.<\/li>\n<li>Second pick in the first round picks third in the second round.<\/li>\n<li>Third pick in the first round picks second in the second round.<\/li>\n<li>Last pick in the first round picks first in the second round.<\/li>\n<li>Then, in the third round, it reverts to the original order: The first pick from the first round picks first again (giving them two picks back-to-back).<\/li>\n<\/ul>\n<p>For that reason, it&#8217;s called a &#8220;snake.&#8221; To illustrate:<\/p>\n<p><a title=\"A snake draft\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/fantasy-snake-draft.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 100%;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/fantasy-snake-draft.png\" alt=\"A snake draft\" \/><\/a><\/p>\n<p>This is also <em>slightly<\/em> reminiscent of playoff brackets or draws in tennis, where the first round starts with the lowest seed playing the highest, the second lowest playing the second highest, and so on.<\/p>\n<p>I borrowed from this technique to split up my restore jobs to make the &#8220;amount of work&#8221; most equitable. I based this by simply ranking by size of the source data files, but you could also use compressed backup size, historical backup times, random distribution (<code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">ORDER BY NEWID()<\/code>), or even segment alphabetically (say, if you didn&#8217;t want the <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">Aaron<\/code> database to always finish first, or the <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">Zorro<\/code> database to always finish last). You might also think about whether there is, say, one database that is disproportionately larger or more resource-intensive than all the others, and manually put that database on a thread all its own.<\/p>\n<p>Again, I didn&#8217;t want to overwhelm the server, perhaps causing all the parallel work to slow things down and defeat the purpose. I landed on 4 &#8220;threads,&#8221; knowing that I might be able to increase that after testing, but I also conceded that it would probably be impossible to get the distribution so perfect that all 4 threads finished at exactly the same time. Maybe something for machine learning to figure out down the road?<\/p>\n<p>Meanwhile, I thought, &#8220;I can rank databases by total data size descending, and put the biggest file on thread 1, the second biggest on thread 2, the third on thread 3, and the fourth on thread 4. Then, the 5th would go on thread 4, the 6th on thread 3, the 7th on thread 2, and the 8th on thread 1. Then start the cycle over.&#8221; This way, I could spread the work most fairly across the four threads, like my snake draft example above.<\/p>\n<p>But I sure didn&#8217;t want to rank those databases by hand &#8211; or even name them, since there are nearly 400. So, I created my original list from <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">sys.databases<\/code> on the source server. On my test server, there are 9 databases, with some ties.<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> SELECT d.name, size = SUM(size)\n   FROM sys.master_files AS f\n   INNER JOIN sys.databases AS d\n      ON f.database_id = d.database_id\n   WHERE f.type_desc &lt;&gt; N'LOG'\n     AND d.database_id &gt; 4\n   GROUP BY d.name;\n<\/pre>\n<p>Then I added the row number so I know the exact ordering by size descending:<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> SELECT d.name, size = SUM(size), \n     r = ROW_NUMBER() OVER (ORDER BY SUM(size) DESC, d.name)\n   FROM sys.master_files AS f\n   INNER JOIN sys.databases AS d\n      ON f.database_id = d.database_id\n   WHERE f.type_desc &lt;&gt; N'LOG'\n     AND d.database_id &gt; 4\n   GROUP BY d.name;<\/pre>\n<p>I worked on this on my local instance. On the left, the results from the first query; on the right, the second query, showing the added row number (and scribbled with the desired thread I wanted for each row):<\/p>\n<p><a title=\"Query results with row number and desired thread\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/thread-query-results-1.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/thread-query-results-1.png\" alt=\"Query results with row number and desired thread\" \/><\/a><\/p>\n<p>Next, to make further queries simpler, I created a view in my <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">Utility<\/code> database on the source system (and added a clause to exclude self):<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> CREATE VIEW dbo.DatabasesBySize\n AS\n   SELECT d.name, size = SUM(size), \n     RowNum = ROW_NUMBER() OVER (ORDER BY SUM(size) DESC, d.name)\n   FROM sys.master_files AS f\n   INNER JOIN sys.databases AS d\n      ON f.database_id = d.database_id\n   WHERE f.type_desc &lt;&gt; N'LOG'\n     AND d.database_id &gt; 4\n     AND d.name &lt;&gt; DB_NAME()\n   GROUP BY d.name;<\/pre>\n<p>To get the desired outcome, we have to think of each group of 4 (or whatever <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">ThreadCount<\/code> you want) as independent. Within each group, depending on whether the group is odd or even, we can order within that group either descending (for odd) or ascending (for even). Visually:<\/p>\n<p><a title=\"Explanation of ranking within each group\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/thread-query-results-2.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/thread-query-results-2.png\" alt=\"Explanation of ranking within each group\" \/><\/a><\/p>\n<p>This certainly sounds like a problem that window functions can solve. First, let&#8217;s take advantage of SQL Server&#8217;s integer division to assign groups of 4:<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> DECLARE @ThreadCount tinyint = 4;\n\n SELECT name, size, r = RowNum, \n        GroupNo = RowNum\/@ThreadCount\n FROM dbo.DatabasesBySize;<\/pre>\n<p>This produces an off-by-one error, though, because <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">1\/4 = 0<\/code>, <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">2\/4 = 0<\/code>, <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">3\/4 = 0<\/code>, and <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">4\/4 = 1<\/code>:<\/p>\n<p><a title=\"Query results with off-by-1\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-off-by-1.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-off-by-1.png\" alt=\"Query results with off-by-1\" \/><\/a><\/p>\n<p>Thankfully, this is easy to fix. To get the first row in the first group, and the fourth row into the second group, we can just subtract one from the row number to force nicer alignment:<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> DECLARE @ThreadCount tinyint = 4;\n\n SELECT name, size, r = RowNum, \n        GroupNo = (RowNum-1)\/@ThreadCount\n -----------------^^^^^^^^^^\n FROM dbo.DatabasesBySize;<\/pre>\n<p>Results:<\/p>\n<p><a title=\"Query results with off-by-1 fixed\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-off-by-1-fix.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-off-by-1-fix.png\" alt=\"Query results with off-by-1 fixed\" \/><\/a><\/p>\n<p>The trickier bit is to flip the order between descending and ascending within each group. Since we are limited to two options, we could make a decision on <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">ORDER BY<\/code> depending on whether the <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">GroupNo<\/code> is odd or even:<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1\"> DECLARE @ThreadCount tinyint = 4;\n\n SELECT name, size, r = RowNum, \n        GroupNo = (RowNum-1)\/@ThreadCount,\n        Odd     = (RowNum-1)\/@ThreadCount % 2\n FROM dbo.DatabasesBySize;<\/pre>\n<p>Results:<\/p>\n<p><a title=\"Query results with odd\/even indicator\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-odd.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-odd.png\" alt=\"Query results with odd\/even indicator\" \/><\/a><\/p>\n<p>With that information, I can use a <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">RANK()<\/code>, partitioned by <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">GroupNo<\/code>, ordered by either the row number (<code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">RowNum<\/code>) if the <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">GroupNo<\/code> is even, or the row number descending (<code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">-RowNum<\/code>) if the <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">GroupNo<\/code> is odd (with <code style=\"font-family: consolas; background: #e4e4e4; padding: 3px 4px 1px 4px; border-radius: 3px;\">name<\/code> as a deterministic tie-breaker):<\/p>\n<pre class=\"theme:tomorrow-night font:consolas font-size:14 line-height:16 decode-attributes:false tab-convert:true lang:tsql decode:true whitespace-before:1 whitespace-after:1 \"> DECLARE @ThreadCount tinyint = 4;\n\n SELECT name, size, r = RowNum, GroupNo, Odd, \n   GroupRank = RANK() OVER \n   (\n     PARTITION BY GroupNo \n     ORDER BY CASE Odd \n       WHEN 1 THEN -RowNum ELSE RowNum END, name\n   )\n FROM\n (\n   SELECT name, size, RowNum, \n          GroupNo = (RowNum-1)\/@ThreadCount,\n          Odd     = (RowNum-1)\/@ThreadCount % 2\n   FROM dbo.DatabasesBySize\n ) AS sq\n ORDER BY r;<\/pre>\n<p>And we get the output we want:<\/p>\n<p><a title=\"Query results with GroupRank (ThreadID)\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-group-rank.png\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-group-rank.png\" alt=\"Query results with GroupRank (ThreadID)\" \/><\/a><\/p>\n<p>You can also change the thread count; here is the result with a thread count of 3:<\/p>\n<p><a title=\"Query results with GroupRank (ThreadID) for a different @ThreadCount\" href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-group-rank-3-threads.pn\" target=\"_blank\" rel=\"noopener\"><img decoding=\"async\" style=\"width: 75%; min-width: 320px;\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/03\/snake-with-group-rank-3-threads.png\" alt=\"Query results with GroupRank (ThreadID) for a different @ThreadCount\" \/><\/a><\/p>\n<p>This general technique may sound similar to other solutions: companies like Amazon use a packing algorithm to minimize wasted space in a shipment, to reduce size and cost. Though, in my experience, it does not always work very well when your shipment has a small number of items, since they will often arrive in a comically oversized box. You might also remember playing Tetris, or may have even taken a break from playing Tetris to read this post.<\/p>\n<h3>Summary<\/h3>\n<p>In this post, I&#8217;ve shown how you can divide your heavy processing tasks up into <em>relatively<\/em> equal units of work. Stay tuned for my next post, where I&#8217;ll demonstrate a few ways to <em>use<\/em> this information.<\/p>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs: Snake draft sorting in SQL Server, part 1<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is snake draft sorting in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Snake draft sorting assigns rows or tasks to processing groups in a serpentine order &#8211; first group gets row 1, second gets row 2, and so on down the list, then the order reverses: the last group gets the next row, working back up. This ensures each group receives a balanced mix of early-listed and late-listed items rather than clustering all &#8216;top&#8217; items into the first group. The name comes from fantasy sports drafts where picks alternate direction each round.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. When should I use snake draft sorting in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use snake draft sorting when you have a list of tasks ranked by some cost or weight &#8211; index rebuilds ranked by fragmentation, tables ranked by row count, jobs ranked by runtime &#8211; and you need to distribute them across multiple workers, maintenance windows, or parallel execution slots such that no single slot gets an overwhelming concentration of heavy work. It is a simple, effective alternative to random distribution when the cost ordering of tasks is known.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How do I implement snake draft distribution in T-SQL?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Assign a row number to each task ordered by cost descending. Then use modular arithmetic to determine which group each row belongs to: group = (row_number &#8211; 1) % number_of_groups. Reverse the within-group ordering for even-numbered rounds by using a CASE expression or by inverting the sort within each group assignment. The result is a table where each group contains a balanced distribution of high-cost, medium-cost, and low-cost tasks.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. What is the difference between snake draft and round-robin distribution in SQL Server?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Round-robin distribution assigns tasks in strict rotation: task 1 \u2192 group 1, task 2 \u2192 group 2, etc. If tasks are sorted by cost, this gives group 1 all the most expensive tasks (positions 1, n+1, 2n+1&#8230;). Snake draft reverses direction each round, so the most expensive task goes to group 1 in round 1, but the most expensive remaining task in round 2 goes to the last group. This significantly improves balance when task costs are skewed.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Snake draft sorting in SQL Server divides processing tasks into evenly-distributed groups by assigning work in a serpentine order. Part 1 introduces the concept with a T-SQL example for distributing index rebuild workloads across maintenance windows.&hellip;<\/p>\n","protected":false},"author":341115,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[143527,53,143531],"tags":[4151],"coauthors":[158980],"class_list":["post-101600","post","type-post","status-publish","format-standard","hentry","category-database-administration-sql-server","category-featured","category-t-sql-programming-sql-server","tag-sql-server"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/101600","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\/341115"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=101600"}],"version-history":[{"count":49,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/101600\/revisions"}],"predecessor-version":[{"id":109597,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/101600\/revisions\/109597"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=101600"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=101600"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=101600"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=101600"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}