{"id":1953,"date":"2023-12-20T15:55:22","date_gmt":"2023-12-20T13:55:22","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=1953"},"modified":"2023-12-26T14:05:44","modified_gmt":"2023-12-26T12:05:44","slug":"beware-of-no_data_found-in-your-ptf","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/beware-of-no_data_found-in-your-ptf\/","title":{"rendered":"Beware of NO_DATA_FOUND in your PTF!"},"content":{"rendered":"\n<p>A few days ago I received a comment\/question on the older post about <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-example-not-transposing-rows-to-columns\/\" target=\"_blank\">dynamically transposing rows to column with Polymorphic Table Functions (PTF)<\/a>. Back then I overlooked a bug in the example code, but the explanation takes a bit longer, so i decided to write a new post about it. The PTF was working initially but after inserting new data started to return wrong results &#8211; all NULL&#8217;s for some columns where we know there are actually values present. So, what&#8217;s going on?<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>For Rafal, who asked the question, the result is looking like this:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; INSERT INTO t (empno, key_name, key_val_char) VALUES(7839, &#039;NEW_COLUMN&#039;, &#039;NEW_VALUE&#039;);\n\n1 row inserted.\n\nSQL&gt; SELECT * FROM keyval2tab(t PARTITION BY empno,&#039;T&#039;);\n\nEMPNO DEPTNO SAL HIREDATE NEW_COLUMN COMM JOB MGR ENAME\n________ _________ _______ ___________ _____________ _______ ____________ ______ ________\n7369 20 800 80\/12\/17\n7499 30 1600 81\/02\/20\n7521 30 1250 81\/02\/22\n7566 20 2975 81\/04\/02\n7654 30 1250 81\/09\/28\n7698 30 2850 81\/05\/01\n7782 10 2450 81\/06\/09\n7788 20 3000 87\/04\/19\n7839 10 5000 81\/11\/17 NEW_VALUE PRESIDENT KING\n7844 30 1500 81\/09\/08\n7876 20 1100 87\/05\/23\n7900 30 950 81\/12\/03\n7902 20 3000 81\/12\/03\n7934 10 1300 82\/01\/23\n\n14 rows selected.\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 1<\/em><\/strong><\/p>\n\n\n\n<p>All columns after <strong>HIREDATE<\/strong> are NULL in all rows except of the row where the value for the <strong>NEW_COLUMN<\/strong> is present. Before we look at the code of the PTF to find the reason, let&#8217;s look at two important points.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">How SQL handles NO_DATA_FOUND<\/h4>\n\n\n\n<p>Consider following example:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; create or replace function my_pipeline return  sys.odcivarchar2list pipelined is\n  2  begin\n  3    for i in 1..5 loop\n  4      if i = 3 then \n  5        raise no_data_found;\n  6      end if;\n  7      pipe row (i);\n  8    end loop;\n  9  end;\n 10  \/\n\nFunction MY_PIPELINE compiled\n\nSQL&gt; \nSQL&gt; \nSQL&gt; select column_value from my_pipeline();\n\nCOLUM\n-----\n1\n2\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 2<\/em><\/strong><\/p>\n\n\n\n<p>Although we throw an exception in the function, we do not get an error at the SQL level. The processing stops there, but, nevertheless, we get the first two rows. This behavior is not unexpected and is described for example in <a rel=\"noreferrer noopener\" href=\"https:\/\/asktom.oracle.com\/ords\/f?p=100:11:0::::p11_question_id:10321465390114\" target=\"_blank\">this thread on AksTom<\/a>. <\/p>\n\n\n\n<p>I go along with that, but the next example is less easy for me to follow (but that is how it is  \ud83d\ude42 )<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; create or replace function my_lower (p_str varchar2) return varchar2 is\n  2  begin\n  3    raise no_data_found;\n  4    return lower (p_str);\n  5  end;\n  6  \/\n\nFunction MY_LOWER compiled\n\nSQL&gt; \nSQL&gt; \nSQL&gt; select dummy\n  2  ,      my_lower(dummy) lower_dummy \n  3  from dual;\n\nD LOWER_DUMMY\n- -----------\nX            \n<\/pre><\/div>\n\n\n<p><strong><em>Listing 3<\/em><\/strong><\/p>\n\n\n\n<p>If we want to return a PL\/SQL function result in a SQL and the function throws uncaught <strong>NO_DATA_FOUND<\/strong> exception then we will still get all rows but the corresponding columns will be NULL! This can for example happen when using recursive SQL inside a function or this can just as well happen when referencing non-existent associative array elements!<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Referencing non-existent associative array elements<\/h4>\n\n\n\n<p>Associative arrays (or INDEX BY tables) in PL\/SQL are sparse. They can have gaps, but referencing a non-existent element throws a <strong>NO_DATA_FOUND<\/strong> exception:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\ndeclare \n    type t_tab is table of number  index by pls_integer;\n    v_tab t_tab;\nbegin    \n    v_tab(1) := 1;\n    v_tab(3) := 3;\n    dbms_output.put_line(v_tab(1)||v_tab(2)||v_tab(3));\nend;\nError report -\nORA-01403: no data found\nORA-06512: at line 7\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 4<\/em><\/strong><\/p>\n\n\n\n<p>Skipping the second element and assigning the third one was okay, but referencing the unassigned second element in line 7 throws an exception.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">What does it mean for PTF&#8217;s?<\/h4>\n\n\n\n<p>Looking at the package DBMS_TF we will see that most of the collections we have to use are declared as associative arrays:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n \/* Collections for each supported types *\/\n  TYPE TAB_VARCHAR2_T      IS TABLE OF VARCHAR2(32767)             INDEX BY PLS_INTEGER;\n  TYPE TAB_NUMBER_T        IS TABLE OF NUMBER                      INDEX BY PLS_INTEGER;\n  TYPE TAB_DATE_T          IS TABLE OF DATE                        INDEX BY PLS_INTEGER;\n\n...\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 5<\/em><\/strong><\/p>\n\n\n\n<p>We have to be careful not to access a non-existent element ourselves, but just as important not to hand over such sparse collections for further processing to the database. <\/p>\n\n\n\n<p>Let&#8217;s now look at the code of the PTF from the <a rel=\"noreferrer noopener\" href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-example-not-transposing-rows-to-columns\/\" target=\"_blank\">initial post<\/a> to see what I mean. What we are doing here is trying to transpose the rows with key names and key values back to the table structure. After we inserted one more key\/value pair for EMPNO=7839, we have 8 key for this EMPNO but still 7 keys for all others. It shouldn&#8217;t be a problem though. The DESCRIBE method will have to look into the data (that is the price we pay for being &#8220;dynamic&#8221;), gather all possible keys and therefore define that there will be 8 columns in the result set. So far so good.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/keyvalues.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/keyvalues.png\" alt=\"\" class=\"wp-image-1965\" width=\"605\" height=\"446\" srcset=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/keyvalues.png 445w, https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/keyvalues-300x221.png 300w\" sizes=\"auto, (max-width: 605px) 100vw, 605px\" \/><\/a><figcaption class=\"wp-element-caption\">Figure 1: Key\/value data set to be transposed<\/figcaption><\/figure>\n\n\n<script async src=\"\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-2925154690547867\" crossorigin=\"anonymous\"><\/script><ins class=\"adsbygoogle\" style=\"display:block; text-align:center;\" data-ad-client=\"ca-pub-2925154690547867\" \ndata-ad-slot=\"2727403138\" \ndata-ad-layout=\"in-article\"\ndata-ad-format=\"fluid\"><\/ins>\n<script> \n(adsbygoogle = window.adsbygoogle || []).push({}); \n<\/script>\n\n\n\n<p><\/p>\n\n\n\n<p>Then we start processing and that is where our <strong>FETCH_ROWS<\/strong> method will be called. We are calling the PTF using <strong>PARTITION BY EMPNO<\/strong> &#8211; it have to be that because we are gathering key\/values for a particular EMPNO. That means that <strong>FETCH_ROWS <\/strong>will be called once for each EMPNO. <\/p>\n\n\n\n<p>What happens with EMPNO&#8217;s which don&#8217;t have this additional key <strong>NEW_COLUMN<\/strong>? The problem is in the procedure <strong>FETCH_ROWS<\/strong>. See the excerpt of the listing 8 from the initial post (lines 110 to 125): <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n    FOR r IN 1 .. rowcnt LOOP   \n       FOR p IN 1 .. env.put_columns.count LOOP   \n          IF  inp_rs(idcol_cnt+1).tab_varchar2(r)=replace(env.put_columns(p).name,&#039;&quot;&#039;) THEN  \n            IF env.put_columns(p).type = dbms_tf.type_varchar2 then  \n               out_rs(p).tab_varchar2(1):= inp_rs(idcol_cnt+2).tab_varchar2(r);   \n            ELSIF env.put_columns(p).type = dbms_tf.type_number then  \n               out_rs(p).tab_number(1):= inp_rs(idcol_cnt+3).tab_number(r);   \n            ELSIF env.put_columns(p).type = dbms_tf.type_date then  \n               out_rs(p).tab_date(1):= inp_rs(idcol_cnt+4).tab_date(r);   \n            END IF;  \n          END IF;  \n        END LOOP;  \n    END LOOP;  \n \n    dbms_tf.put_row_set (out_rs, repfac);  \nEND; \n<\/pre><\/div>\n\n\n<p><strong><em>Listing 6<\/em><\/strong><\/p>\n\n\n\n<p>You don&#8217;t need to fully understand what these variables, arrays, etc. are meaning. I&#8217;ll explain the problem. We are constructing the output rowset <strong>OUT_RS <\/strong>and then hand it over to the procedure <strong>DBMS_TF.PUT_ROW_SET<\/strong>. <\/p>\n\n\n\n<p>We are iterating from 1 to 7 in the outer loop &#8211; the rows from one partition of the queried table (EMPNO=7369). On the other hand the inner loop will iterate over 9 (8 known keys plus EMPNO)  columns in the output result set. It will find and provide values for seven of them but not for the column <strong>NEW_COLUMN<\/strong> which is not known for this EMPNO. We end up with the structure of <strong>OUT_RS<\/strong> like this<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1015\" height=\"162\" src=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset.png\" alt=\"\" class=\"wp-image-1969\" srcset=\"https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset.png 1015w, https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset-300x48.png 300w, https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset-768x123.png 768w, https:\/\/blog.sqlora.com\/en\/wp-content\/uploads\/sites\/2\/2023\/12\/out_rowset-624x100.png 624w\" sizes=\"auto, (max-width: 1015px) 100vw, 1015px\" \/><\/a><figcaption class=\"wp-element-caption\">Figure 2: Output row set<\/figcaption><\/figure>\n\n\n\n<p>Look at the difference between element five and six: for the column <strong>COMM <\/strong>we know the value is NULL but for the column <strong>NEW_COLUMN <\/strong>there is no element in the array TAB_VARCHAR2_T at all. Obviously, the procedure <strong>DBMS_TF.PUT_ROW_SET<\/strong> is iterating the <strong>OUT_RS <\/strong>and after processing first four columns <strong>NO_DATA_FOUND <\/strong>exception is thrown. The columns five to nine will not be processed for this output row anymore. That is the reason we see all NULL&#8217;s for the columns after <strong>HIREDATE<\/strong>. But as we can assume from what we have seen in listings 2 and 3 at the beginning of this post: the processing doesn&#8217;t stop there, the next row will be processed which unfortunately will have the same problem and so on, until we reach the EMPNO = 7839. For this row we will be able to output all columns correctly. <\/p>\n\n\n\n<p>Quite bad: we are returning wrong results and don&#8217;t even know about it. <\/p>\n\n\n\n<p>How can we prove what is happening?<\/p>\n\n\n\n<p>Just add an exception handler to <strong>FETCH_ROWS<\/strong>, catch <strong>NO_DATA_FOUND<\/strong> and throw another exception instead of it:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n...\n    dbms_tf.put_row_set (out_rs, repfac);  \nEXCEPTION \n  when no_data_found then\n     raise_application_error(-20001,&#039;NO_DATA_FOUND! &#039;||chr(13)||dbms_utility.format_error_backtrace) ;    \nEND; \n\nSQL&gt; SELECT * FROM keyval2tab(t PARTITION BY empno,&#039;T&#039;);\n\nORA-20001: NO_DATA_FOUND! \nORA-06512: at &quot;SYS.DBMS_TF&quot;, line 220\nORA-06512: at &quot;SYS.DBMS_TF&quot;, line 308\nORA-06512: at &quot;ONFTEST.KV2TAB_PKG&quot;, line 184\nORA-06512: at &quot;ONFTEST.KV2TAB_PKG&quot;, line 187\n\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 7<\/em><\/strong><\/p>\n\n\n\n<p>Better! We get no wrong results but are pointed to the problem in our code! Now it&#8217;s time to fix this problem. Just one possible way is to exchange the inner and outer loops, iterate over output columns and then find and assign the corresponding value or otherwise assign NULL, like shown in Listing 8.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n    ...\n   FOR p IN idcol_cnt+1 .. env.put_columns.count LOOP   \n       found := false;\n       FOR r IN 1 .. rowcnt LOOP  \n          EXIT WHEN found;\n          IF  inp_rs(idcol_cnt+1).tab_varchar2(r)=replace(env.put_columns(p).name,&#039;&quot;&#039;) THEN  \n            found := true;\n            IF env.put_columns(p).type = dbms_tf.type_varchar2 then  \n               out_rs(p).tab_varchar2(1):= inp_rs(idcol_cnt+2).tab_varchar2(r);   \n            ELSIF env.put_columns(p).type = dbms_tf.type_number then  \n               out_rs(p).tab_number(1):= inp_rs(idcol_cnt+3).tab_number(r);   \n            ELSIF env.put_columns(p).type = dbms_tf.type_date then  \n               out_rs(p).tab_date(1):= inp_rs(idcol_cnt+4).tab_date(r);   \n            END IF;  \n          END IF; \n        END LOOP;  \n        IF NOT found THEN \n            IF env.put_columns(p).type = dbms_tf.type_varchar2 then  \n               out_rs(p).tab_varchar2(1):= null;   \n            ELSIF env.put_columns(p).type = dbms_tf.type_number then  \n               out_rs(p).tab_number(1):= null;   \n            ELSIF env.put_columns(p).type = dbms_tf.type_date then  \n               out_rs(p).tab_date(1):= null;   \n            END IF;  \n          END IF;       \n    END LOOP; \n...\nSQL&gt;  SELECT * FROM keyval2tab(t PARTITION BY empno,&#039;T&#039;);\n\n     EMPNO     DEPTNO        SAL HIREDATE   NEW_COLUMN       COMM JOB               MGR ENAME     \n---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------\n      7369         20        800 1980-12-17                       CLERK            7902 SMITH     \n      7499         30       1600 1981-02-20                   300 SALESMAN         7698 ALLEN     \n      7521         30       1250 1981-02-22                   500 SALESMAN         7698 WARD      \n      7566         20       2975 1981-04-02                       MANAGER          7839 JONES     \n      7654         30       1250 1981-09-28                  1400 SALESMAN         7698 MARTIN    \n      7698         30       2850 1981-05-01                       MANAGER          7839 BLAKE     \n      7782         10       2450 1981-06-09                       MANAGER          7839 CLARK     \n      7788         20       3000 1987-04-19                       ANALYST          7566 SCOTT     \n      7839         10       5000 1981-11-17 NEW_VALUE             PRESIDENT             KING      \n      7844         30       1500 1981-09-08                     0 SALESMAN         7698 TURNER    \n      7876         20       1100 1987-05-23                       CLERK            7788 ADAMS     \n      7900         30        950 1981-12-03                       CLERK            7698 JAMES     \n      7902         20       3000 1981-12-03                       ANALYST          7566 FORD      \n      7934         10       1300 1982-01-23                       CLERK            7782 MILLER    \n\n14 rows selected. \n<\/pre><\/div>\n\n\n<p><strong><em>Listing 8<\/em><\/strong><\/p>\n\n\n\n<p>Now, everything looks fine again!<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Conclusion<\/h4>\n\n\n\n<p>We have seen how not populated elements of the collections used in PTF can lead to <strong>NO_DATA_FOUND <\/strong>exception. This exception will not be propagated and that can potentially leave you with wrong results. This can not only happen if you are constructing the whole output result set in your PTF and then use <strong>DBMS_TF.PUT_ROW_SET<\/strong>. This can just as well happen using <strong>DBMS_TF.PUT_COL<\/strong>. As well as with other collection, e.g. with replication factor as mentioned in <a href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-ptf-part-3-row-replication\/\" target=\"_blank\" rel=\"noreferrer noopener\">this post.<\/a><\/p>\n\n\n\n<p>I think it is a good idea to catch <strong>NO_DATA_FOUND <\/strong>and re-raise it as another user-defined exception. If your processing is wrong in some way, this will make your query fail, instead of returning wrong results (and you have first to figure out they are wrong!) .<\/p>\n\n\n\n<p>This also applies to normal functions (non PTF) used in SQL like shown in Listing 2 and 3. I often used to code the simplest functions without an exception handler at all. Let propagate the error and let the caller decide what should happen. It&#8217;s better than <strong>WHEN OTHERS THEN NULL<\/strong> anyway \ud83d\ude09  But if there any chance that <strong>NO_DATA_FOUND <\/strong>can be thrown &#8211; you have to be alert because you will not see it explicitly! <\/p>\n\n\n<script async src=\"\/\/pagead2.googlesyndication.com\/pagead\/js\/adsbygoogle.js?client=ca-pub-2925154690547867\" crossorigin=\"anonymous\"><\/script><ins class=\"adsbygoogle\" style=\"display:block; text-align:center;\" data-ad-client=\"ca-pub-2925154690547867\" \ndata-ad-slot=\"2727403138\" \ndata-ad-layout=\"in-article\"\ndata-ad-format=\"fluid\"><\/ins>\n<script> \n(adsbygoogle = window.adsbygoogle || []).push({}); \n<\/script>\n\n\n<div class=\"crp-list-container\"><h3 class=\"crp-list-title\">Related Posts<\/h3><ul class=\"crp-list\"><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-ptf-part-3-row-replication\/\" target=\"_blank\">Polymorphic Table Functions (PTF) , Part 3 \u2013 Row Replication<\/a><\/div><\/li><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-example-transposing-columns-to-rows\/\" target=\"_blank\">Polymorphic Table Functions Example &#8211; Transposing Columns To Rows<\/a><\/div><\/li><li class=\"crp-list-item crp-list-item-image-none\"><div class=\"crp-list-item-title\"><a href=\"https:\/\/blog.sqlora.com\/en\/polymorphic-table-functions-example-not-transposing-rows-to-columns\/\" target=\"_blank\">Polymorphic Table Functions Example \u2013 (NOT) Transposing Rows to Columns<\/a><\/div><\/li><\/ul><\/div>","protected":false},"excerpt":{"rendered":"<p>A few days ago I received a comment\/question on the older post about dynamically transposing rows to column with Polymorphic Table Functions (PTF). Back then I overlooked a bug in the example code, but the explanation takes a bit longer, so i decided to write a new post about it. The PTF was working initially [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1986,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,67,3],"tags":[144,72,70],"class_list":["post-1953","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oracle","category-ptf","category-sql","tag-no_data_found","tag-polymorphic-table-function","tag-ptf"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/comments?post=1953"}],"version-history":[{"count":30,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1953\/revisions"}],"predecessor-version":[{"id":2017,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1953\/revisions\/2017"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/1986"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=1953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=1953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=1953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}