{"id":71671,"date":"2017-07-17T12:34:58","date_gmt":"2017-07-17T12:34:58","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=71671"},"modified":"2021-09-29T16:21:10","modified_gmt":"2021-09-29T16:21:10","slug":"sql-server-r-services-digging-r-language","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/sql-server-r-services-digging-r-language\/","title":{"rendered":"SQL Server R Services: Digging into the R Language"},"content":{"rendered":"<h4>The series so far:<\/h4>\n<ol>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-basics\/\">SQL Server R Services: The Basics<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-digging-r-language\/\">SQL Server R Services: Digging into the R Language<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-working-ggplot2-statistical-graphics\/\">SQL Server R Services: Working with ggplot2 Statistical Graphics<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-working-data-frames\/\">SQL Server R Services: Working with Data Frames<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-generating-sparklines-types-spark-graphs\/\">SQL Server R Services: Generating Sparklines and Other Types of Spark Graphs<\/a><\/li>\n<li><a href=\"https:\/\/www.red-gate.com\/simple-talk\/sql\/bi\/sql-server-r-services-working-multiple-data-sets\/\">SQL Server R Services: Working with Multiple Data Sets<\/a><\/li>\n<\/ol>\n\n<p>In SQL Server 2016, Microsoft added support for the R language in two different forms: SQL Server R Server, a stand-alone product that provides parallel processing and other performance enhancements, and SQL Server R Services, an integrated service that lets you run R scripts directly within the SQL Server environment and incorporate SQL Server data within those scripts.<\/p>\n<p>This article is the second in a series about SQL Server R Services. In the first article, I explained how to use R Services to run R scripts within the SQL Server environment. To do so, you must use the <code>sp_execute_external_script<\/code> system stored procedure to run the script, including it as a parameter value when calling the procedure. The first article provides a number of examples of how to go about doing this.<\/p>\n<p>In this article, I focus on the R script itself, using a single example that demonstrates how to generate a bar chart (also known as a <em>bar plot<\/em> in R lingo). The article walks you through the script one element at a time so you can better understand the various elements that make up the script, while gaining a foundation in many important concepts related to R scripting.<\/p>\n<p>When calling the <code>sp_execute_external_script<\/code> stored procedure, we\u2019ll take the same approach as in the first article; that is, we\u2019ll save the R script and T-SQL <code>SELECT<\/code> statement to variables (<code>@rscript<\/code> and <code>@sqlscript<\/code>, respectively). We\u2019ll then use those variables as parameter values when calling the procedure. We\u2019ll also be running the procedure within the context of the <code>AdventureWorks2014<\/code> database because it will provide our source data.<\/p>\n<p>With that in mind, take a look at the following T-SQL code, which includes all the elements necessary to generate a bar chart based on data extracted from the <code>Sales.SalesOrderHeader<\/code> and <code>Sales.Territory<\/code> tables in the <code>AdventureWorks2014<\/code> database:<\/p>\n<pre class=\"theme:ssms2012-simple-talk lang:tsql decode:true\">USE AdventureWorks2014;\r\nGO\r\n\r\nDECLARE @rscript NVARCHAR(MAX);\r\nSET @rscript = N'\r\n\r\n  # import scales and ggplot2 packages\r\n  library(scales)\r\n  library(ggplot2)\r\n\r\n  # set up report file for chart\r\n  reportfile &lt;- \"C:\\\\DataFiles\\\\SalesReport.png\"\r\n  png(filename=reportfile, width=1000, height=600)\r\n\r\n  # construct data frame\r\n  sales &lt;- InputDataSet\r\n  c1 &lt;- levels(sales$SalesTerritories)\r\n  c2 &lt;- round(tapply(sales$Subtotal, sales$SalesTerritories, sum))\r\n  salesdf &lt;- data.frame(c1, c2)\r\n  names(salesdf) &lt;- c(\"Territories\", \"Sales\")\r\n\r\n  # generate bar chart\r\n  barchart &lt;- ggplot(salesdf, aes(x=Territories, y=Sales)) + \r\n    labs(title=\"Total Sales per Territory\", x=\"Sales Territories \\n (with country code)\", y=\"Sales Amounts\") +\r\n    geom_bar(stat=\"identity\", color=\"green\", size=1, fill=\"lightgreen\") + \r\n    coord_flip() + xlim(rev(levels(sales$SalesTerritories))) +\r\n    scale_y_continuous(labels=function(x) format(x, big.mark=\",\", scientific=FALSE)) +\r\n    geom_text(aes(label=comma(Sales), ymax=100, ymin=0), size=4, hjust=0, position=position_fill())\r\n  print(barchart)\r\n  dev.off()';\r\n\r\nDECLARE @sqlscript NVARCHAR(MAX);\r\nSET @sqlscript = N'\r\n  SELECT h.Subtotal, \r\n    t.Name + '' ('' + t.CountryRegionCode + '')'' AS SalesTerritories\r\n  FROM Sales.SalesOrderHeader h INNER JOIN Sales.SalesTerritory t\r\n    ON h.TerritoryID = t.TerritoryID;';\r\n\r\nEXEC sp_execute_external_script\r\n  @language = N'R',\r\n  @script = @rscript,\r\n  @input_data_1 = @sqlscript\r\n  WITH RESULT SETS NONE; \r\nGO\r\n<\/pre>\n<p>If you read the first article in this series, the components of the procedure call should be familiar to you. We start by defining the R script and assigning it to the <code>@rscript<\/code> variable. Next, we define the <code>SELECT<\/code> statement and assign it to the <code>@sqlscript<\/code> variable. Finally, we call the <code>sp_execute_external_script<\/code> stored procedure, specifying the <code>@language<\/code> value as <code>R<\/code>, the <code>@rscript<\/code> value as <code>@rscript<\/code>, and the <code>@input_date_1<\/code> value as <code>@sqlscript<\/code>. We also specify that no result set be returned. Instead, we\u2019ll be generating a .png file that contains our bar chart.<\/p>\n<p>The remainder of the article walks you through each section of the R script: importing R packages, setting up the report file, constructing a data frame, and generating the bar chart. If you have any questions about the non-R elements in the example, be sure to refer back to the first article.<\/p>\n<h2>Importing R packages<\/h2>\n<p>The first part of our code imports two R packages from the local SQL Server library into the scripting environment. Packages are collections of functions or data sets in the SQL Server R Services library. When you install R Services, SQL Server setup adds a number of common R packages; however, you can install additional packages at any time.<\/p>\n<p>Once a package has been installed in the library, you can import it into your scripting environment in order to use its functions or data sets within your R script.<\/p>\n<p>In our example, we\u2019ll be importing the <code>scales<\/code> and <code>ggplot2<\/code> packages. Before we can do that, however, we must install them into the SQL Server R Services library. Any package we import must exist in the library. Fortunately, we can get both packages by installing the <code>ggplot2<\/code> package. When you install this package, R Services installs several additional packages, including <code>scales<\/code>.<\/p>\n<p>If the machine on which SQL Server is running can connect to the Internet, one of the simplest ways to install a package is to use the RGui utility available in the following folder (on a default installation):<\/p>\n<pre>C:\\Program Files\\MSSQL13.MSSQLSERVER\\R_SERVICES\\bin\\<\/pre>\n<p>If you\u2019re working with a named SQL Server instance, simply replace <code>MSSQLSERVER<\/code> with the name of that instance.<\/p>\n<p>From this folder, launch the RGui utility as an administrator and run the following two commands (again replacing the default instance with the named instance, if applicable):<\/p>\n<pre>lib.SQL &lt;- \"C:\\\\Program Files\\\\Microsoft SQL Server\\\\MSSQL13.MSSQLSERVER\\\\R_SERVICES\\\\library\"\r\n  \r\n  install.packages(\"ggplot2\", lib = lib.SQL)<\/pre>\n<p>All you\u2019re doing here is specifying where the target SQL Server library is located and then using that location when running the <code>install.packages<\/code> function. R Services takes care of the rest, checking for an available download server, pulling the files from that server, and installing the package into the library. The following figure shows the RGui utility after running the <code>install.packages<\/code> function. Your results might be different, depending on the download server used.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1077\" height=\"782\" class=\"wp-image-71672\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/07\/rgui-png.png\" alt=\"rgui.png\" \/><\/p>\n<p>That\u2019s all there is to installing a package in the SQL Server library, assuming you can connect to the Internet. If you can\u2019t, you\u2019ll have to take a different approach. For more information, see the Microsoft article <a href=\"https:\/\/docs.microsoft.com\/en-us\/sql\/advanced-analytics\/r\/install-additional-r-packages-on-sql-server\">Install Additional R Packages on SQL Server<\/a>.<\/p>\n<p>With the packages in place, we can import them into the scripting environment. To do so, you need only run the <code>library<\/code> function for each package, specifying the package name when calling the function, as shown in the following code:<\/p>\n<pre>library(scales)\r\nlibrary(ggplot2)<\/pre>\n<p>Once we\u2019ve imported the packages, we can use their functions in our script, which we\u2019ll be doing later in the script, when we define the bar chart.<\/p>\n<h2>Setting up the image file<\/h2>\n<p>The next step is to set up the image file that will hold our bar chart. We do this by including the following two lines of code:<\/p>\n<pre>reportfile &lt;- \"C:\\\\DataFiles\\\\SalesReport.png\"\r\npng(filename=reportfile, width=1000, height=600)<\/pre>\n<p>First, we define a string variable to hold the folder and file name of the target image file and then use the back-arrow assignment operator (<code>&lt;-<\/code>) to assign the value to the <code>reportfile<\/code> variable. Notice that we must escape the backslashes in the file path by doubling them. (The folder and file names specified here are just for the example. You can specify any folder or .png file name you want. Just substitute them within the code.)<\/p>\n<p>Next, we call the <code>png<\/code> device, which is used to create the .png file for our bar chart. The R language provides devices such as <code>png<\/code>, <code>bmp<\/code>, and <code>tiff<\/code> for creating plots and image files in various formats. In this case, when calling the <code>png<\/code> device, we specify the <code>reportfile<\/code> variable for the folder and file names and then provide a width and height for the file, in pixels.<\/p>\n<p>That\u2019s all there is to setting up our graphic file. After we add the bar chart, we\u2019ll need to close the <code>png<\/code> device, using the <code>dev.off()<\/code> function, but we\u2019ll get to that later in the article, after we define our bar chart.<\/p>\n<h2>Constructing the data frame<\/h2>\n<p>Before we can create the bar chart, we must get the data we want to visualize into a data frame. A data frame is similar to a database table. Strictly speaking, it is a list of vectors of equal length that are assigned the <code>data.frame<\/code> type, but to keep in simple, we can think of the data set in terms of rows and columns.<\/p>\n<p>To construct the data frame, we need to retrieve the SQL Server data, transform the data, and build the data frame, as shown in the following code:<\/p>\n<pre>sales &lt;- InputDataSet\r\nc1 &lt;- levels(sales$SalesTerritories)\r\nc2 &lt;- round(tapply(sales$Subtotal, sales$SalesTerritories, sum))\r\nsalesdf &lt;- data.frame(c1, c2)\r\nnames(salesdf) &lt;- c(\"Territories\", \"Sales\")<\/pre>\n<p>First, we use the following statement to assign the SQL Server data to the <code>sales<\/code> variable:<\/p>\n<pre>sales &lt;- InputDataSet<\/pre>\n<p>The SQL Server data is represented by the <code>InputDataSet<\/code> variable, which is the default variable used to reference the data returned by the <code>SELECT<\/code> statement. We must use the <code>InputDataSet<\/code> variable to pull the SQL Server data into our R script, unless we specify a different name for the data set variable. (We would do this in the stored procedure call. If you\u2019re not sure how this works, refer to the first article in this series.)<\/p>\n<p>Once we have the data set stored in the <code>sales<\/code> variable, we can start working with that data. Our final data frame will contain two columns. The first will include the list of territories, and the second will include the aggregated sales for each territory, as defined in the following code:<\/p>\n<pre>c1 &lt;- levels(sales$SalesTerritories)\r\nc2 &lt;- round(tapply(sales$Subtotal, sales$SalesTerritories, sum))<\/pre>\n<p>We start by assigning the values for the first column to the <code>c1<\/code> variable. To get the values, we use the <code>levels<\/code> function to retrieve a distinct list of values from the <code>SalesTerritories<\/code> column in the <code>sales<\/code> data set. Notice that we must first specify the <code>sales<\/code> variable, then a dollar sign (<code>$<\/code>), and finally the <code>SalesTerritories<\/code> column.<\/p>\n<p>Next, we assign the values for the second column to the <code>c2<\/code> variable. This time, we use the <code>tapply<\/code> function to return the aggregated subtotals for each territory. The function takes three arguments. The first is the <code>Subtotal<\/code> column in the <code>sales<\/code> data set.<\/p>\n<p>The second argument, <code>sales$SalesTerritories<\/code>, contains the factors used for aggregating the subtotals specified in the first argument. In other words, the second column provides the basis (territories) for how the values in the first column (subtotals) will be grouped together and aggregated.<\/p>\n<p>The third argument, <code>sum<\/code>, is an aggregate function that adds together the subtotals in each territory to produce a total for each group. Notice that we also use the <code>round<\/code> function to round the aggregated totals to integers.<\/p>\n<p>Once we\u2019ve defined the two columns, we can put them together into a data frame and assign names to the columns, as shown the following code:<\/p>\n<pre>salesdf &lt;- data.frame(c1, c2)\r\nnames(salesdf) &lt;- c(\"Territories\", \"Sales\")<\/pre>\n<p>In the first line of code, we use the <code>data.frame<\/code> function to merge the two columns into a data frame, which we assign to the <code>salesdf<\/code> variable.<\/p>\n<p>In the second line, we use the <code>names<\/code> function to assign names to the <code>salesdf<\/code> data frame. To do so, we use the <code>c<\/code> function to concatenate the column names, <code>Territories<\/code> and <code>Sales<\/code>, and then assign the results to the data frame.<\/p>\n<p>That\u2019s all there is to creating our data frame. As you become more adept at the R language, you\u2019ll be able to perform far more sophisticated calculations. But for now, let\u2019s see what we can do with the data.<\/p>\n<h2>Generating the bar chart<\/h2>\n<p>One of the most powerful aspects of the R language is its ability to plot data and generate meaningful visualizations. In this case, we\u2019re using the following code to create a bar chart based on the data in the <code>salesdf<\/code> data frame:<\/p>\n<pre>barchart &lt;- ggplot(salesdf, aes(x=Territories, y=Sales)) + \r\n  labs(title=\"Total Sales per Territory\", x=\"Sales Territories \\n (with country code)\", y=\"Sales Amounts\") +\r\n  geom_bar(stat=\"identity\", color=\"green\", size=1, fill=\"lightgreen\") + \r\n  coord_flip() + xlim(rev(levels(sales$SalesTerritories))) +\r\n  scale_y_continuous(labels=function(x) format(x, big.mark=\",\", scientific=FALSE)) +\r\n  geom_text(aes(label=comma(Sales), ymax=100, ymin=0), size=4, hjust=0, position=position_fill())\r\nprint(barchart)\r\ndev.off()';\r\n<\/pre>\n<p>In this section of code, we use several functions from the <code>scales<\/code> and <code>ggplot2<\/code> packages. (Prior to this section, all the functions we used were part of default packages included with the R Services installation.)<\/p>\n<p>The bulk of the code in this section is related the bar chart definition, which we assign to the <code>barchart<\/code> variable. The definition is made up of seven elements, connected together with plus (<code>+<\/code>) signs. In the first element, we use the <code>ggplot<\/code> function to create the base layer for our bar chart:<\/p>\n<pre>ggplot(salesdf, aes(x=Territories, y=Sales))<\/pre>\n<p>The function\u2019s first argument, <code>salesdf<\/code>, is the data frame that provides the data for the bar chart. The second argument uses the <code>aes<\/code> function to define the default aesthetics used by all layers in the chart, unless specifically overridden within a layer. In this case, the <code>aes<\/code> function merely defines the chart\u2019s X-axis and Y-axis (<code>Territories<\/code> and <code>Sales<\/code>, respectively), which coincide with the columns in the <code>salesdf<\/code> data set.<\/p>\n<p>In the next element in the bar chart definition, we use the <code>labs<\/code> function to provide labels for the title and each axis:<\/p>\n<pre>labs(title=\"Total Sales per Territory\", x=\"Sales Territories \\n (with country code)\", y=\"Sales Amounts\")<\/pre>\n<p>All we\u2019re doing here is passing three arguments into the function, one for each label. One thing worth noting, however, is the <code>\\n<\/code> in the X-axis label. This inserts a line break into the text. By using the <code>labs<\/code> function, we can override the default labels that would normally be used, as specified in the <code>ggplot<\/code> function.<\/p>\n<p>Now we get to the third element, which uses the <code>geom_bar<\/code> function to specify that a bar plot be created, as opposed to another type of visualization:<\/p>\n<pre>geom_bar(stat=\"identity\", color=\"green\", size=1, fill=\"lightgreen\")<\/pre>\n<p>The function takes four arguments. The <code>stat=\"identity\"<\/code> argument ensures that data values map correctly to the plot points. The <code>color<\/code> argument sets the bar outlines to green, the <code>size<\/code> argument sets the bar outlines to 1 point, and the <code>fill<\/code> argument sets the main color of the bars to light green.<\/p>\n<p>Next, we call the <code>coord_flip<\/code> function to flip how the X-axis and Y-axis are displayed in order to make it easier to read the axis labels:<\/p>\n<pre>coord_flip()<\/pre>\n<p>After we rotate the chart, we need to modify the order of the territory names to ensure that they\u2019re listed alphabetically, starting at the top:<\/p>\n<pre>xlim(rev(levels(sales$SalesTerritories)))<\/pre>\n<p>We\u2019re taking this approach because flipping the X-axis and Y-axis resulted in the territory names being listed in reverse order (from down to up). To fix this, we first use the <code>levels<\/code> function to retrieve a distinct list of the territory names, and then use the <code>rev<\/code> function to reverse their order. We then wrap all this within the <code>xlim<\/code> function to ensure that the values are correctly mapped to the reversed labels.<\/p>\n<p>Next, we use the <code>scale_y_continuous<\/code> function to ensure that the numeric labels use integers rather than scientific notation:<\/p>\n<pre>scale_y_continuous(labels=function(x) format(x, big.mark=\",\", scientific=FALSE))<\/pre>\n<p>The <code>scale_y_continuous<\/code> function lets us refine the labels used for the Y-axis aesthetics. In this case, we\u2019re creating the <code>x<\/code> function as the value for the <code>labels<\/code> argument, and then using the <code>format<\/code> function to modify the <code>x<\/code> function, adding commas to the numerical values and ensuring they\u2019re not rendered as scientific notation.<\/p>\n<p>The <code>format<\/code> function takes three arguments. The first specifies the <code>x<\/code> function as the object being formatted. The second argument (<code>big.mark<\/code>) specifies that a comma be used for large numerical numbers. The third argument, <code>scientific<\/code>, specifies <code>FALSE<\/code> to prevent scientific notation from being displayed.<\/p>\n<p>The next step is to add the sales totals to the bars themselves so they appear on top of the bars. For this, we must use the <code>geom_text<\/code> function:<\/p>\n<pre>geom_text(aes(label=comma(Sales), ymax=100, ymin=0), size=4, hjust=0, position=position_fill()))<\/pre>\n<p>The function takes four arguments. The first argument, <code>label<\/code>, uses the <code>aes<\/code> function, which itself takes three arguments. The first specifies that the <code>Sales<\/code> values be displayed, using the <code>comma<\/code> function to add commas to the numeric vales. The <code>ymax<\/code> and <code>ymin<\/code> settings specify the upper and lower pointwise limits of the displayed value. You must include these two arguments, but you can experiment with their settings, particularly <code>ymax<\/code>.<\/p>\n<p>The <code>size<\/code> argument of the <code>geom_text<\/code> function specifies <code>4<\/code>, which sets the font size to 4 points, and the <code>hjust<\/code> argument specifies <code>0<\/code>, which left-justifies the labels. Finally, the <code>position<\/code> argument uses the <code>position_fill<\/code> function to ensure that the labels appear on top of the bar, rather than off to the right.<\/p>\n<p>This completes our bar chart definition, we can now use the <code>print<\/code> function to send the bar plot to the .png file and the <code>dev.off<\/code> function to close the <code>png<\/code> device:<\/p>\n<pre>print(barchart)\r\ndev.off()<\/pre>\n<p>If you were to now run the script, calling the <code>sp_execute_external_script<\/code> stored procedure as defined in our example, the <code>SalesReport.png<\/code> file will be created in the designated folder. When you open file, you should see an image similar to the one shown in the following figure.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"608\" class=\"wp-image-71673\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2017\/07\/salesreport-png.png\" alt=\"SalesReport.png\" \/><\/p>\n<p>As you can see, the SQL Server data has been aggregated and rendered in the chart, providing us with the total sales for each territory. Notice that all the numerical values are rounded and include commas to make them more readable.<\/p>\n<h2>Working with R Services<\/h2>\n<p>Not surprisingly, we can do a lot more with both the data and the visualization in our example. We can modify the bar chart to change how data is displayed, or we can try different types of visualizations. Because it\u2019s so easy to generate a graphic file, we can play around with the code as much as we want to see what we can come up with. In fact, this is often the best approach to learn R because much of the documentation is very unclear. Sometimes the only way to understand how a language element works is to try it out.<\/p>\n<p>Although the example we\u2019ve been working here is very basic, it should help you better understand some of the language elements that go into analyzing and visualizing data. In future articles in this series, we\u2019ll dig deeper into both the analytics side and the visualization side. Until then, you have plenty here you can play with, so dive in and start having some fun.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It is not just the analytic power of R that you get from using SQL Server R Services, but also the great range of packages that can be run in R that provide a daunting range of graphing and plotting facilities. Robert Sheldon shows how you can take data held in SQL Server and, via SQL Server R Services, use an R package called ggPlot that offers a powerful graphics language for creating elegant and complex plots.&hellip;<\/p>\n","protected":false},"author":221841,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143528,143531],"tags":[5134],"coauthors":[6779],"class_list":["post-71671","post","type-post","status-publish","format-standard","hentry","category-bi-sql-server","category-t-sql-programming-sql-server","tag-sql-prompt"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71671","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\/221841"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=71671"}],"version-history":[{"count":6,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71671\/revisions"}],"predecessor-version":[{"id":71723,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/71671\/revisions\/71723"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=71671"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=71671"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=71671"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=71671"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}