{"id":1205,"date":"2020-03-03T10:00:34","date_gmt":"2020-03-03T08:00:34","guid":{"rendered":"https:\/\/blog.sqlora.com\/en\/?p=1205"},"modified":"2020-03-22T18:22:00","modified_gmt":"2020-03-22T16:22:00","slug":"building-hash-keys-using-sql-macros-in-oracle-20c","status":"publish","type":"post","link":"https:\/\/blog.sqlora.com\/en\/building-hash-keys-using-sql-macros-in-oracle-20c\/","title":{"rendered":"Building Hash Keys using SQL Macros in Oracle 20c"},"content":{"rendered":"\n<p>In the next post about SQL macros in Oracle 20c we look at how they could be useful for building hash keys. If you are familiar with Data Warehousing and <a href=\"https:\/\/en.wikipedia.org\/wiki\/Data_vault_modeling\">Data Vault<\/a> modelling approach, you will probably know why it can be a good idea to build hash keys or hash diffs. Anyway, we will not discuss whether or not you should use them, but rather how you can do this in Oracle in a consistent and performant way.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>There are several ways to generate a hash key in Oracle. The fastest one is by using a SQL function <strong>STANDARD_HASH<\/strong> which has been introduced in Oracle 12.1. For more details see the <a href=\"https:\/\/danischnider.wordpress.com\/2017\/01\/24\/how-to-build-hash-keys-in-oracle\/\">blog post of my colleague Dani Schnider<\/a>.    <\/p>\n\n\n\n<p>On the other hand it is very important to establish a set or rules to follow while building a hash key, especially in case of multi-column keys or even hashing all columns of a data row to produce a hash diff. For example you have to  cast all data types to string using the same format mask, concatenate all strings using the same delimiter an so on. <\/p>\n\n\n\n<p>A proven idea is to encapsulate these rules in a PL\/SQL procedure, that should be used by everyone in your project to build hash keys. In the past, it was not so easy to make such a procedure generic though. Since Oracle 18c we could try to do it in a flexible way using  polymorphic table functions (PTF). But there is still a problem with this approach. <\/p>\n\n\n\n<p>STANDARD_HASH is only available in SQL. You cannot call it directly in PL\/SQL. This is still true in 20c.  The only way is doing a SELECT  FROM DUAL. But doing SELECT out of PTF will cause the infamous context switch!<\/p>\n\n\n\n<p>So we need to encapsulate the rule set in the PL\/SQL procedure, while remaining in a SQL engine. It&#8217;s where SQL macros introduced in 20c can help, I guess.<\/p>\n\n\n\n<p>Let&#8217;s take an EMP table as an example and look at what we want to achieve and what the problem is. We&#8217;re using a function STANDARD_HASH to build a MD5-hash of a primary key and of the whole row, which means all fields converted to string and concatenated with a defined delimiter. Note that I formatted the values so that you see only a narrower part or it.  <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; SELECT STANDARD_HASH(TO_CHAR(empno),&#039;MD5&#039;) as hash_key\n,      STANDARD_HASH(TO_CHAR(empno)||&#039;#&#039;||ename||&#039;#&#039;||job||&#039;#&#039;||\n                     TO_CHAR(mgr)||&#039;#&#039;||TO_CHAR(hiredate,&#039;YYYYMMDD&#039;)\n                     ||&#039;#&#039;||TO_CHAR(sal)||&#039;#&#039;||TO_CHAR(comm)\n                     ||&#039;#&#039;||TO_CHAR(deptno), &#039;MD5&#039;) as hash_diff\n,      e.*\nFROM   emp e\nFETCH FIRST 3 ROWS ONLY\n\nHASH_KEY   HASH_DIFF       EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO\n---------- ---------- ---------- ---------- --------- ---------- --------- ---------- ---------- ----------\n0D7F9017FB 42CB6932B4       7369 SMITH      CLERK           7902 17-DEC-80        800                    20\n7A2B33C672 AA63299F72       7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30\nE1E1F667CE 27332DD16B       7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30\n\n3 rows selected.\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 1: Manually build a hash key and a hash diff<\/em><\/strong><\/p>\n\n\n\n<p>It is obvious that if you have a concatenated key or in case of hash diffs, the expressions to be hashed become so complex that they can hardly be handled manually. Imagine what it looks like with tables of few hundreds of columns! You need a generator (Data Warehouse Automation tool, like  <a href=\"https:\/\/biginius.info\">BiGENiUS<\/a> for example) to generate this code. If you don&#8217;t have one, you can still generate only a hardest part of such SQL statement &#8211; with a SQL macro! <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Implementation with SQL macro<\/h4>\n\n\n\n<p>Now what kind of macro should we try: scalar or table? At first sight, we have to return scalar values &#8211; hash_key and hash_diff &#8211; so why not use scalar macro? But note that we than have to pass all the columns as parameter to build a hash diff. Again, think of a table with few hundred columns \ud83d\ude09 Such macro will be table specific, that means you just need another macro with different parameters for another table. And last but not least, to be able to apply string conversion rules, we have to get the data type information for every column, likely querying the data dictionary.  <\/p>\n\n\n\n<p>Table macros, on the other hand, don&#8217;t have those disadvantages and can be made really generic. <\/p>\n\n\n\n<p>Table is the default type for SQL macros, so you don&#8217;t have to specify the type in the function annotation. Table macros can only appear in a FROM clause.  So what we want to implement, is a SQM function which can be queried as follows and will pass all source columns  through adding two new columns HASH_KEY and HASH_DIFF:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSELECT e.hash_key\n,      e.hash_diff\n,      e.*\nFROM   add_hash_columns (emp) e;\n   \n<\/pre><\/div>\n\n\n<p><strong><em>Listing 2: The desired way to use a SQL macro table function<\/em><\/strong><\/p>\n\n\n\n<p>The key difference is that table macros can accept <strong>table parameters<\/strong>. You may know what table parameters are, if you are already familiar with <a href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/20\/lnpls\/plsql-optimization-and-tuning.html#GUID-981102A8-5204-4931-B10A-93486304B184\">Polymorphic Table Functions (PTF)<\/a> which have been introduced in Oracle 18c. I have also <a href=\"https:\/\/blog.sqlora.com\/en\/tag\/ptf\/\">blogged a lot about PTF<\/a>. A PTF must have exactly one parameter of type TABLE. For use in DESCRIBE method this will be implicitly converted in record type defined in DBMS_TF package: DBMS_TF.TABLE_T. With SQL macros you cannot define arguments of type TABLE but instead you can directly use DBMS_TF.TABLE_T. Passing a table name to a macro function leads to implicit conversion to that record type.<\/p>\n\n\n\n<p>Let&#8217;s start with a macro to build a hash diff only. For now, all we need is just one table parameter of the type DBMS_TF.TALE_T.  The DBMS_TF.TABLE_T record contains all information we need: all columns with their data types as long as the table name. How exactly do we get at it? Please note that the documentation of <a href=\"https:\/\/docs.oracle.com\/en\/database\/oracle\/oracle-database\/20\/arpls\/DBMS_TF.html#GUID-C29EF370-F918-4199-B8B2-A20B8EC47967\">DBMS_TF.TABLE_T<\/a> is not correct as of now. You can refer the package specification in the database itself, where you&#8217;ll find the table_name as an atribute:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n  TYPE TABLE_T          IS RECORD\n  (\n    column                TABLE_COLUMNS_T,     \/* Column information *\/\n    schema_name           dbms_quoted_id,  \/* the schema name OF ptf *\/\n    package_name          dbms_quoted_id, \/* the package name OF ptf *\/\n    ptf_name              dbms_quoted_id,    \/* the ptf name invoked *\/\n    table_schema_name     dbms_quoted_id,       \/* schema name table *\/\n    table_name            dbms_quoted_id               \/* table name *\/\n  );\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 3: the definition or the record DBMS_TF.TABLE_T<\/em><\/strong><\/p>\n\n\n\n<p>The field <strong><em>column<\/em><\/strong> is of type <strong>TABLE_COLUMNS_T<\/strong> which is a table of the type <strong>COLUMN_T<\/strong>, which is turn defined as:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nTYPE COLUMN_T          IS RECORD\n  (\n    description            COLUMN_METADATA_T, \/* Column metadata *\/\n    pass_through           BOOLEAN,      \/* Pass-through column? *\/\n    for_read               BOOLEAN  \/* Column data will be read? *\/\n  );\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 4: record type COLUMN_T<\/em><\/strong><\/p>\n\n\n\n<p> Next we look at the the field <strong><em>description<\/em><\/strong> and what its datatype  <strong>COLUMN_METADATA_T<\/strong> means (Listing 5). Here we find the information we need to cast actual data types a strings.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nTYPE COLUMN_METADATA_T IS RECORD\n  (\n    type               PLS_INTEGER DEFAULT TYPE_VARCHAR2,\n    max_len            PLS_integer DEFAULT -1,\n    name               VARCHAR2(32767),\n    name_len           PLS_INTEGER,\n    \/* following two attributes are used for numerical data *\/\n    precision          PLS_INTEGER,\n    scale              PLS_INTEGER,\n    \/* following three attributes are used for character data *\/\n    charsetid          PLS_INTEGER,\n    charsetform        PLS_INTEGER,\n    collation          PLS_INTEGER,\n    \/* following attributes may be used in future *\/\n    schema_name        DBMS_QUOTED_ID,\n    schema_name_len    PLS_INTEGER,\n    type_name          DBMS_QUOTED_ID,\n    type_name_len      PLS_INTEGER\n  );\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 5: record type COLUMN_METADATA_T<\/em><\/strong><\/p>\n\n\n\n<p> Now we know all the ingredients and can implement the function (Listing 6). Let inspect the code:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li> line 9 &#8211; we&#8217;re building hash diff of the whole row, so we iterate through all columns<\/li><li>line 10 &#8211;  the name of the column is in  <em><strong>t.column(i).description.name<\/strong><\/em> <\/li><li>lines 11, 13, 15 &#8211; <em><strong>t.column(i).description.type<\/strong><\/em> that is the way we get the data type of particular column that we compare to the constants defined in DBMS_TF<\/li><li>lines 12, 14, 16, 18 &#8211; continue to build a string to be hashed bearing in mind the rules for converting different data types, using delimiter and so on<\/li><li>line 21 &#8211; build and return the expression for then STANDARD_HASH call along with selecting all other columns from the table (<s>we have the table name in <\/s><strong><em><s>t.table_name<\/s><\/em><\/strong>)<\/li><\/ul>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><p><strong>UPDATE 22.03.2020: We don&#8217;t actually have to concatenate the table name as in  line 21 at the end of the Listing 6. See the update at the end of this post<\/strong><\/p><\/blockquote>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE FUNCTION add_hash_columns(t DBMS_TF.TABLE_T) \nRETURN VARCHAR2 SQL_MACRO(TABLE)\nAS\n v_hdiff clob ;\n v_str   varchar2(200);\n v_delimiter varchar2(9):= &#039;||&#039;&#039;#&#039;&#039;||&#039;;\n v_name dbms_id;\nBEGIN\n  FOR I IN 1..t.column.count LOOP\n    v_name := t.column(i).description.name;\n    IF t.column(i).description.type = dbms_tf.type_varchar2 THEN  \n      v_str := v_name;\n    ELSIF t.column(i).description.type = dbms_tf.type_number THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;)&#039;;\n    ELSIF t.column(i).description.type = dbms_tf.type_date THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;,&#039;&#039;YYYYMMDD&#039;&#039;)&#039;;\n    END IF;\n    v_hdiff := v_hdiff || v_delimiter || v_str;\n  END LOOP;\n  v_hdiff := LTRIM(v_hdiff,&#039;|&#039;&#039;#&#039;);\n  RETURN &#039;SELECT  STANDARD_HASH(&#039;||v_hdiff||&#039;,&#039;&#039;MD5&#039;&#039;) hash_diff, &#039;||\n         &#039; e.* FROM &#039;||t.table_name ||&#039; e&#039;;\nEND;\n\/\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 6: Implementation for hash diff<\/em><\/strong><\/p>\n\n\n\n<p>That&#8217;s it! And it works! Even better, in that case the EXPAND_SQL_TEXT procedure is showing us the real SQL statement that runs:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nSQL&gt; SELECT e.*\nFROM   add_hash_columns (emp) e\nFETCH FIRST 3 ROWS ONLY\n\nHASH_DIFF       EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO\n---------- ---------- ---------- --------- ---------- --------- ---------- ---------- ----------\n42CB6932B4       7369 SMITH      CLERK           7902 17-DEC-80        800                    20\nAA63299F72       7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30\n27332DD16B       7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30\n\n3 rows selected.\n\nDECLARE\n  l_clob CLOB;\nBEGIN\n  DBMS_UTILITY.expand_sql_text (\n    input_sql_text  =&gt; q&#039;!SELECT e.* FROM   add_hash_columns (emp) e!&#039;,\n    output_sql_text =&gt; l_clob  );\n  DBMS_OUTPUT.put_line(l_clob);\nEND;\n\/\n\nSELECT &quot;A1&quot;.&quot;HASH_DIFF&quot; &quot;HASH_DIFF&quot;,&quot;A1&quot;.&quot;EMPNO&quot; &quot;EMPNO&quot;,&quot;A1&quot;.&quot;ENAME&quot; &quot;ENAME&quot;,&quot;A1&quot;.&quot;JOB&quot; &quot;JOB&quot;,&quot;A1&quot;.&quot;MGR&quot; &quot;MGR&quot;,&quot;A1&quot;.&quot;HIREDATE&quot; &quot;HIREDATE&quot;,&quot;A1&quot;.&quot;SAL&quot; &quot;SAL&quot;,&quot;A1&quot;.&quot;COMM&quot; &quot;COMM&quot;,&quot;A1&quot;.&quot;DEPTNO&quot; &quot;DEPTNO&quot; \nFROM  (\nSELECT &quot;A2&quot;.&quot;HASH_DIFF&quot; &quot;HASH_DIFF&quot;,&quot;A2&quot;.&quot;EMPNO&quot; &quot;EMPNO&quot;,&quot;A2&quot;.&quot;ENAME&quot; &quot;ENAME&quot;,&quot;A2&quot;.&quot;JOB&quot; &quot;JOB&quot;,&quot;A2&quot;.&quot;MGR&quot; &quot;MGR&quot;,&quot;A2&quot;.&quot;HIREDATE&quot; &quot;HIREDATE&quot;,&quot;A2&quot;.&quot;SAL&quot; &quot;SAL&quot;,&quot;A2&quot;.&quot;COMM&quot; &quot;COMM&quot;,&quot;A2&quot;.&quot;DEPTNO&quot; &quot;DEPTNO&quot; \nFROM  (\nSELECT STANDARD_HASH(TO_CHAR(&quot;A3&quot;.&quot;EMPNO&quot;)||&#039;#&#039;||&quot;A3&quot;.&quot;ENAME&quot;||&#039;#&#039;||&quot;A3&quot;.&quot;JOB&quot;||\n&#039;#&#039;||TO_CHAR(&quot;A3&quot;.&quot;MGR&quot;)||&#039;#&#039;||TO_CHAR(&quot;A3&quot;.&quot;HIREDATE&quot;,&#039;YYYYMMDD&#039;)||\n&#039;#&#039;||TO_CHAR(&quot;A3&quot;.&quot;SAL&quot;)||&#039;#&#039;||TO_CHAR(&quot;A3&quot;.&quot;COMM&quot;)||\n&#039;#&#039;||TO_CHAR(&quot;A3&quot;.&quot;DEPTNO&quot;),&#039;MD5&#039;) &quot;HASH_DIFF&quot;,\n&quot;A3&quot;.&quot;EMPNO&quot; &quot;EMPNO&quot;,&quot;A3&quot;.&quot;ENAME&quot; &quot;ENAME&quot;,&quot;A3&quot;.&quot;JOB&quot; &quot;JOB&quot;,&quot;A3&quot;.&quot;MGR&quot; &quot;MGR&quot;,&quot;A3&quot;.&quot;HIREDATE&quot; &quot;HIREDATE&quot;,&quot;A3&quot;.&quot;SAL&quot; &quot;SAL&quot;,&quot;A3&quot;.&quot;COMM&quot; &quot;COMM&quot;,&quot;A3&quot;.&quot;DEPTNO&quot; &quot;DEPTNO&quot; \nFROM &quot;ONFTEST&quot;.&quot;EMP&quot; &quot;A3&quot;) &quot;A2&quot;) &quot;A1&quot;\n\n\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 7: Run the SQL with macro<\/em><\/strong><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Passing column lists<\/h4>\n\n\n\n<p>We started with building a hash diff only  and I said it would be easier. Why so? Because we need ALL columns and could simply iterate through them. In case of a hash key we are building a hash out of the business key. Often it is a primary key of the table but not always. The safest way would be to explicitly name the column(s) to be hashed. How do we pass the column list? <\/p>\n\n\n\n<p>With a PTF we can use an operator <strong>COLUMNS<\/strong> for that. Fortunately, we can also use it with SQL macros! Unfortunately, it is not mentioned in the documentation. Simply pass a list of columns inside this operator. The database will implicitly convert it to the type <strong>DBMS_TF.COLUMNS_T<\/strong>, which is a table of DBMS_QUOTED_ID.<\/p>\n\n\n\n<p>Now we are ready to extend the implementation (Listing 8). As we iterate through the columns, just check if the current column is member of the columns array (line 21) and build an additional string to hash for the key. <\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE FUNCTION add_hash_columns(t DBMS_TF.TABLE_T\n                                          , key_cols DBMS_TF.COLUMNS_T) \nRETURN VARCHAR2 SQL_MACRO(TABLE)\nAS\n v_hdiff clob ;\n v_hkey  clob ;\n v_str   varchar2(200);\n v_delimiter varchar2(9):= &#039;||&#039;&#039;#&#039;&#039;||&#039;;\n v_name dbms_id;\nBEGIN\n  FOR I IN 1..t.column.count LOOP\n    v_name := t.column(i).description.name;\n    IF t.column(i).description.type = dbms_tf.type_varchar2 THEN  \n      v_str := v_name;\n    ELSIF t.column(i).description.type = dbms_tf.type_number THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;)&#039;;\n    ELSIF t.column(i).description.type = dbms_tf.type_date THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;,&#039;&#039;YYYYMMDD&#039;&#039;)&#039;;\n    END IF;\n    v_hdiff := v_hdiff || v_delimiter || v_str;\n    IF v_name MEMBER OF key_cols THEN\n      v_hkey := v_hkey || v_delimiter || v_str;\n    END IF; \n  END LOOP;\n  v_hdiff := LTRIM(v_hdiff,&#039;|&#039;&#039;#&#039;);\n  v_hkey := LTRIM(v_hkey,&#039;|&#039;&#039;#&#039;);\n  RETURN &#039;SELECT STANDARD_HASH(&#039;||v_hkey||&#039;,&#039;&#039;MD5&#039;&#039;) hash_key, &#039;||\n         &#039;       STANDARD_HASH(&#039;||v_hdiff||&#039;,&#039;&#039;MD5&#039;&#039;) hash_diff, &#039;||\n         &#039; e.* FROM &#039;||t.table_name ||&#039; e&#039;;\nEND;\n\/\n\nSQL&gt; SELECT e.*\nFROM   add_hash_columns (emp, COLUMNS(empno)) e\nFETCH FIRST 3 ROWS ONLY\n\nHASH_KEY   HASH_DIFF       EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO\n---------- ---------- ---------- ---------- --------- ---------- --------- ---------- ---------- ----------\n0D7F9017FB 42CB6932B4       7369 SMITH      CLERK           7902 17-DEC-80        800                    20\n7A2B33C672 AA63299F72       7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30\nE1E1F667CE 27332DD16B       7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30\n\n3 rows selected.\n<\/pre><\/div>\n\n\n<p><strong><em>Listing 8: the final implementation<\/em><\/strong><\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Conclusion<\/h4>\n\n\n\n<p>I think, this &#8220;marriage&#8221; of PTF and SQL macros is a great thing indeed. Look at the Listing 8. We were able to produce the same output as desired in the Listing 1.  We were able to hide the complex logic and provide one generic function to use across the whole project. And we have not compromised the performance in any way! The real SQL statement running is the actually the same as in the Listing 1. So everything happens in the SQL engine with no context switches at all. This use case alone is enough for me to become a fan of SQL macros. But that is not all, stay tuned! <\/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\n<h4 class=\"wp-block-heading\">UPDATE<\/h4>\n\n\n\n<p>We don&#8217;t actually have to concatenate the table name as shown in the RETURN operator of the function in Listing 6 and 8. The parameter substitution will just work with table parameters as well. We can just use the parameter name in the return string and the actual table name (or even a subquery if you want) will be substituted and incorporated in the target SQL statement:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\nCREATE OR REPLACE FUNCTION add_hash_columns(t DBMS_TF.TABLE_T\n                                          , key_cols DBMS_TF.COLUMNS_T) \nRETURN VARCHAR2 SQL_MACRO(TABLE)\nAS\n v_hdiff clob ;\n v_hkey  clob ;\n v_str   varchar2(200);\n v_delimiter varchar2(9):= &#039;||&#039;&#039;#&#039;&#039;||&#039;;\n v_name dbms_id;\nBEGIN\n  FOR I IN 1..t.column.count LOOP\n    v_name := t.column(i).description.name;\n    IF t.column(i).description.type = dbms_tf.type_varchar2 THEN  \n      v_str := v_name;\n    ELSIF t.column(i).description.type = dbms_tf.type_number THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;)&#039;;\n    ELSIF t.column(i).description.type = dbms_tf.type_date THEN\n      v_str := &#039;to_char(&#039;||v_name||&#039;,&#039;&#039;YYYYMMDD&#039;&#039;)&#039;;\n    END IF;\n    v_hdiff := v_hdiff || v_delimiter || v_str;\n    IF v_name MEMBER OF key_cols THEN\n      v_hkey := v_hkey || v_delimiter || v_str;\n    END IF; \n  END LOOP;\n  v_hdiff := LTRIM(v_hdiff,&#039;|&#039;&#039;#&#039;);\n  v_hkey := LTRIM(v_hkey,&#039;|&#039;&#039;#&#039;);\n  RETURN &#039;SELECT STANDARD_HASH(&#039;||v_hkey||&#039;,&#039;&#039;MD5&#039;&#039;) hash_key, &#039;||\n         &#039;       STANDARD_HASH(&#039;||v_hdiff||&#039;,&#039;&#039;MD5&#039;&#039;) hash_diff, &#039;||\n         &#039; t.* FROM t&#039;;\nEND;\n\/\nSQL&gt; SELECT e.*\nFROM   add_hash_columns (emp, COLUMNS(empno)) e\nFETCH FIRST 3 ROWS ONLY\n\nHASH_KEY   HASH_DIFF       EMPNO ENAME      JOB              MGR HIREDATE         SAL       COMM     DEPTNO\n---------- ---------- ---------- ---------- --------- ---------- --------- ---------- ---------- ----------\n0D7F9017FB 42CB6932B4       7369 SMITH      CLERK           7902 17-DEC-80        800                    20\n7A2B33C672 AA63299F72       7499 ALLEN      SALESMAN        7698 20-FEB-81       1600        300         30\nE1E1F667CE 27332DD16B       7521 WARD       SALESMAN        7698 22-FEB-81       1250        500         30\n\n3 rows selected.\n<\/pre><\/div>\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\/oracle-20c-sql-macros\/\" target=\"_blank\">Oracle 20c: SQL Macros<\/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\/sql-macros-part-2-parameters-and-parsing\/\" target=\"_blank\">SQL Macros Part 2 &#8211; Passing of Parameters and Parsing<\/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\/parameterized-views-in-oracle-no-problem-with-sql-macros\/\" target=\"_blank\">Parameterized Views in Oracle? No problem! With SQL macros!<\/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\/temporal-joins-with-sql-macros-in-oracle-20c\/\" target=\"_blank\">Temporal Joins with SQL Macros in Oracle 20c<\/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\/sql-macros-in-oracle-19c\/\" target=\"_blank\">SQL macros in Oracle 19c<\/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\/yet-another-print_table-as-a-sql-macro\/\" target=\"_blank\">Yet another PRINT_TABLE &#8211; as a SQL Macro!<\/a><\/div><\/li><\/ul><\/div>","protected":false},"excerpt":{"rendered":"<p>In the next post about SQL macros in Oracle 20c we look at how they could be useful for building hash keys. If you are familiar with Data Warehousing and Data Vault modelling approach, you will probably know why it can be a good idea to build hash keys or hash diffs. Anyway, we will [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1208,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[99,1,3,100],"tags":[105,104,103],"class_list":["post-1205","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-20c","category-general","category-sql","category-sql-macros","tag-data-vault","tag-hash-key","tag-sql-macros"],"_links":{"self":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1205","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=1205"}],"version-history":[{"count":36,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1205\/revisions"}],"predecessor-version":[{"id":1321,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/posts\/1205\/revisions\/1321"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media\/1208"}],"wp:attachment":[{"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/media?parent=1205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/categories?post=1205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.sqlora.com\/en\/wp-json\/wp\/v2\/tags?post=1205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}