{"id":13490,"date":"2020-02-24T19:52:34","date_gmt":"2020-02-24T18:52:34","guid":{"rendered":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/"},"modified":"2020-02-24T19:52:34","modified_gmt":"2020-02-24T18:52:34","slug":"oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance","status":"publish","type":"post","link":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/","title":{"rendered":"Oracle 20c SQL Macros: a scalar example to join agility and performance"},"content":{"rendered":"<h2>By Franck Pachot<\/h2>\n<p>.<br \/>\nLet&#8217;s say you have a PEOPLE table with FIRST_NAME and LAST_NAME and you want, in many places of your application, to display the full name. Usually my name will be displayed as &#8216;Franck Pachot&#8217; and I can simply add a virtual column to my table, or view, as: initcap(FIRST_NAME)||&#8217; &#8216;||initcap(LAST_NAME). Those are simple SQL functions. No need for procedural code there, right? But, one day, the business will come with new requirements. In some countries (I&#8217;ve heard about Hungary but there are others), my name may be displayed with last name&#8230; first, like: &#8216;Pachot Franck&#8217;. And in some context, it may have a comma like: &#8216;Pachot, Franck&#8217;.<\/p>\n<p>There comes a religious debate between Dev and Ops:<\/p>\n<ul>\n<li><b>Developer:<\/b> We need a function for that, so that the code can evolve without changing all SQL queries or views<\/li>\n<li><b>DBA<\/b>: That&#8217;s the worst you can do. Calling a function for each row is a context switch between SQL and PL\/SQL engine. Not scalable.<\/li>\n<li><b>Developer<\/b>: Ok, let&#8217;s put all that business logic in the application so that we don&#8217;t have to argue with the DBA&#8230;<\/li>\n<li><b>DBA<\/b>: Oh, that&#8217;s even worse. The database cannot perform correctly with all those row-by-row calls!<\/li>\n<li><b>Developer<\/b>: No worry, we will put the database on Kubernetes, shard and distribute it, and scale as far as we need for acceptable throughput<\/li>\n<\/ul>\n<p>And this is where we arrive in an unsustainable situation. Because we didn&#8217;t find a tradeoff between code maintainability and application performance, we get the worst from each of them: crazy resource usage for medium performance.<\/p>\n<p>However, in Oracle 20c, we have a solution for that. Did you code some C programs where you replace functions by pre-processor macros? So that your code is readable and maintainable like when using modules and functions. But compiled as if those functions have been merged to the calling code at compile time? What was common in those 3rd generation languages is now possible in a 4th generation declarative language: Oracle SQL.<\/p>\n<p>Let&#8217;s take an example. I&#8217;m building a PEOPLE table using the Linux \/usr\/share\/dict of words:<\/p>\n<pre><code>\ncreate or replace directory \"\/usr\/share\/dict\" as '\/usr\/share\/dict';\ncreate table people as\nwith w as (\nselect *\n from external((word varchar2(60))\n type oracle_loader default directory \"\/usr\/share\/dict\" access parameters (nologfile) location('linux.words'))\n) select upper(w1.word) first_name , upper(w2.word) last_name\nfrom w w1,w w2 where w1.word like 'ora%' and w2.word like 'aut%'\norder by ora_hash(w1.word||w2.word)\n\/\n<\/code><\/pre>\n<p>I have 100000 rows table here with first and last names.<br \/>\nHere is a sample:<\/p>\n<pre><code>\nSQL&gt; select count(*) from people;\n\n  COUNT(*)\n----------\n    110320\n\nSQL&gt; select * from people where rownum&lt;=10;\n\nFIRST_NAME                     LAST_NAME\n------------------------------ ------------------------------\nORACULUM                       AUTOMAN\nORANGITE                       AUTOCALL\nORANGUTANG                     AUTHIGENOUS\nORAL                           AUTOPHOBIA\nORANGUTANG                     AUTOGENEAL\nORATORIAN                      AUTOCORRELATION\nORANGS                         AUTOGRAPHICAL\nORATORIES                      AUTOCALL\nORACULOUSLY                    AUTOPHOBY\nORATRICES                      AUTOCRATICAL\n<\/code><\/pre>\n<h3>PL\/SQL function<\/h3>\n<p>Here is my function that displays the full name, with the Hungarian specificity as an example but, as it is a function, it can evolve further:<\/p>\n<pre><code>\ncreate or replace function f_full_name(p_first_name varchar2,p_last_name varchar2)\nreturn varchar2\nas\n territory varchar2(64);\nbegin\n select value into territory from nls_session_parameters\n where parameter='NLS_TERRITORY';\n case (territory)\n when 'HUNGARY'then return initcap(p_last_name)||' '||initcap(p_first_name);\n else               return initcap(p_first_name)||' '||initcap(p_last_name);\n end case;\nend;\n\/\nshow errors\n<\/code><\/pre>\n<p>The functional result depends on my session settings:<\/p>\n<pre><code>\nSQL&gt; select f_full_name(p_first_name=&gt;first_name,p_last_name=&gt;last_name) from people\n     where rownum&lt;=10;\n\nFIRST_NAME,P_LAST_NAME=&gt;LAST_NAME)\n------------------------------------------------------------------------------------------------\nOraculum Automan\nOrangite Autocall\nOrangutang Authigenous\nOral Autophobia\nOrangutang Autogeneal\nOratorian Autocorrelation\nOrangs Autographical\nOratories Autocall\nOraculously Autophoby\nOratrices Autocratical\n\n10 rows selected.\n\n<\/code><\/pre>\n<p>But let&#8217;s run it on many rows, like using this function in the where clause, with autotrace:<\/p>\n<pre><code>\nSQL&gt; set timing on autotrace on\nselect f_full_name(first_name,last_name) from people\nwhere f_full_name(p_first_name=&gt;first_name,p_last_name=&gt;last_name) like 'Oracle Autonomous';\n\nF_FULL_NAME(FIRST_NAME,LAST_NAME)\n------------------------------------------------------------------------------------------------------\nOracle Autonomous\n\nElapsed: 00:00:03.47\n\nExecution Plan\n----------------------------------------------------------\nPlan hash value: 2528372185\n\n----------------------------------------------------------------------------\n| Id  | Operation         | Name   | Rows  | Bytes | Cost (%CPU)| Time     |\n----------------------------------------------------------------------------\n|   0 | SELECT STATEMENT  |        |  1103 | 25369 |   129   (8)| 00:00:01 |\n|*  1 |  TABLE ACCESS FULL| PEOPLE |  1103 | 25369 |   129   (8)| 00:00:01 |\n----------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n\n   1 - filter(\"F_FULL_NAME\"(\"P_FIRST_NAME\"=&gt;\"FIRST_NAME\",\"P_LAST_NAME\"=&gt;\n              \"LAST_NAME\")='Oracle Autonomous')\n\n\nStatistics\n----------------------------------------------------------\n     110361  recursive calls\n          0  db block gets\n        426  consistent gets\n          0  physical reads\n          0  redo size\n        608  bytes sent via SQL*Net to client\n        506  bytes received via SQL*Net from client\n          2  SQL*Net roundtrips to\/from client\n          0  sorts (memory)\n          0  sorts (disk)\n          1  rows processed\n<\/code><\/pre>\n<p>100000 recursive calls. That is bad and not scalable. The time spent in context switches from the SQL to the PL\/SQL engine is a waste of CPU cycles.<\/p>\n<p>Note that this is difficult to improve because we cannot create on index for that predicate:<\/p>\n<pre><code>\nSQL&gt; create index people_full_name on people(f_full_name(first_name,last_name));\ncreate index people_full_name on people(f_full_name(first_name,last_name))\n                                        *\nERROR at line 1:\nORA-30553: The function is not deterministic\n<\/code><\/pre>\n<p>Yes, this function cannot be deterministic because it depends on many other parameters (like the territory in this example, in order to check if I am in Hungary)<\/p>\n<h3>Update 25-FEB-2020<\/h3>\n<p>If you have read this post yesterday, pleased note that I&#8217;ve updated it. I initially didn&#8217;t add the territory parameter, thinking that changing NLS_TERRITORY would re-parse the query but it seems that cursors are shared across different territories. Anyway, the <a href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/20\/lnpls\/plsql-language-elements.html#GUID-292C3A17-2A4B-4EFB-AD38-68DF6380E5F7\" rel=\"noopener noreferrer\" target=\"_blank\">documentation<\/a> says:<br \/>\n<i>Although the DETERMINISTIC property cannot be specified, a SQL macro is always implicitly deterministic.<\/i><br \/>\nSo, better not relying on child cursor sharing. Thanks to Stew Ashtom for the heads up on that:<\/p>\n<blockquote class=\"twitter-tweet\" data-width=\"500\" data-dnt=\"true\">\n<p lang=\"en\" dir=\"ltr\">Ouch, I was wrong&#8230; different NLS_TERRITORY share the same cursor \ud83d\ude31 <a href=\"https:\/\/t.co\/BvPTLXWWpY\">pic.twitter.com\/BvPTLXWWpY<\/a><\/p>\n<p>&mdash; Franck Pachot (@FranckPachot) <a href=\"https:\/\/twitter.com\/FranckPachot\/status\/1232244899258552320?ref_src=twsrc%5Etfw\">February 25, 2020<\/a><\/p><\/blockquote>\n<p><script async src=\"https:\/\/platform.twitter.com\/widgets.js\" charset=\"utf-8\"><\/script><\/p>\n<h3>SQL Macro<\/h3>\n<p>The solution in 20c, currently available in the Oracle Cloud, here is very easy. I create a new function, M_FULL_NAME, when the only differences with F_FULL_NAME are:<\/p>\n<ol>\n<li> I add the SQL_MACRO(SCALAR) keyword and change the return type to varchar2 (if not already)<\/li>\n<li> I enclose the return expression value in quotes (using q'[ &#8230; ]&#8217; for better readability) to return it as a varchar2 containing the expression string where variable names are just placeholders (no bind variables here!)<\/li>\n<li> I add all external values as parameters because the SQL_MACRO function must be deterministic<\/li>\n<\/ol>\n<pre><code>\ncreate or replace function m_full_name(p_first_name varchar2,p_last_name varchar2,territory varchar2)\nreturn varchar2 SQL_MACRO(SCALAR)\nas\nbegin\n case (territory)\n when 'HUNGARY'then return q'[initcap(p_last_name)||' '||initcap(p_first_name)]';\n else               return q'[initcap(p_first_name)||' '||initcap(p_last_name)]';\n end case;\nend;\n\/\n<\/code><\/pre>\n<p>Here is the difference if I call both of them:<\/p>\n<pre><code>\nSQL&gt; set serveroutput on\nSQL&gt; exec dbms_output.put_line(f_full_name('AAA','BBB'));\nAaa Bbb\n\nPL\/SQL procedure successfully completed.\n\nSQL&gt; exec dbms_output.put_line(m_full_name('AAA','BBB','SWITZERLAND'));\ninitcap(p_first_name)||' '||initcap(p_last_name)\n\nPL\/SQL procedure successfully completed.\n\nSQL&gt; select m_full_name('AAA','BBB','SWITZERLAND') from dual;\n\nM_FULL_\n-------\nAaa Bbb\n<\/code><\/pre>\n<p>One returns the function value, the other returns the expression that can be used to return the value. It is a SQL Macro that can be applied to a SQL text to replace part of it &#8211; a scalar expression in this case as I mentioned SQL_MACRO(SCALAR)<\/p>\n<p>The result is the same as with the previous function:<\/p>\n<pre><code>\nSQL&gt; select m_full_name(p_first_name=&gt;first_name,p_last_name=&gt;last_name,territory=&gt;'SWITZERLAND') from people\n     where rownum&lt;=10;\n\nM_FULL_NAME(P_FIRST_NAME=&gt;FIRST_NAME,P_LAST_NAME=&gt;LAST_NAME,TERRITORY=&gt;'SWITZERLAND')\n------------------------------------------------------------------------------------------------------------------------\nOraculum Automan\nOrangite Autocall\nOrangutang Authigenous\nOral Autophobia\nOrangutang Autogeneal\nOratorian Autocorrelation\nOrangs Autographical\nOratories Autocall\nOraculously Autophoby\nOratrices Autocratical\n\n\n10 rows selected.\n<\/code><\/pre>\n<p>And now let&#8217;s look at the query using this as a predicate:<\/p>\n<pre><code>\nSQL&gt; set timing on autotrace on\nSQL&gt; select m_full_name(first_name,last_name,territory=&gt;'SWITZERLAND') from people\n     where m_full_name(p_first_name=&gt;first_name,p_last_name=&gt;last_name,territory=&gt;'SWITZERLAND') like 'Oracle Autonomous';\n\nM_FULL_NAME(FIRST_NAME,LAST_NAME,TERRITORY=&gt;'SWITZERLAND')\n------------------------------------------------------------------------------------------------------------------------\nOracle Autonomous\n\nElapsed: 00:00:00.01\n\nExecution Plan\n----------------------------------------------------------\nPlan hash value: 1341595178\n\n------------------------------------------------------------------------------------------------\n| Id  | Operation        | Name                        | Rows  | Bytes | Cost (%CPU)| Time     |\n------------------------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT |                             |     1 |    46 |     4   (0)| 00:00:01 |\n|*  1 |  INDEX RANGE SCAN| PEOPLE_FULL_NAME_FIRST_LAST |     1 |       |     3   (0)| 00:00:01 |\n------------------------------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n\n   1 - access(INITCAP(\"FIRST_NAME\")||' '||INITCAP(\"LAST_NAME\")='Oracle Autonomous')\n\n\nStatistics\n----------------------------------------------------------\n         42  recursive calls\n          0  db block gets\n         92  consistent gets\n          7  physical reads\n          0  redo size\n        633  bytes sent via SQL*Net to client\n        565  bytes received via SQL*Net from client\n          2  SQL*Net roundtrips to\/from client\n          0  sorts (memory)\n          0  sorts (disk)\n          1  rows processed\n\n<\/code><\/pre>\n<p>I don&#8217;t have all those row-by-row recursive calls. And the difference is easy to see in the execution plan predicate sections: there&#8217;s no call to my PL\/SQL function there. It was called only at parse time to transform the SQL statement: now only using the string returned by the macro, with parameter substitution.<\/p>\n<p>That was my goal: stay in SQL engine for the execution, calling only standard SQL functions. But while we are in the execution plan, can we do something to avoid the full table scan? My function is not deterministic but has a small number of variations. Two in my case. Then I can create an index for each one:<\/p>\n<pre><code>\n \nSQL&gt;\nSQL&gt; create index people_full_name_first_last on people(initcap(first_name)||' '||initcap(last_name));\nIndex created.\n\nSQL&gt; create index people_full_name_first_first on people(initcap(last_name)||' '||initcap(first_name));\nIndex created.\n<\/code><\/pre>\n<p>And run my query again:<\/p>\n<pre><code>\nSQL&gt; select m_full_name(first_name,last_name,'SWITZERLAND') from people\n     where m_full_name(p_first_name=&gt;first_name,p_last_name=&gt;last_name,'SWITZERLAND') like 'Autonomous Oracle';\n\nno rows selected\n\nElapsed: 00:00:00.01\n\nExecution Plan\n----------------------------------------------------------\nPlan hash value: 1341595178\n\n------------------------------------------------------------------------------------------------\n| Id  | Operation        | Name                        | Rows  | Bytes | Cost (%CPU)| Time     |\n------------------------------------------------------------------------------------------------\n|   0 | SELECT STATEMENT |                             |  1103 | 25369 |   118   (0)| 00:00:01 |\n|*  1 |  INDEX RANGE SCAN| PEOPLE_FULL_NAME_FIRST_LAST |   441 |       |     3   (0)| 00:00:01 |\n------------------------------------------------------------------------------------------------\n\nPredicate Information (identified by operation id):\n---------------------------------------------------\n\n   1 - access(INITCAP(\"FIRST_NAME\")||' '||INITCAP(\"LAST_NAME\")='Autonomous Oracle')\n<\/code><\/pre>\n<h3>Performance and agility<\/h3>\n<p>Now we are ready to bring back the business logic into the database so that it is co-located with data and run within the same process. Thanks to SQL Macros, we can even run it within the same engine, SQL, calling the PL\/SQL one only at compile time to resolve the macro. And we keep full code maintainability as the logic is defined in a function that can evolve and be used in many places without duplicating the code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>By Franck Pachot . Let&#8217;s say you have a PEOPLE table with FIRST_NAME and LAST_NAME and you want, in many places of your application, to display the full name. Usually my name will be displayed as &#8216;Franck Pachot&#8217; and I can simply add a virtual column to my table, or view, as: initcap(FIRST_NAME)||&#8217; &#8216;||initcap(LAST_NAME). Those [&hellip;]<\/p>\n","protected":false},"author":27,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[955,1320,59],"tags":[1763,1841,96,458,98],"type_dbi":[],"class_list":["post-13490","post","type-post","status-publish","format-standard","hentry","category-cloud","category-devops","category-oracle","tag-20c","tag-macros","tag-oracle","tag-oracle-20c","tag-sql"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Oracle 20c SQL Macros: a scalar example to join agility and performance - dbi Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Oracle 20c SQL Macros: a scalar example to join agility and performance\" \/>\n<meta property=\"og:description\" content=\"By Franck Pachot . Let&#8217;s say you have a PEOPLE table with FIRST_NAME and LAST_NAME and you want, in many places of your application, to display the full name. Usually my name will be displayed as &#8216;Franck Pachot&#8217; and I can simply add a virtual column to my table, or view, as: initcap(FIRST_NAME)||&#8217; &#8216;||initcap(LAST_NAME). Those [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\" \/>\n<meta property=\"og:site_name\" content=\"dbi Blog\" \/>\n<meta property=\"article:published_time\" content=\"2020-02-24T18:52:34+00:00\" \/>\n<meta name=\"author\" content=\"Oracle Team\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Oracle Team\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"9 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\"},\"author\":{\"name\":\"Oracle Team\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee\"},\"headline\":\"Oracle 20c SQL Macros: a scalar example to join agility and performance\",\"datePublished\":\"2020-02-24T18:52:34+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\"},\"wordCount\":985,\"commentCount\":0,\"keywords\":[\"20c\",\"Macros\",\"Oracle\",\"Oracle 20c\",\"SQL\"],\"articleSection\":[\"Cloud\",\"DevOps\",\"Oracle\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\",\"name\":\"Oracle 20c SQL Macros: a scalar example to join agility and performance - dbi Blog\",\"isPartOf\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\"},\"datePublished\":\"2020-02-24T18:52:34+00:00\",\"author\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee\"},\"breadcrumb\":{\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/www.dbi-services.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Oracle 20c SQL Macros: a scalar example to join agility and performance\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#website\",\"url\":\"https:\/\/www.dbi-services.com\/blog\/\",\"name\":\"dbi Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee\",\"name\":\"Oracle Team\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g\",\"caption\":\"Oracle Team\"},\"url\":\"https:\/\/www.dbi-services.com\/blog\/author\/oracle-team\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Oracle 20c SQL Macros: a scalar example to join agility and performance - dbi Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/","og_locale":"en_US","og_type":"article","og_title":"Oracle 20c SQL Macros: a scalar example to join agility and performance","og_description":"By Franck Pachot . Let&#8217;s say you have a PEOPLE table with FIRST_NAME and LAST_NAME and you want, in many places of your application, to display the full name. Usually my name will be displayed as &#8216;Franck Pachot&#8217; and I can simply add a virtual column to my table, or view, as: initcap(FIRST_NAME)||&#8217; &#8216;||initcap(LAST_NAME). Those [&hellip;]","og_url":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/","og_site_name":"dbi Blog","article_published_time":"2020-02-24T18:52:34+00:00","author":"Oracle Team","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Oracle Team","Est. reading time":"9 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#article","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/"},"author":{"name":"Oracle Team","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee"},"headline":"Oracle 20c SQL Macros: a scalar example to join agility and performance","datePublished":"2020-02-24T18:52:34+00:00","mainEntityOfPage":{"@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/"},"wordCount":985,"commentCount":0,"keywords":["20c","Macros","Oracle","Oracle 20c","SQL"],"articleSection":["Cloud","DevOps","Oracle"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/","url":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/","name":"Oracle 20c SQL Macros: a scalar example to join agility and performance - dbi Blog","isPartOf":{"@id":"https:\/\/www.dbi-services.com\/blog\/#website"},"datePublished":"2020-02-24T18:52:34+00:00","author":{"@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee"},"breadcrumb":{"@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.dbi-services.com\/blog\/oracle-20c-sql-macros-a-scalar-example-to-join-agility-and-performance\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/www.dbi-services.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Oracle 20c SQL Macros: a scalar example to join agility and performance"}]},{"@type":"WebSite","@id":"https:\/\/www.dbi-services.com\/blog\/#website","url":"https:\/\/www.dbi-services.com\/blog\/","name":"dbi Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.dbi-services.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/www.dbi-services.com\/blog\/#\/schema\/person\/66ab87129f2d357f09971bc7936a77ee","name":"Oracle Team","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f711f7cd2c9b09bf2627133755b569fb5be0694810cfd33033bdd095fedba86d?s=96&d=mm&r=g","caption":"Oracle Team"},"url":"https:\/\/www.dbi-services.com\/blog\/author\/oracle-team\/"}]}},"_links":{"self":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/13490","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/users\/27"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/comments?post=13490"}],"version-history":[{"count":0,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/posts\/13490\/revisions"}],"wp:attachment":[{"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/media?parent=13490"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/categories?post=13490"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/tags?post=13490"},{"taxonomy":"type","embeddable":true,"href":"https:\/\/www.dbi-services.com\/blog\/wp-json\/wp\/v2\/type_dbi?post=13490"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}