{"id":1343,"date":"2012-05-28T00:00:00","date_gmt":"2012-05-28T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/sql-view-beyond-the-basics\/"},"modified":"2021-09-29T16:21:48","modified_gmt":"2021-09-29T16:21:48","slug":"sql-view-beyond-the-basics","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/sql-view-beyond-the-basics\/","title":{"rendered":"SQL View: Beyond the Basics"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">In my previous article, &#8216;<a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/sql-view-basics\/\">SQL View Basics<\/a>&#8216;, I discussed a&#160; <code>VIEW<\/code> as being a virtual table that does not exist until it is invoked by name in SQL statements, and which disappears at the end of that statement. The strong advantage of a <code>VIEW<\/code> is that it will produce the correct results when it is invoked, based on the current data. Trying to do the same sort of things with temporary tables or computed columns within a table can be subject to errors and is likely to be slower to read from disk. Let&#8217;s keep going and look at some code. <\/p>\n<h2>Types of VIEWs <\/h2>\n<p>We can classify a view as being either read-only or updatable. This is handy, but we can also use the type of <code>SELECT<\/code> statement in the <code>VIEW<\/code>s to classify them. These two methods can overlap, but let&#8217;s start with the <code>SELECT<\/code> statements method. <\/p>\n<h2>Single-Table Projection and Restriction<\/h2>\n<p>In practice, many <code>VIEW<\/code>s are projections or restrictions on a single base table. This is a common method for implementing security-control by removing rows or columns that a particular group of users is not allowed to see. These <code>VIEW<\/code>s are usually updatable and represent the most common <code>VIEW<\/code> pattern. <\/p>\n<h2>Translated Columns <\/h2>\n<p>Another common use of a <code>VIEW<\/code> is to translate codes into text or other codes by doing table look ups. This is a special case of a joined <code>VIEW<\/code> based on a <code>FOREIGN KEY<\/code> relationship between two tables. For example, an order table might use a part number that we wish to display with a part name on an order entry screen. This is done with a <code>JOIN<\/code> between the <code>Orders <\/code>table and the<code> Inventory<\/code> table, thus:<\/p>\n<pre>CREATE VIEW Screen (part_nbr, part_name, ...)\nAS SELECT Orders.part_nbr, Inventory.part_name, ...\n&#160;&#160;&#160;&#160; FROM Inventory, Orders\n&#160;&#160;&#160; WHERE Inventory.part_nbr = Orders.part_nbr;\n<\/pre>\n<p>The idea of <code>JOIN VIEW<\/code>s to translate codes can be expanded to show more than just one translated column. The result is often a &#8220;star&#8221; query with one table in the center, joined by <code>FOREIGN KEY<\/code> relations to many other tables to produce a result that is more readable than the original central table. <\/p>\n<p>Missing values are a problem. If there is no translation for a given encoding, no row appears in the <code>VIEW<\/code>, or if an <code>OUTER<\/code> <code>JOIN<\/code> was used, a NULL will appear. The programmer should establish a referential integrity constraint to <code>CASCADE <\/code>changes between the tables to prevent loss of data. <\/p>\n<h2>Grouped VIEWs<\/h2>\n<p>A grouped <code>VIEW<\/code> is based on a query with a <code>GROUP BY <\/code>clause. Since each of the groups may have more than one row in the base from which it was built, these are necessarily read-only <code>VIEW<\/code>s. Such <code>VIEW<\/code>s usually have one or more aggregate functions and they are used for reporting purposes. They are also handy for working around weaknesses in SQL. Consider a <code>VIEW<\/code> that shows the largest sale in each state. The query is straightforward:<\/p>\n<pre>CREATE VIEW BigSales (state_code, sales_amt_total)\nAS SELECT state_code, MAX(sales_amt)\n&#160;&#160;&#160;&#160; FROM Sales\n&#160;&#160;&#160; GROUP BY state_code;\n<\/pre>\n<p>SQL does not require that the grouping column(s) appear in the select clause, but it is a good idea in this case. <\/p>\n<p>These <code>VIEW<\/code>s are also useful for &#8220;flattening out&#8221; one-to-many relationships. For example, consider a Personnel table, keyed on the employee number (<code>emp_nbr<\/code>), and a table of dependents, keyed on a combination of the employee number for each dependent&#8217;s parent (<code>emp_nbr<\/code>) and the dependent&#8217;s own serial number (<code>dep_id<\/code>). The goal is to produce a report of the employees by name with the number of dependents each has.<\/p>\n<pre>CREATE VIEW DepTally1 (emp_nbr, dependent_cnt)\nAS SELECT emp_nbr, COUNT(*)\n&#160;&#160;&#160;&#160; FROM Dependents\n&#160;&#160;&#160; GROUP BY emp_nbr;\n<\/pre>\n<p>The report is simply an<code> OUTER JOIN<\/code> between this <code>VIEW<\/code> and the Personnel table. <\/p>\n<p>The <code>OUTER<\/code> <code>JOIN<\/code> is needed to account for employees without dependents with a <code>NULL <\/code>value, like this.<\/p>\n<pre>SELECT emp_name, dependent_cnt\n&#160; FROM Personnel AS P1\n&#160;&#160;&#160;&#160;&#160;&#160; LEFT OUTER JOIN \n&#160;&#160;&#160;&#160;&#160;&#160; DepTally1 AS D1 \n&#160;&#160;&#160;&#160;&#160;&#160; ON P1.emp_nbr = D1.emp_nbr;\n<\/pre>\n<h2>UNION-ed VIEWs<\/h2>\n<p><code>VIEW<\/code>s based on a <code>UNION<\/code> or <code>UNION ALL<\/code> operation are read-only because there is no single way to map a change onto just one row in one of the base tables. The <code>UNION<\/code> operator will remove duplicate rows from the results. Both the <code>UNION<\/code> and <code>UNION ALL<\/code> operators hide which table the rows came from. Such <code>VIEW<\/code>s must use a <code>&lt;view column list&gt;,<\/code> because the columns in a <code>UNION [ALL] <\/code>have no names of their own. In theory, a <code>UNION<\/code> of two disjoint tables, neither of which has duplicate rows in itself should be updatable. <\/p>\n<p>The problem given in the section on grouped <code>VIEW<\/code>s, could also be done with a <code>UNION<\/code> query that would assign a count of zero to employees without dependents, thus:<\/p>\n<pre>CREATE VIEW DepTally2 (emp_nbr, dependent_cnt)\nAS (SELECT emp_nbr, COUNT(*)\n&#160;&#160; &#160;&#160;&#160;FROM Dependents\n&#160;&#160;&#160;&#160; GROUP BY emp_nbr)\n&#160;&#160; UNION\n&#160;&#160; (SELECT emp_nbr, 0\n&#160;&#160;&#160;&#160;&#160; FROM Personnel AS P2\n&#160;&#160;&#160;&#160; WHERE NOT EXISTS \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; (SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM Dependents AS D2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE D2.emp_nbr = P2.emp_nbr));\n<\/pre>\n<p>The report is now a simple<code> INNER JOIN<\/code> between this <code>VIEW<\/code> and the Personnel table. The zero value, instead of a NULL value, will account for employees without dependents.<\/p>\n<h2>Calculated Columns <\/h2>\n<p>The main reason for hiding computations in a <code>VIEW<\/code> is so that the computation is done one way, one place, one time and so it gets a unique data element name. One common use for a <code>VIEW<\/code> is to provide summary data across a row. For example, given a table with measurements in metric units, we can construct a <code>VIEW<\/code> that hides the calculations to convert them into English units. <\/p>\n<p>It is important to be sure that you have no problems with <code>NULL<\/code> values when constructing a calculated column. For example, given a Personnel table with columns for both salary and commission, you might construct this <code>VIEW<\/code>:<\/p>\n<pre>CREATE VIEW Payroll (emp_nbr, paycheck_amt)\nAS \nSELECT emp_nbr, (salary + COALESCE(commission), 0.00)\n&#160;FROM Personnel;\n<\/pre>\n<p>Office workers do not get commissions, so the value of their commission column will be <code>NULL<\/code>, so we use the <code>COALESCE()<\/code> function to change the <code>NULLs<\/code> to zeros. <\/p>\n<p>SQL Server introduced a computed column construct in their table declaration syntax,<code> &lt;expression&gt; AS &lt;column name&gt;<\/code>. This is proprietary and has some limitations in that it can only reference columns in the same row. This can be a way to avoid a <code>VIEW<\/code> and is a good idea to reduce the number of objects in a schema.<\/p>\n<p>Computed VIEWs not just for fancy math; strings and temporal data also have computations. For example a <code>VIEW<\/code> can hide all but the last four digits of a 16-digit credit card number:  <\/p>\n<pre>masked_creditcard_nbr \n&#160;&#160;AS '****-****-****-' + SUBSTRING (creditcard_nbr, 13, 16)<\/pre>\n<p>likewise, the following Monday from a weekend date is easy to put into a computation business_date<\/p>\n<pre>&#160;&#160;AS CASE WHEN DATEPART(DW, sale_date) = 7\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN DATEADD (DD, 1, sale_date)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHEN DATEPART(DW, sale_date) = 6\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;THEN DATEADD (DD, 2, sale_date)\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;ELSE sale_date END<\/pre>\n<h2>Updatable and Read-Only VIEWs<\/h2>\n<p>Unlike base tables, <code>VIEW<\/code>s are either updatable or read-only, but not both. <code>INSERT<\/code>, <code>UPDATE<\/code>, and <code>DELETE<\/code> operations are allowed on updatable <code>VIEW<\/code>s and base tables, subject to any other constraints. <code>INSERT<\/code>, <code>UPDATE<\/code>, and <code>DELETE<\/code> are not allowed on read-only <code>VIEW<\/code>s, but you can change their base tables, as you would expect.<\/p>\n<p>For the computer science majors, this is an NP-complete problem. Since we have no general solution, we need a way to handle each <code>VIEW<\/code> with particular code. We have a way to do that! <\/p>\n<h2>The INSTEAD OF Trigger<\/h2>\n<p>The <code>INSTEAD OF<\/code> trigger is how to resolve the <code>VIEW<\/code> updating problem. It is possible for a user to write <code>INSTEAD OF<\/code> triggers on <code>VIEW<\/code>s, which catch the changes and route them to the base tables that make up the <code>VIEW<\/code>. The database designer has complete control over the way <code>VIEW<\/code>s are handled.<\/p>\n<p>These triggers are added to a <code>VIEW<\/code> and are executed on base tables instead of making changes directly to the <code>VIEW<\/code> it self. If you think about it, how would a program change the <code>VIEW<\/code> anyway? It does not exist. <\/p>\n<p>The basic syntax is fairly simple; T-SQL has a lot of other options but we will not be concerned with them. <\/p>\n<pre>CREATE TRIGGER &lt;trigger_name&gt;\nON &lt;view_name&gt;\nINSTEAD OF } \n{[INSERT] [,] [UPDATE] [,] [DELETE]} \nAS &lt;SQL statement&gt;; \n<\/pre>\n<p>Triggers have a global scope for obvious reasons. The options <code>INSERT<\/code>, <code>UPDATE<\/code> or <code>DELETE<\/code> are called &#8220;database events&#8221; and they cause the trigger to fire. As a general heuristic, you will want to have your SQL statement fire on all three events. <\/p>\n<p>The trigger&#8217;s SQL statement is executed instead of the triggering SQL statement. The attempted <code>INSERT<\/code>, <code>UPDATE<\/code> or <code>DELETE<\/code> has no effect at all. If you feel fancy, you can define views on views where each view has its own <code>INSTEAD OF<\/code> trigger.<\/p>\n<p><code>INSTEAD OF<\/code> triggers are not allowed on updatable views that use <code>WITH CHECK OPTION<\/code>. SQL Server raises an error when an <code>INSTEAD OF<\/code> trigger is added to an updatable view <code>WITH CHECK OPTION<\/code> specified. The user must remove that option by using ALTER <code>VIEW<\/code> before defining the <code>INSTEAD OF<\/code> trigger.<\/p>\n<p>For <code>INSTEAD OF<\/code> triggers, the <code>DELETE<\/code> option is not allowed on tables that have an <code>ON DELETE CASCADE<\/code> referential action. Similarly, the <code>UPDATE<\/code> option is not allowed on tables that have ON <code>UPDATE CASCADE<\/code> referential action. <\/p>\n<p>This can be complicated, so let us start with a very example of a read-only <code>VIEW<\/code> and its base tables. <\/p>\n<pre>CREATE TABLE Alpha\n(alpha_key VARCHAR(10) NOT NULL PRIMARY KEY,\n&#160;alpha_amt INTEGER NOT NULL);\n&#160;\nCREATE TABLE Beta\n(beta_key VARCHAR(10)&#160; NOT NULL PRIMARY KEY,\n&#160;beta_amt INTEGER NOT NULL);\n<\/pre>\n<p>Now let&#8217;s declare a <code>VIEW<\/code> that can&#8217;t be updated. <\/p>\n<pre>CREATE VIEW&#160; Combined_Shares (ab_key, ab_tot)\nAS \nSELECT ab_key, SUM(ab_tot)\n&#160; FROM (SELECT * FROM Alpha \n&#160;&#160;&#160;&#160;&#160;&#160;&#160; UNION ALL \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; SELECT * FROM Beta) \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AS X (ab_key, ab_tot)\n&#160;GROUP BY ab_key;\n<\/pre>\n<p>As a simple example, when you do an insertion, put half of an amount into each of the two hidden tables, Alpha and Beta. If the amount is an odd number, favor one of the two hidden tables. <\/p>\n<pre>CREATE TRIGGER Split_Loot\nON Combined_Shares\nINSTEAD OF INSERT&#160; \nAS \nBEGIN \nINSERT INTO Alpha -- favor alpha \nSELECT I.ab_key, CEILING (I.ab_tot\/2.0)\n&#160; FROM INSERTED AS I;\n&#160;\nINSERT INTO Beta \nSELECT I.ab_key, FLOOR(I.ab_tot\/2.0)\n&#160; FROM INSERTED&#160; AS I;\nEND; \n<\/pre>\n<p>Put in one row of data via the view and not the base tables. <\/p>\n<pre>INSERT INTO Combined_Shares (ab_key, ab_tot)\nVALUES ('Jerry', 51); \n<\/pre>\n<p>Now look at what happened by looking at the <code>VIEW<\/code> and the hidden tables in it. <\/p>\n<pre>SELECT * FROM Combined_Shares;\n<\/pre>\n<table class=\"MsoNormalTable\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p>Jerry<\/p>\n<\/td>\n<td valign=\"top\">\n<p>51<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre>&#160;SELECT * FROM Alpha;\n<\/pre>\n<table class=\"MsoNormalTable\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p>Jerry<\/p>\n<\/td>\n<td valign=\"top\">\n<p>25<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre>SELECT * FROM Beta;\n<\/pre>\n<table class=\"MsoNormalTable\">\n<tbody>\n<tr>\n<td valign=\"top\">\n<p>Jerry<\/p>\n<\/td>\n<td valign=\"top\">\n<p>26<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>That was nice. Now let&#8217;s do a delete trigger <\/p>\n<pre>CREATE TRIGGER Delete_Loot\nON Combined_Shares\nINSTEAD OF DELETE \nAS \nBEGIN \nMERGE INTO Alpha\nUSING DELETED AS D\nON D.ab_key = Alpha.ab_key\nWHEN MATCHED\nTHEN DELETE;\n&#160;\nMERGE INTO Beta\nUSING DELETED AS D\nON D.ab_key = Beta.ab_key\nWHEN MATCHED\nTHEN DELETE;\nEND; \n<\/pre>\n<p>And fire the trigger:<\/p>\n<pre>DELETE FROM Combined_Shares\nWHERE ab_key = 'Jerry';\n<\/pre>\n<p>Please notice the use of the <code>MERGE<\/code> in this trigger. You need to learn how to write standard SQL.&#160; Also, when this fires you will get three messages, one for the <code>VIEW<\/code> and one for each table. <\/p>\n<pre>CREATE TRIGGER Update_Loot\nON Combined_Shares\nINSTEAD OF UPDATE\nAS \nBEGIN\nMERGE INTO Alpha\nUSING INSERTED AS I\nON I.ab_key = Alpha.alpha_key\nWHEN MATCHED\nTHEN UPDATE\n&#160;&#160;&#160;&#160; SET alpha_amt = CEILING (I.ab_tot\/2.0);\nMERGE INTO Beta\nUSING INSERTED AS I\nON I.ab_key = Beta.beta_key\nWHEN MATCHED\nTHEN UPDATE\n&#160;&#160;&#160;&#160; SET beta_amt = FLOOR (I.ab_tot\/2.0);\nEND; \n<\/pre>\n<h2>WITH CHECK OPTION Example <\/h2>\n<p>In my previous article, &#8216;<a href=\"http:\/\/www.simple-talk.com\/sql\/learn-sql-server\/sql-view-basics\/\">SQL View Basics<\/a>&#8216;,, I mentioned the use of the <code>WITH CHECK OPTION<\/code> but did not give an example. Let us define a simple Organizational Chart table in a nested sets model. I am going to assume that you know this SQL idiom and so I will not explain it. <\/p>\n<pre>&#160;\nCREATE TABLE OrgChart \n(dept_name CHAR(10) NOT NULL PRIMARY KEY, \n&#160;lft INTEGER NOT NULL UNIQUE CHECK (lft &gt; 0), \n&#160;rgt INTEGER NOT NULL UNIQUE CHECK (rgt &gt; 1),\n&#160; CONSTRAINT order_okay CHECK (lft &lt; rgt));\n&#160;\n&#160;INSERT INTO OrgChart \nVALUES ('Corporate', 1, 120), \n('Acct', 20, 30), \n('Sales', 40, 110), \n('Retail', 50, 60), \n('Wholesale', 70, 80), \n('Internal', 90, 100);\n<\/pre>\n<p>&#160;Let us define a simple Organizational Chart table in a nested sets model. I am going to assume that you know this SQL idiom and I will not explain it in detail. <\/p>\n<pre>CREATE TABLE OrgChart \n(dept_name CHAR(10) NOT NULL PRIMARY KEY, \n&#160;lft INTEGER NOT NULL UNIQUE CHECK (lft &gt; 0), \n&#160;rgt INTEGER NOT NULL UNIQUE CHECK (rgt &gt; 1),\n&#160; CONSTRAINT order_okay CHECK (lft &lt; rgt));\n&#160;\n&#160;INSERT INTO OrgChart \nVALUES ('Corporate', 1, 120), \n('Acct', 20, 30), \n('Sales', 40, 110), \n('Retail', 50, 60), \n('Wholesale', 70, 80), \n('Internal', 90, 100);\n<\/pre>\n<p>The (<code>lft<\/code>, <code>rgt<\/code>) pairs are like tags in a mark-up language, or parentheses in algebra, <code>BEGIN-END<\/code> blocks in Algol-family programming languages, etc. &#8212; they bracket a sub-set. This is a set-oriented approach to trees in a set-oriented language. <\/p>\n<p>&#160;Notice all of the constraints on the columns at the column and table level. But we are missing two constraints we need. The first is that we have a single root. The second is that the (<code>lft<\/code>, <code>rgt<\/code>) pairs do not overlap .. that is, we really have nesting. <\/p>\n<pre>&#160;CREATE VIEW&#160; OrgChart_2\nAS\nSELECT O1.dept_name, O1.lft, O1.rgt \n&#160; FROM OrgChart AS O1\n&#160;WHERE NOT EXISTS \n&#160;&#160;&#160;&#160;&#160; (SELECT *\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM OrgChart AS O2\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE O1.lft BETWEEN O2.lft AND O2.rgt\n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; AND O1.rgt NOT BETWEEN O2.lft AND O2.rgt)\n&#160;&#160; AND O1.lft \n&#160;&#160;&#160;&#160;&#160;&#160; BETWEEN 1\n&#160;&#160;&#160;&#160;&#160;&#160; AND (SELECT rgt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM OrgChart \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; WHERE lft = 1)\n&#160;&#160; AND O1.rgt \n&#160;&#160;&#160;&#160;&#160;&#160; BETWEEN 1\n&#160;&#160;&#160;&#160;&#160;&#160; AND (SELECT rgt \n&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; FROM OrgChart \n&#160; &#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;WHERE lft = 1)\nWITH CHECK OPTION; \n<\/pre>\n<p>This assumes that we start with a root node that has (<code>lft<\/code> = 1) and build from that. That could be enforced with another predicate in the&#160; <code>VIEW<\/code>, if you wish. <\/p>\n<p>&#160;Try these statements and watch the <code>WITH CHECK OPTION<\/code> errors. The first one is a dangling node not under the root node, the second is an overlapping range. <\/p>\n<pre>INSERT INTO OrgChart_2\nVALUES ('dangle', 130, 131);\n&#160;\nINSERT INTO OrgChart_2\nVALUES ('overlap', 51, 61);\n<\/pre>\n<p>giving the error&#8230;<\/p>\n<pre>Msg 550, Level 16, State 1, Line 2\nThe attempted insert or update failed because the target view either specifies WITH CHECK OPTION or spans a view that specifies WITH CHECK OPTION and one or more rows resulting from the operation did not qualify under the CHECK OPTION constraint.\nThe statement has been terminated. \n<\/pre>\n<h2>SUMMARY<\/h2>\n<p><code>VIEW<\/code>s are not as simple as most SQL programmers first think they are. But they are worth the effort because they are powerful and, because they are declarative, they can be optimized. <\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Following up from his popular article, SQL View Basics, Joe delves into the main uses of views, explains how the WITH CHECK OPTION works,  and demonstrates how the  INSTEAD OF trigger can be used in those cases where views cannot be updatable.&hellip;<\/p>\n","protected":false},"author":96214,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[4242,4150,4252],"coauthors":[],"class_list":["post-1343","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-basics","tag-sql","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1343","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\/96214"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1343"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1343\/revisions"}],"predecessor-version":[{"id":40689,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1343\/revisions\/40689"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1343"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1343"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1343"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1343"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}