<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[TalkApex]]></title><description><![CDATA[TalkApex]]></description><link>https://talkapex.com</link><generator>RSS for Node</generator><lastBuildDate>Thu, 14 May 2026 10:52:09 GMT</lastBuildDate><atom:link href="https://talkapex.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[SQLcl host Command]]></title><description><![CDATA[Oracle SQLcl is the command line tool to connect to an Oracle database. One neat feature it has is the ability to call host commands (i.e call terminal commands directly from SQLcl). You can use any of the following to trigger a host command:
host
ho...]]></description><link>https://talkapex.com/sqlcl-host-command</link><guid isPermaLink="true">https://talkapex.com/sqlcl-host-command</guid><category><![CDATA[sqlcl]]></category><category><![CDATA[SQL]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sat, 05 Jul 2025 13:13:37 GMT</pubDate><content:encoded><![CDATA[<p>Oracle <a target="_blank" href="https://www.oracle.com/ca-en/database/sqldeveloper/technologies/sqlcl/">SQLcl</a> is the command line tool to connect to an Oracle database. One neat feature it has is the ability to call host commands (i.e call terminal commands directly from SQLcl). You can use any of the following to trigger a host command:</p>
<pre><code class="lang-sql">host
ho
!
</code></pre>
<p>Examples:</p>
<pre><code class="lang-sql">sql /nolog

<span class="hljs-comment">-- Full command name</span>
SQL&gt; host ls
dist        README.md    src

<span class="hljs-comment">-- Short name</span>
SQL&gt; ho ls
dist        README.md    src

<span class="hljs-comment">-- Really short name: (optional space between ! and the command you want to run)</span>
SQL&gt; !ls
dist        README.md    src
</code></pre>
<p>Personally I like using the shortcut <code>!</code> reference as it’s very quick to write when doing a lot of scripting.</p>
<p>The <code>host</code> command can be really useful is when working with the new SQLcl CI/CD tool <a target="_blank" href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.1/sqcug/database-application-ci-cd.html">Projects</a>. You can quickly see the status of the git repo using <code>!git status</code> rather than having to exit SQLcl, run the command, then go back into SQLcl.</p>
]]></content:encoded></item><item><title><![CDATA[Tips for APEX Application Constants]]></title><description><![CDATA[It's very common for APEX application to have application level items (usually these are named APP_... or G_... and are defined in Shared Components > Application Items). For values that are unique to a given user/session application items are a logi...]]></description><link>https://talkapex.com/tips-for-apex-application-constants</link><guid isPermaLink="true">https://talkapex.com/tips-for-apex-application-constants</guid><category><![CDATA[orclapex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Mon, 22 Jan 2024 12:00:21 GMT</pubDate><content:encoded><![CDATA[<p>It's very common for APEX application to have application level items (<em>usually these are named</em> <code>APP_...</code> <em>or</em> <code>G_...</code> <em>and are defined in Shared Components &gt; Application Items).</em> For values that are unique to a given user/session application items are a logical place to store them (ex: <code>APP_USER_ID</code>). When the item contains a value a new row is inserted into the <code>apex_application_items</code> table (<em>this is really a view but there's an underlying table that stores the value</em>).</p>
<p>If an application item is constant for the entire application (regardless of user/session) then it may not make sense for a new row to be created to store a session state value. For example, suppose you had the following setup:</p>
<ul>
<li><p>Application item called <code>APP_NAME</code></p>
</li>
<li><p>Set for each session based on a value in a lookup table</p>
</li>
<li><p>In dev you set to <code>Demo Dev</code>, test: <code>Demo Test</code>, and production: <code>Demo</code></p>
</li>
</ul>
<p>This means that the following would occur for every session:</p>
<ul>
<li><p>Computation or process <code>on new instance</code> to load value from lookup table</p>
</li>
<li><p>A new row is created in <code>apex_application_items</code> for every session</p>
</li>
</ul>
<p>The above steps would happen <strong>for every session</strong> despite the fact that the value <strong>does not change</strong>. In smaller systems this may not matter however on higher volume systems or environments with lots of applications this may add additional overhead.</p>
<h1 id="heading-solution">Solution</h1>
<p>To avoid the additional overhead you can use a <a target="_blank" href="https://docs.oracle.com/en/database/oracle/apex/23.2/htmdb/creating-text-messages.html#GUID-1007B380-42DB-4D0E-857D-A112EA7BCDAF">Text Message</a>. Text Messages are primarily intended for translation purposes however they also allow developers to have configurable application constants. Text messages are different than Application Settings in the sense that they can be referenced in APEX as a substitution string.</p>
<p>The following example shows how to use text messages for this purpose.</p>
<p>Go to Shared Components &gt; Text Messages</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705241654030/7325d7bc-bbb5-460b-8b1a-349a2208466a.png" alt class="image--center mx-auto" /></p>
<p>Click <code>Create Text Message</code> and add the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705241760170/8919c126-9005-405f-bbc3-43e99a5656cb.png" alt class="image--center mx-auto" /></p>
<p>On the login page (or this can be anywhere in the application where you want a dynamic application name) change the region title to: <code>&amp;APP_TEXT$APP_NAME.</code> <em>More information about referencing text messages as substitution strings can be found in</em> <a target="_blank" href="https://docs.oracle.com/en/database/oracle/apex/23.2/htmdb/using-substitution-strings.html#GUID-2FDF06A4-B083-49F8-9061-AE1F5629C659"><em>docs</em></a><em>.</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705241973568/2f553cf1-f9d9-4d81-bf02-927209dd9e07.png" alt class="image--center mx-auto" /></p>
<p>When the page is run it shows <code>Demo Dev</code> as expected:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705242104290/73e75642-360c-4b36-b04d-8e667c8fb320.png" alt class="image--center mx-auto" /></p>
<p>To change this in other environments it's unrealistic to set the value via the APEX application builder as most developers won't have access to Test and Prod instances. Instead it can be easily set through the API <a target="_blank" href="https://docs.oracle.com/en/database/oracle/apex/23.2/aeapi/UPDATE_MESSAGE-Procedure.html#GUID-47C6C7BC-165A-4445-A787-D062CE94D2A0"><code>apex_lang.update_message</code></a> As part of your release process after the APEX application has been update the following code should be run to set the value:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">declare</span>
    c_apex_app_id apex_application_translations.application_id%<span class="hljs-keyword">type</span> := <span class="hljs-number">200</span>;
    l_translation_entry_id apex_application_translations.translation_entry_id%type;
<span class="hljs-keyword">begin</span>

    <span class="hljs-keyword">select</span> 
        aat.translation_entry_id
    <span class="hljs-keyword">into</span> 
        l_translation_entry_id
    <span class="hljs-keyword">from</span> apex_application_translations aat
    <span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
        <span class="hljs-keyword">and</span> aat.application_id = c_apex_app_id
        <span class="hljs-keyword">and</span> aat.translatable_message = <span class="hljs-string">'APP_NAME'</span>
    ;

    <span class="hljs-comment">-- Detect if in APEX already</span>
    <span class="hljs-comment">-- Usually necessary as this should be run as a post-install script in SQLcl</span>
    <span class="hljs-comment">-- </span>
    <span class="hljs-comment">-- If you're not in an APEX application you'll get following error:</span>
    <span class="hljs-comment">-- ORA-20001: Package variable g_security_group_id must be set.</span>
    <span class="hljs-comment">-- </span>
    <span class="hljs-comment">-- May get ORA-20987: APEX - An API call has been prohibited.</span>
    <span class="hljs-comment">-- If so go to Shared Components &gt;  Security Attributes</span>
    <span class="hljs-comment">-- Check box: Runtime API Usage &gt; Modify This Application</span>
    <span class="hljs-comment">-- </span>
    if apex_application.g_flow_id is null then 
        apex_session.create_session (
            p_app_id =&gt; c_apex_app_id,
            p_page_id =&gt; 1,
            p_username =&gt; 'dummy'
        );
    <span class="hljs-keyword">end</span> <span class="hljs-keyword">if</span>;

    <span class="hljs-comment">-- Set text message:</span>
    apex_lang.update_message (
        p_id =&gt; l_translation_entry_id,
        <span class="hljs-comment">-- <span class="hljs-doctag">Note:</span> This value should come from a lookup table</span>
        p_message_text =&gt; 'Dev Test'
    );

    <span class="hljs-comment">-- Commit is required for changes to take affect</span>
    <span class="hljs-keyword">commit</span>;
<span class="hljs-keyword">end</span>;
/
</code></pre>
<p>After running this script the login page will show the update application name:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705243373674/2586a301-95a8-45e0-95ba-e829b0e182bc.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-closing-remarks">Closing Remarks</h1>
<p>I've used this technique in a very high-use application that can have between 300,000 ~ 500,000 sessions a day. The application has about 10 application constants. If Application Items were used for these constants it would result in <code>10 x 500,000</code> (5 Million) unnecessary extra writes occurring every day to store values that don't change.</p>
<p>I also found that there were some places "regular" substitution strings did not work (using an Application Item or Page Item) but worked for Text Messages. These were extreme edge cases.</p>
<p>When this technique is used you need to ensure that you have a good release process. I.e. right after the APEX application is installed/updated in each environments the text messages need to be updated as they will contain the "original" text message from the application export.</p>
]]></content:encoded></item><item><title><![CDATA[SQL Developer for VS Code]]></title><description><![CDATA[Last week SQL Developer for VS Code was released (direct link to VS Code extension here). If you're not using VS Code (VSC) I highly suggest you download and install as it's the de facto editor for developers (regardless of programming language).
I'v...]]></description><link>https://talkapex.com/sql-developer-for-vs-code</link><guid isPermaLink="true">https://talkapex.com/sql-developer-for-vs-code</guid><category><![CDATA[Visual Studio Code]]></category><category><![CDATA[vscode extensions]]></category><category><![CDATA[sqldev]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Mon, 22 Jan 2024 11:30:29 GMT</pubDate><content:encoded><![CDATA[<p>Last week <a target="_blank" href="https://www.oracle.com/database/sqldeveloper/vscode/">SQL Developer for VS Code</a> was released (direct link to VS Code extension <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=Oracle.sql-developer">here</a>). If you're not using <a target="_blank" href="https://code.visualstudio.com/">VS Code</a> (VSC) I highly suggest you download and install as it's the de facto editor for developers (regardless of programming language).</p>
<p>I've had the fortunate opportunity of beta testing the SQL Dev extension for VSC for a while and as such have a few helpful hints to start using it. <em>Before continuing this article please read</em> <a target="_blank" href="https://twitter.com/thatjeffsmith"><em>Jeff Smith</em></a><em>'s</em> <a target="_blank" href="https://www.thatjeffsmith.com/archive/2024/01/announcing-the-oracle-sql-developer-extension-for-vs-code/"><em>VSC announcement article</em></a> <em>as it covers a lot of the key features.</em></p>
<h1 id="heading-keyboard-shortcuts">Keyboard Shortcuts</h1>
<p>Traditional <a target="_blank" href="https://www.oracle.com/database/sqldeveloper/">SQL Developer</a> users are used to the standard keyboard shortcuts (ex <code>F11</code> for <code>commit</code>). You can view the SQL Developer for VSC shortcuts by going to <code>Code &gt; Settings &gt; Keyboard Shortcuts</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705757784265/dabcdafb-f284-4494-b132-4d2867b8f30e.png" alt class="image--center mx-auto" /></p>
<p>The new tab will show all the shortcuts available in VSC. Filter down to <code>oracle sql developer</code> You'll see the list of default shortcuts that you can modify. There's also a lot of commands that don't have shortcuts associated to them which you can configure as needed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705758403489/4b733dfb-d954-4e2c-8979-f0f060a1477a.png" alt class="image--center mx-auto" /></p>
<p>If you can't remember all the keyboard shortcuts you can easily search on the fly using VSC Command Palette and type in the command you're looking for.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705758659071/b75a85e0-d70d-4265-8792-07936692232d.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-extension-settings">Extension Settings</h1>
<p>It's always good to look at all the options for each VSC extension that is installed. To view the SQL Dev VSC extension settings go to Extensions then search for <code>@installed sql</code> &gt; click on the gears and select <code>Extension Settings</code>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705841624915/b140f484-5eca-431f-9449-0aaaaf5fc47a.png" alt class="image--center mx-auto" /></p>
<p>Some of key settings to pay attention to:</p>
<ul>
<li><p><code>Database › Nls: Sort</code> I like using <code>BINARY_CI</code> more information about this can be found <a target="_blank" href="https://talkapex.com/case-insensitive-sorting-in-apex">here</a>.</p>
</li>
<li><p><code>Connections › Startup Script</code>: Can reference your <code>login.sql</code> More about login.sql <a target="_blank" href="https://talkapex.com/sqlcl-and-loginsql">here</a>.</p>
</li>
<li><p><code>Database &gt; Date / Timestamp Formats</code>: I like to include minutes and seconds as well as displaying the month as a string (use <code>Mon</code> ) as shown below</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705841753756/656ddeab-9a9b-499a-94ab-a5e8d0b1bbf9.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-substitution-strings">Substitution Strings</h1>
<p>If you're running an anonymous PL/SQL block or a query with an ampersand (<code>&amp;</code> ) using the <code>run</code> command (or <code>ctrl+enter</code>) it will prompt you for a the substitution string:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705845285206/bfff48d1-6597-4ecd-ba52-b93233df7835.png" alt class="image--center mx-auto" /></p>
<p>To get around this just use <code>Compile</code> command (open the command palette and search for it). This will run just the block / query that the cursor is in (not the entire sheet).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705845332323/15d915df-8ca3-4176-bb14-49b5024e06c2.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-query-results-column-width">Query Results: Column Width</h1>
<p>If your query has a lot of columns you'll need to select a formatting option. Right click on the row header and select <code>Auto-fit All Columns</code> and select the best option.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1705929870896/df009cba-1b53-423c-ba1d-ff094467e8da.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-feedback">Feedback</h1>
<p>Given that this is the initial release of SQL Dev for VSC there's a lot of opportunity for community engagement You can post your feedback, suggestions, bugs, etc on the <a target="_blank" href="https://forums.oracle.com/ords/apexds/domain/dev-community/category/sqldev-for-vscode">SQL Developer for VS Code Forums</a>.</p>
]]></content:encoded></item><item><title><![CDATA[How to Generate a sitemap.xml in SQL]]></title><description><![CDATA[I recently had to generate a sitemap.xml for an public facing APEX application. The official sitemap protocol has the following example:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
   <url>
    ...]]></description><link>https://talkapex.com/how-to-generate-a-sitemapxml-in-sql</link><guid isPermaLink="true">https://talkapex.com/how-to-generate-a-sitemapxml-in-sql</guid><category><![CDATA[SQL]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Wed, 17 Jan 2024 12:00:25 GMT</pubDate><content:encoded><![CDATA[<p>I recently had to generate a <code>sitemap.xml</code> for an public facing APEX application. The official <a target="_blank" href="https://www.sitemaps.org/protocol.html">sitemap protocol</a> has the following example:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">urlset</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>http://www.example.com/<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2005-01-01<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>monthly<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.8<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span>
   <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">urlset</span>&gt;</span>
</code></pre>
<p>I've seen various implementations of this in SQL and PL/SQL that require a lot of code. Thankfully the entire XML file can be generated with one SQL statement. Using the sample <code>emp</code> table the following query shows how it can be generated: <em>note the URLs won't make sense given the</em> <code>emp</code> <em>table doesn't map to any URLs:</em></p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>
    <span class="hljs-keyword">xmlserialize</span>(
        <span class="hljs-keyword">content</span>
        <span class="hljs-keyword">xmlroot</span>(
            <span class="hljs-keyword">xmlelement</span>(
                <span class="hljs-string">"urlset"</span>,
                <span class="hljs-keyword">xmlattributes</span>(<span class="hljs-string">'http://www.sitemaps.org/schemas/sitemap/0.9'</span> <span class="hljs-keyword">as</span> <span class="hljs-string">"xmlns"</span>),
                <span class="hljs-keyword">xmlagg</span>(
                    <span class="hljs-keyword">xmlelement</span>(
                        <span class="hljs-string">"url"</span>,
                        <span class="hljs-keyword">xmlelement</span>(<span class="hljs-string">"loc"</span>, <span class="hljs-string">'http://my-url.com/'</span> || e.ename),
                        <span class="hljs-comment">-- See https://www.w3.org/TR/NOTE-datetime for format</span>
                        <span class="hljs-comment">-- If e.hiredate was a timestamp can use:</span>
                        <span class="hljs-comment">-- xmlelement("lastmod", to_char(e.hiredate at time zone 'UTC', 'YYYY-MM-DD"T"HH24:MI:SSTZH:TZM')),</span>
                        <span class="hljs-keyword">xmlelement</span>(<span class="hljs-string">"lastmod"</span>, to_char(e.hiredate, <span class="hljs-string">'YYYY-MM-DD'</span>)),
                        <span class="hljs-comment">-- See https://www.sitemaps.org/protocol.html#xmlTagDefinitions for changefreq and other options</span>
                        <span class="hljs-keyword">xmlelement</span>(<span class="hljs-string">"changefreq"</span>, <span class="hljs-string">'daily'</span>)
                    )
                ) <span class="hljs-comment">-- xmlagg</span>
            ) <span class="hljs-comment">-- urlset</span>
        ,
        <span class="hljs-comment">-- Verion is only meant for a number but using a trick to also add the "encoding" attribute</span>
        <span class="hljs-comment">-- See https://forums.oracle.com/ords/apexds/post/how-to-add-version-and-encoding-to-xml-2163#comment_323462166473746547632690547881602100073</span>
        <span class="hljs-keyword">version</span> <span class="hljs-string">'1.0" encoding="UTF-8'</span>
        )
        <span class="hljs-keyword">as</span> <span class="hljs-keyword">clob</span>
    ) sitemap_xml
<span class="hljs-keyword">from</span> emp e
;
</code></pre>
<p>Result:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">urlset</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>http://my-url.com/KING<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>1981-11-17<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
<span class="hljs-comment">&lt;!-- ... (all the other emps) --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>http://my-url.com/MILLER<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>1982-01-23<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">urlset</span>&gt;</span>
</code></pre>
<p>For APEX developers see APEX idea <a target="_blank" href="https://apex.oracle.com/ideas/FR-3105"><code>FR-3105</code></a> has some discussion on how to include a sitemap.xml file in an application. Specifically look at the comment by <a target="_blank" href="https://twitter.com/vuvarovs?lang=en"><code>vladislav.uvarov</code></a></p>
]]></content:encoded></item><item><title><![CDATA[What Happens to APEX Page Items that are Never Rendered?]]></title><description><![CDATA[Every APEX page item has a Server Side condition which controls whether the item is rendered on the page. They're various server side condition options and for the purpose of this article we'll just set it to Never (can do things like queries that re...]]></description><link>https://talkapex.com/what-happens-to-apex-page-items-that-are-never-rendered</link><guid isPermaLink="true">https://talkapex.com/what-happens-to-apex-page-items-that-are-never-rendered</guid><category><![CDATA[orclapex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Mon, 15 Jan 2024 12:00:20 GMT</pubDate><content:encoded><![CDATA[<p>Every APEX page item has a Server Side condition which controls whether the item is rendered on the page. They're various server side condition options and for the purpose of this article we'll just set it to <code>Never</code> (can do things like queries that return a row, PL/SQL expressions, etc).</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704551463125/3e4391e3-bd00-43d9-9140-5c824d065d8b.png" alt class="image--center mx-auto" /></p>
<p>One misconception is that all references to this page item will be null / won't do anything. This is not correct as page items that aren't marked to be rendered can still have values. In the example below I have two page items:</p>
<ul>
<li><p><code>P6_ALWAYS</code> has no conditions and will always be shown on the page</p>
</li>
<li><p><code>P6_NEVER</code> its condition will be set to <code>Never</code> and not render on the page</p>
</li>
<li><p>Both of the page items have a item source as a <code>Function Body</code> with the following code:</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-comment">-- For always for P6_ALWAYS is "always" and P6_NEVER is "never"</span>
logger.log('Source for always/never');
return 'always/never';
</code></pre>
<p>When the page is run it looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704553679665/a2715f39-745e-4980-aaa3-f893e2c6ac1e.png" alt class="image--center mx-auto" /></p>
<p>Things to note:</p>
<ul>
<li><p><code>P6_NEVER</code> was not rendered</p>
</li>
<li><p>No values in session state (i.e. <code>P6_ALWAYS</code> item's value will only be saved in session state when the page is saved)</p>
</li>
<li><p>Looking at the logs only the <code>P6_ALWAYS</code> source code section was run</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704553961189/004e2fe2-c66b-4fe3-b9f3-2017090b0b5d.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
<p>A new computation is now added to the page to set a value for <code>P6_NEVER</code> as shown below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704554124545/d441d492-a3b5-4645-b4ee-1acb0884f5e9.png" alt class="image--center mx-auto" /></p>
<p>When the page is run it will look the same as before except that <code>P6_NEVER</code> has a value in session state. This means that when referencing <code>P6_NEVER</code> in a block of SQL or PL/SQL it will have a value, however if trying to reference it on the page (via JavaScript) it does not exists nor has a value.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704554228795/7ab6baa1-e4a8-4aea-8231-2d8d3e1c395a.png" alt class="image--center mx-auto" /></p>
<p>In the past when I've explained this to people (both new and experienced APEX developers) and they think it's a bug. It isn't for the following reasons:</p>
<ul>
<li><p>APEX has an order of operations. Essentially it runs "top down" for everything listed on the left side in Page Designer</p>
</li>
<li><p>Since Pre-Rendering is done <strong>before</strong> page items are "processed" APEX can't know if a page item should not be rendered</p>
<ul>
<li>This can also be true of a page item's condition references another page ex. For example if a page item's condition is <code>P6_ADMIN_YN = 'Y'</code> then APEX will only know if this is true or false once it starts to process the page item.</li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Case Insensitive Searching in APEX]]></title><description><![CDATA[By default Oracle (and inherently APEX) is case sensitive, i.e. MARTIN is not the same string as martin. This is apparent in both sorting (order by) and searching (where clauses).
In a previous article I covered how to do case insensitive sorting in ...]]></description><link>https://talkapex.com/case-insensitive-searching-in-apex</link><guid isPermaLink="true">https://talkapex.com/case-insensitive-searching-in-apex</guid><category><![CDATA[orclapex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Wed, 10 Jan 2024 12:00:23 GMT</pubDate><content:encoded><![CDATA[<p>By default Oracle (and inherently APEX) is case sensitive, i.e. <code>MARTIN</code> is not the same string as <code>martin</code>. This is apparent in both sorting (<code>order by</code>) and searching (<code>where</code> clauses).</p>
<p>In a previous article I covered how to do <a target="_blank" href="https://talkapex.com/case-insensitive-sorting-in-apex">case insensitive sorting in APEX</a>. <em>Before continuing please read the article as this post builds upon it.</em> This post shows how to do case insensitive searching by default in APEX (i.e. every time you have a <code>where</code> clause <code>MARTIN</code> is the same as <code>martin</code>).</p>
<p>Suppose an report's query is the following. No row's will be returned because all the jobs are uppercase. In this example the valid job name is <code>CLERK</code> not <code>cLERk</code>.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>  *
<span class="hljs-keyword">from</span> emp
<span class="hljs-comment">-- Note this could also be a page item like :P1_JOB</span>
<span class="hljs-comment">-- Where user types in job name</span>
<span class="hljs-keyword">where</span> job = <span class="hljs-string">'cLERk'</span>
</code></pre>
<p>To have all queries do case insensitive searching in APEX go to <code>Shared Components &gt; Globalization Attributes</code> and change the following settings:</p>
<ul>
<li><p>Character Value Comparison: <code>BINARY_CI</code></p>
</li>
<li><p>Character Value Comparison Behavior: <code>Linguistic</code></p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704197808376/b3a1fc99-61b7-4666-89fa-7634abfa79e9.png" alt class="image--center mx-auto" /></p>
<p>If you run the same report with the above query it will now return rows for all the <code>CLERK</code> jobs:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1704197872443/dcf2de80-9d8e-4659-9b32-14c90f40c551.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-reference-material">Reference Material</h2>
<p>To learn more about each of the key APEX settings please click on the help icon as it covers what each option means. The settings reference NLS parameters <code>NLS_SORT</code> and <code>NLS_COMP</code>. You can find the default settings for each parameter using the query below. More information about these parameters can be found in the Oracle documentation for <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/21/nlspg/setting-up-globalization-support-environment.html#GUID-60ED0CB2-50AB-4BF4-8FAF-04B79A400329">NLS parameters</a>.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> v$nls_parameters
<span class="hljs-keyword">where</span> parameter <span class="hljs-keyword">in</span> (<span class="hljs-string">'NLS_COMP'</span>, <span class="hljs-string">'NLS_SORT'</span>)
;

PARAMETER VALUE  CON_ID 
<span class="hljs-comment">--------- ------ ------ </span>
NLS_SORT  BINARY      0 
NLS_COMP  BINARY      0
</code></pre>
<p>If you don't want to enable case insensitive searching for your entire application but do want it on a specific query/report you can do so using <code>collate binary_ci</code>. <a target="_blank" href="https://twitter.com/ChrisRSaxon">Chris Saxon</a> has a <a target="_blank" href="https://blogs.oracle.com/sql/post/how-to-do-case-insensitive-and-accent-insensitive-search-in-oracle-database">great article</a> which goes into detail about the <code>collate</code> command.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>  *
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">where</span> job = <span class="hljs-string">'cLERk'</span> <span class="hljs-keyword">collate</span> binary_ci
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Data Integrity for Fast Refresh Materialized Views with Aggregates and Updates/Deletes]]></title><description><![CDATA[Update: thanks to Connor McDonald for pointing out how to fix this issue (see "The Solution" and subsequent explanation below). Connor wrote a followup post based on this article discussing count(*) vs count(1) differences.
The Problem
I recently had...]]></description><link>https://talkapex.com/data-integrity-for-fast-refresh-materialized-views-with-aggregates-and-updatesdeletes</link><guid isPermaLink="true">https://talkapex.com/data-integrity-for-fast-refresh-materialized-views-with-aggregates-and-updatesdeletes</guid><category><![CDATA[Oracle]]></category><category><![CDATA[oracle-sql]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Mon, 08 Jan 2024 12:00:16 GMT</pubDate><content:encoded><![CDATA[<p><strong><em>Update:</em></strong> <em>thanks to</em> <a target="_blank" href="https://twitter.com/connor_mc_d"><em>Connor McDonald</em></a> <em>for pointing out how to fix this issue (see "The Solution" and subsequent explanation below). Connor wrote a</em> <a target="_blank" href="https://connor-mcdonald.com/2024/01/12/count-versus-count1-the-finale/"><em>followup post</em></a> <em>based on this article discussing</em> <code>count(*)</code> <em>vs</em> <code>count(1)</code> <em>differences.</em></p>
<h1 id="heading-the-problem">The Problem</h1>
<p>I recently had a data integrity issue with an Oracle materialized view (MV) in where no errors were raised but the data was not correct in the MV. The following situation is what triggered caused the issue:</p>
<ul>
<li><p>MV with fast refresh</p>
</li>
<li><p>Underlying query contains an aggregate function (<code>min, max, sum, count, etc</code>)</p>
</li>
<li><p>An <code>update</code> or <code>delete</code> occurs on base table</p>
</li>
</ul>
<p>The following code snippet highlights the issue:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- reset</span>
<span class="hljs-comment">-- drop table demo_emps;</span>
<span class="hljs-comment">-- drop materialized view demo_emps_mv;</span>

<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> demo_emps (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">number</span> primary <span class="hljs-keyword">key</span>, 
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">varchar2</span>(<span class="hljs-number">255</span>),
    sal <span class="hljs-built_in">number</span>
);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> demo_emps <span class="hljs-keyword">values</span>(<span class="hljs-number">1</span>, <span class="hljs-string">'martin'</span>, <span class="hljs-number">100</span>);

<span class="hljs-comment">-- Required for fast refresh</span>
<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> <span class="hljs-keyword">log</span> <span class="hljs-keyword">on</span> demo_emps
<span class="hljs-keyword">with</span> <span class="hljs-keyword">rowid</span>, <span class="hljs-keyword">sequence</span>(<span class="hljs-keyword">id</span>, sal)
<span class="hljs-keyword">including</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">values</span>
;

<span class="hljs-comment">-- Will return the max sal</span>
<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv
<span class="hljs-keyword">refresh</span> <span class="hljs-keyword">fast</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">commit</span>
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span> <span class="hljs-keyword">max</span>(sal) max_sal
<span class="hljs-keyword">from</span> demo_emps
;

<span class="hljs-comment">-- Returns 100 (correct)</span>
<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> demo_emps_mv
;

MAX_SAL 
<span class="hljs-comment">------- </span>
    100 

<span class="hljs-comment">-- Insert a new row</span>
<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> demo_emps <span class="hljs-keyword">values</span>(<span class="hljs-number">2</span>, <span class="hljs-string">'sally'</span>, <span class="hljs-number">200</span>);

<span class="hljs-comment">-- Commit required for MV Refresh:</span>
<span class="hljs-keyword">commit</span>;

<span class="hljs-comment">-- Returns 200 (correct)</span>
<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> demo_emps_mv
;

MAX_SAL 
<span class="hljs-comment">------- </span>
    200 

<span class="hljs-comment">-- View MV Status</span>
<span class="hljs-keyword">select</span> staleness
<span class="hljs-keyword">from</span> user_mviews
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
    <span class="hljs-keyword">and</span> mview_name = <span class="hljs-string">'DEMO_EMPS_MV'</span>
;

STALENESS 
<span class="hljs-comment">--------- </span>
FRESH     

<span class="hljs-comment">-- Update rows (add 1 to each salary)</span>
<span class="hljs-comment">-- Same issue will also occur if a row is deleted</span>
<span class="hljs-keyword">update</span> demo_emps
<span class="hljs-keyword">set</span> sal = sal + <span class="hljs-number">1</span>
;

<span class="hljs-keyword">commit</span>;

<span class="hljs-comment">-- Should show 201 but shows old 200 (incorrect)</span>
<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> demo_emps_mv
;

MAX_SAL 
<span class="hljs-comment">------- </span>
    200 

<span class="hljs-comment">-- View MV Status</span>
<span class="hljs-comment">-- <span class="hljs-doctag">Note:</span> Staleness now shows that it is UNUSABLE</span>
<span class="hljs-keyword">select</span> staleness
<span class="hljs-keyword">from</span> user_mviews
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
    <span class="hljs-keyword">and</span> mview_name = <span class="hljs-string">'DEMO_EMPS_MV'</span>
;

STALENESS 
<span class="hljs-comment">--------- </span>
UNUSABLE
</code></pre>
<p>Despite no errors raised the data is clearly not correct. Some thoughts:</p>
<ul>
<li><p>I think the only way to resolve this issue is to create the MV as <code>refresh on demand</code> and do manual refreshes</p>
</li>
<li><p>It is can be very hard to catch this bug and I recommend monitoring your MVs to ensure the data is correct</p>
<ul>
<li><em>Update: You can catch this issue but need to analyze the MV (see below)</em></li>
</ul>
</li>
</ul>
<p>This issue is documented in <a target="_blank" href="https://support.oracle.com/knowledge/Oracle%20Database%20Products/726333_1.html">MOS 726333.1</a> <em>Why Does Mview Staleness in Dba_Mview Become Unusable?</em></p>
<blockquote>
<p>For a MV with MIN/MAX or COUNT we only support fast refresh if DML is only insert. On delete/update we won't be able to do fast refresh. For such on-commit mview refresh case we mark it as UNUSABLE.</p>
</blockquote>
<h1 id="heading-the-solution">The Solution</h1>
<p><em>This solution is an excerpt from</em> <a target="_blank" href="https://twitter.com/connor_mc_d"><em>Connor McDonald</em></a> <em>sent me.</em></p>
<p>This can be resolved by modifying the MV to also include <code>count(*)</code> <em>(specifically</em> <code>count(*)</code> <em>and not anything else).</em> Using the above examples:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Drop the MV objects </span>
<span class="hljs-keyword">drop</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> <span class="hljs-keyword">log</span> <span class="hljs-keyword">on</span> demo_emps;
<span class="hljs-keyword">drop</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv;

<span class="hljs-comment">-- Rebuilding MV log to include name (not part of fix but needed for grouping)</span>
<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> <span class="hljs-keyword">log</span> <span class="hljs-keyword">on</span> demo_emps
<span class="hljs-keyword">with</span> <span class="hljs-keyword">rowid</span>, <span class="hljs-keyword">sequence</span>(<span class="hljs-keyword">id</span>, sal, <span class="hljs-keyword">name</span>)
<span class="hljs-keyword">including</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">values</span>
;

<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv
<span class="hljs-keyword">refresh</span> <span class="hljs-keyword">fast</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">commit</span>
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span>
    <span class="hljs-keyword">name</span>,
    <span class="hljs-keyword">max</span>(sal) max_sal,
    <span class="hljs-comment">-- Key to solving the issue</span>
    <span class="hljs-keyword">count</span>(*) cnt
<span class="hljs-keyword">from</span> demo_emps
<span class="hljs-comment">-- Needs to have a "group by" clause (most MVs will)</span>
<span class="hljs-keyword">group</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">name</span>
;

<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> demo_emps_mv
;

NAME   MAX_SAL CNT 
<span class="hljs-comment">------ ------- --- </span>
martin     101   1 
sally      201   1 

<span class="hljs-comment">-- Before this update triggered the issue</span>
<span class="hljs-keyword">update</span> demo_emps
<span class="hljs-keyword">set</span> sal = sal + <span class="hljs-number">5</span>
;

<span class="hljs-keyword">commit</span>;

<span class="hljs-comment">-- Now values in MV are correct</span>
<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> demo_emps_mv
;

NAME   MAX_SAL CNT 
<span class="hljs-comment">------ ------- --- </span>
martin     106   1 
sally      206   1
</code></pre>
<h2 id="heading-explanation">Explanation</h2>
<p>Going back to the first block of code if I rebuild the MV and run <code>dbms_mview.explain_mview</code> to analyze the MV. Some key notes:</p>
<ul>
<li><p><code>5006</code>: MV supports insert</p>
</li>
<li><p><code>6007</code> MV does not other DML</p>
</li>
<li><p><code>6008</code> Reason why other DML is not supported / solution</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">drop</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv;

<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv
<span class="hljs-keyword">refresh</span> <span class="hljs-keyword">fast</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">commit</span>
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span> <span class="hljs-keyword">max</span>(sal) max_sal
<span class="hljs-keyword">from</span> demo_emps
;

<span class="hljs-comment">-- Used to store results of dbms_view.explain_mview</span>
<span class="hljs-comment">-- See https://docs.oracle.com/en/database/oracle/oracle-database/19/arpls/DBMS_MVIEW.html#GUID-DB06F5D8-C883-4443-9123-E1519B44AFA6</span>
<span class="hljs-keyword">truncate</span> <span class="hljs-keyword">table</span> mv_capabilities_table;

exec dbms_mview.explain_mview('DEMO_EMPS_MV');

<span class="hljs-keyword">select</span> 
    seq,
    capability_name,
    possible,
    related_text,
    msgtxt
<span class="hljs-keyword">from</span> mv_capabilities_table
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> seq
;


  SEQ CAPABILITY_NAME               POSSIBLE RELATED_TEXT MSGTXT                                                                             
<span class="hljs-comment">----- ----------------------------- -------- ------------ ---------------------------------------------------------------------------------- </span>
    1 PCT                           N                                                                                                        
 1002 REFRESH_COMPLETE              Y                                                                                                        
 2003 REFRESH_FAST                  Y                                                                                                        
 3004 REWRITE                       N                                                                                                        
 4005 PCT_TABLE                     N        DEMO_EMPS    relation is not a partitioned table                                                
 <span class="hljs-comment">-- INSERTS ARE OK</span>
 5006 REFRESH_FAST_AFTER_INSERT     Y                                                                                                        
 <span class="hljs-comment">-- OTHER DML NOT OK</span>
 6007 REFRESH_FAST_AFTER_ONETAB_DML N                     mv uses the MIN, MAX or ANY_VALUE aggregate functions                              
 6008 REFRESH_FAST_AFTER_ONETAB_DML N                     COUNT(*) is not present in the <span class="hljs-keyword">select</span> <span class="hljs-keyword">list</span>                                         
 <span class="hljs-number">7009</span> REFRESH_FAST_AFTER_ANY_DML    N                     see the reason why REFRESH_FAST_AFTER_ONETAB_DML <span class="hljs-keyword">is</span> disabled                       
 <span class="hljs-number">8010</span> REFRESH_FAST_PCT              N                     PCT <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> possible <span class="hljs-keyword">on</span> <span class="hljs-keyword">any</span> <span class="hljs-keyword">of</span> the detail <span class="hljs-keyword">tables</span> <span class="hljs-keyword">in</span> the <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span>           
 <span class="hljs-number">9011</span> REWRITE_FULL_TEXT_MATCH       N                     <span class="hljs-keyword">query</span> rewrite <span class="hljs-keyword">is</span> disabled <span class="hljs-keyword">on</span> the <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span>                                 
<span class="hljs-number">10012</span> REWRITE_PARTIAL_TEXT_MATCH    N                     <span class="hljs-keyword">query</span> rewrite <span class="hljs-keyword">is</span> disabled <span class="hljs-keyword">on</span> the <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span>                                 
<span class="hljs-number">11013</span> REWRITE_GENERAL               N                     <span class="hljs-keyword">query</span> rewrite <span class="hljs-keyword">is</span> disabled <span class="hljs-keyword">on</span> the <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span>                                 
<span class="hljs-number">12014</span> REWRITE_PCT                   N                     <span class="hljs-keyword">general</span> rewrite <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> possible <span class="hljs-keyword">or</span> PCT <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> possible <span class="hljs-keyword">on</span> <span class="hljs-keyword">any</span> <span class="hljs-keyword">of</span> the detail <span class="hljs-keyword">tables</span> 
<span class="hljs-number">13015</span> PCT_TABLE_REWRITE             N        DEMO_EMPS    relation <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> a partitioned <span class="hljs-keyword">table</span>
</code></pre>
<p>Updating the MV to use the solution (with <code>count(*)</code>) and running the same analysis has the following results:</p>
<ul>
<li><p>Lines <code>6007</code> and <code>6008</code> aren't present anymore</p>
</li>
<li><p><code>REFRESH_FAST_AFTER_ANY_DML</code> is now marked as <code>Y</code></p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">drop</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv;

<span class="hljs-keyword">create</span> <span class="hljs-keyword">materialized</span> <span class="hljs-keyword">view</span> demo_emps_mv
<span class="hljs-keyword">refresh</span> <span class="hljs-keyword">fast</span> <span class="hljs-keyword">on</span> <span class="hljs-keyword">commit</span>
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span>
    <span class="hljs-keyword">name</span>,
    <span class="hljs-keyword">max</span>(sal) max_sal,
    <span class="hljs-keyword">count</span>(*) cnt
<span class="hljs-keyword">from</span> demo_emps
<span class="hljs-keyword">group</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">name</span>
;

<span class="hljs-keyword">truncate</span> <span class="hljs-keyword">table</span> mv_capabilities_table;

exec dbms_mview.explain_mview('DEMO_EMPS_MV');

<span class="hljs-keyword">select</span> 
    seq,
    capability_name,
    possible,
    related_text,
    msgtxt
<span class="hljs-keyword">from</span> mv_capabilities_table
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> seq
;


  SEQ CAPABILITY_NAME            POSSIBLE RELATED_TEXT MSGTXT                                                                             
<span class="hljs-comment">----- -------------------------- -------- ------------ ---------------------------------------------------------------------------------- </span>
    1 PCT                        N                                                                                                        
 1002 REFRESH_COMPLETE           Y                                                                                                        
 2003 REFRESH_FAST               Y                                                                                                        
 3004 REWRITE                    N                                                                                                        
 4005 PCT_TABLE                  N        DEMO_EMPS    relation is not a partitioned table                                                
 5006 REFRESH_FAST_AFTER_INSERT  Y                                                                                                        
 7007 REFRESH_FAST_AFTER_ANY_DML Y                                                                                                        
 8008 REFRESH_FAST_PCT           N                     PCT is not possible on any of the detail tables in the materialized view           
 9009 REWRITE_FULL_TEXT_MATCH    N                     query rewrite is disabled on the materialized view                                 
10010 REWRITE_PARTIAL_TEXT_MATCH N                     query rewrite is disabled on the materialized view                                 
11011 REWRITE_GENERAL            N                     query rewrite is disabled on the materialized view                                 
12012 REWRITE_PCT                N                     general rewrite is not possible or PCT is not possible on any of the detail tables 
13013 PCT_TABLE_REWRITE          N        DEMO_EMPS    relation is not a partitioned table
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to Get a Non-Aggregate value in an Aggregate Query]]></title><description><![CDATA[Suppose you want to find the maximum salary for each job in the emp table. This is very easy using using the max aggregate:
select job, max(sal) max_sal
from emp
group by job
order by job
;

JOB       MAX_SAL 
--------- ------- 
ANALYST      3000 
CL...]]></description><link>https://talkapex.com/how-to-get-a-non-aggregate-value-in-an-aggregate-query</link><guid isPermaLink="true">https://talkapex.com/how-to-get-a-non-aggregate-value-in-an-aggregate-query</guid><category><![CDATA[Oracle]]></category><category><![CDATA[SQL]]></category><category><![CDATA[oracle-sql]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Fri, 05 Jan 2024 12:00:22 GMT</pubDate><content:encoded><![CDATA[<p>Suppose you want to find the maximum salary for each job in the <code>emp</code> table. This is very easy using using the <code>max</code> aggregate:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> job, <span class="hljs-keyword">max</span>(sal) max_sal
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">group</span> <span class="hljs-keyword">by</span> job
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> job
;

JOB       MAX_SAL 
<span class="hljs-comment">--------- ------- </span>
ANALYST      3000 
CLERK        1300 
MANAGER      2975 
PRESIDENT    5000 
SALESMAN     1600
</code></pre>
<p>The next logical question is: <em>Who earns the max salary?</em> A very common solution to this problem is to use an analytical function:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>
    job, ename, sal
<span class="hljs-keyword">from</span> (
    <span class="hljs-keyword">select</span>
        row_number() <span class="hljs-keyword">over</span> (<span class="hljs-keyword">partition</span> <span class="hljs-keyword">by</span> job <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> sal <span class="hljs-keyword">desc</span>) rn,
        job, ename, sal
    <span class="hljs-keyword">from</span> emp
)
<span class="hljs-keyword">where</span> rn = <span class="hljs-number">1</span>
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> job
;

JOB       ENAME   SAL 
<span class="hljs-comment">--------- ------ ---- </span>
ANALYST   SCOTT  3000 
CLERK     MILLER 1300 
MANAGER   JONES  2975 
PRESIDENT KING   5000 
SALESMAN  ALLEN  1600 

<span class="hljs-comment">-- Explain plan:                                                                                     </span>
<span class="hljs-comment">------------------------------------------------------------------------------------ </span>
| Id  | Operation                   | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
<span class="hljs-comment">------------------------------------------------------------------------------------ </span>
|   0 | <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">STATEMENT</span>            |      |    <span class="hljs-number">14</span> |   <span class="hljs-number">546</span> |     <span class="hljs-number">4</span>  (<span class="hljs-number">25</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
|*  <span class="hljs-number">1</span> |  <span class="hljs-keyword">VIEW</span>                       |      |    <span class="hljs-number">14</span> |   <span class="hljs-number">546</span> |     <span class="hljs-number">4</span>  (<span class="hljs-number">25</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
|*  <span class="hljs-number">2</span> |   <span class="hljs-keyword">WINDOW</span> <span class="hljs-keyword">SORT</span> PUSHED <span class="hljs-keyword">RANK</span>   |      |    <span class="hljs-number">14</span> |   <span class="hljs-number">252</span> |     <span class="hljs-number">4</span>  (<span class="hljs-number">25</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
|   <span class="hljs-number">3</span> |    <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">ACCESS</span> <span class="hljs-keyword">STORAGE</span> <span class="hljs-keyword">FULL</span>| EMP  |    <span class="hljs-number">14</span> |   <span class="hljs-number">252</span> |     <span class="hljs-number">3</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
<span class="hljs-comment">------------------------------------------------------------------------------------ </span>

Predicate Information (<span class="hljs-keyword">identified</span> <span class="hljs-keyword">by</span> operation <span class="hljs-keyword">id</span>):                                  
<span class="hljs-comment">---------------------------------------------------                                  </span>

   <span class="hljs-number">1</span> - filter(<span class="hljs-string">"RN"</span>=<span class="hljs-number">1</span>)                                                                
   <span class="hljs-number">2</span> - filter(ROW_NUMBER() <span class="hljs-keyword">OVER</span> ( <span class="hljs-keyword">PARTITION</span> <span class="hljs-keyword">BY</span> <span class="hljs-string">"JOB"</span> <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> <span class="hljs-string">"SAL"</span>)&lt;=<span class="hljs-number">1</span>)
</code></pre>
<p>There is an alternate way to do this using the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/FIRST.html#GUID-85AB9246-0E0A-44A1-A7E6-4E57502E9238"><code>keep</code></a> clause and <code>dense_rank</code> along with the <code>first/last</code> commands:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>
    job,
    <span class="hljs-comment">-- The initial "min" aggregate could also be "max" </span>
    <span class="hljs-comment">-- The key logic is in the dense_rank first/last and ordering</span>
    <span class="hljs-keyword">min</span>(ename) <span class="hljs-keyword">keep</span> (<span class="hljs-keyword">dense_rank</span> <span class="hljs-keyword">last</span> <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> sal) ename,
    <span class="hljs-comment">-- Could also write as:</span>
    <span class="hljs-comment">-- min(ename) keep (dense_rank first order by sal desc) ename,</span>
    <span class="hljs-keyword">max</span>(sal)
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">group</span> <span class="hljs-keyword">by</span> job
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> job
;

JOB       ENAME  MAX(SAL) 
<span class="hljs-comment">--------- ------ -------- </span>
ANALYST   FORD       3000 
CLERK     MILLER     1300 
MANAGER   JONES      2975 
PRESIDENT KING       5000 
SALESMAN  ALLEN      1600 

<span class="hljs-comment">-- Explain Plan</span>
<span class="hljs-comment">----------------------------------------------------------------------------------- </span>
| Id  | Operation                  | Name | Rows  | Bytes | Cost (%CPU)| Time     | 
<span class="hljs-comment">----------------------------------------------------------------------------------- </span>
|   0 | <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">STATEMENT</span>           |      |     <span class="hljs-number">5</span> |    <span class="hljs-number">90</span> |     <span class="hljs-number">4</span>  (<span class="hljs-number">25</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
|   <span class="hljs-number">1</span> |  <span class="hljs-keyword">SORT</span> <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span>             |      |     <span class="hljs-number">5</span> |    <span class="hljs-number">90</span> |     <span class="hljs-number">4</span>  (<span class="hljs-number">25</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
|   <span class="hljs-number">2</span> |   <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">ACCESS</span> <span class="hljs-keyword">STORAGE</span> <span class="hljs-keyword">FULL</span>| EMP  |    <span class="hljs-number">14</span> |   <span class="hljs-number">252</span> |     <span class="hljs-number">3</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> | 
<span class="hljs-comment">-----------------------------------------------------------------------------------</span>
</code></pre>
<p>Using the <code>dense_rank</code> command is easier to write and it may result in more performant queries. The different explain plans shows that less bytes were required.</p>
<p><em>Note: the keen observer may note that for the</em> <code>ANALYST</code> <em>position there's two different results. That's because both they're two analysts (</em><code>FORD</code> <em>and</em> <code>SCOTT</code><em>) and they both earn the same amount. If "ties are allowed" the following query can be used:</em></p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> job, ename, sal
<span class="hljs-keyword">from</span> (
    <span class="hljs-keyword">select</span> 
        job, ename, sal,
        <span class="hljs-keyword">dense_rank</span>() <span class="hljs-keyword">over</span> (<span class="hljs-keyword">partition</span> <span class="hljs-keyword">by</span> job <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> sal <span class="hljs-keyword">desc</span>) rn
    <span class="hljs-keyword">from</span> emp
)
<span class="hljs-keyword">where</span> rn = <span class="hljs-number">1</span>
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> job
;

<span class="hljs-comment">-- Note that ANALYST has two rows given that SCOTT and FORD earn the same amount</span>
JOB       ENAME   SAL 
<span class="hljs-comment">--------- ------ ---- </span>
ANALYST   SCOTT  3000 
ANALYST   FORD   3000 
CLERK     MILLER 1300 
MANAGER   JONES  2975 
PRESIDENT KING   5000 
SALESMAN  ALLEN  1600
</code></pre>
<h2 id="heading-references">References</h2>
<ul>
<li><p>Oracle's official documentation on this feature is actually in the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/FIRST.html#GUID-85AB9246-0E0A-44A1-A7E6-4E57502E9238"><code>first</code></a> command</p>
</li>
<li><p><a target="_blank" href="https://twitter.com/rwijk">Rob van Wijk</a> has a good <a target="_blank" href="https://rwijk.blogspot.com/2012/09/keep-clause.html">article</a> about the <code>keep</code> clause</p>
<ul>
<li>Covers some of the performance gains in detail</li>
</ul>
</li>
</ul>
<p><em>Thanks to</em> <a target="_blank" href="https://twitter.com/jeffreykemp"><em>Jeff Kemp</em></a> <em>for showing me this feature!</em></p>
]]></content:encoded></item><item><title><![CDATA[Dot Notation for JSON in Oracle SQL]]></title><description><![CDATA[When dealing with Oracle SQL statements and the need to parse JSON arises, you've likely encountered the json_value function. However, Oracle offers a more straightforward approach through simple dot-notation access to JSON data. This feature can sim...]]></description><link>https://talkapex.com/dot-notation-for-json-in-oracle-sql</link><guid isPermaLink="true">https://talkapex.com/dot-notation-for-json-in-oracle-sql</guid><category><![CDATA[oracle-sql]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[json]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Wed, 03 Jan 2024 12:00:10 GMT</pubDate><content:encoded><![CDATA[<p>When dealing with Oracle SQL statements and the need to parse JSON arises, you've likely encountered the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/sqlrf/JSON_VALUE.html#GUID-C7F19D36-1E75-4CB2-AE67-ADFBAD23CBC2"><code>json_value</code></a> function. However, Oracle offers a more straightforward approach through simple dot-notation access to JSON data. This feature can simplify your SQL coding process. Below is an example of a basic query using <code>json_value</code></p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> demo_json (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">number</span>,
    json_data <span class="hljs-built_in">varchar2</span>(<span class="hljs-number">4000</span>)
);

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> demo_json(<span class="hljs-keyword">id</span>, json_data)
<span class="hljs-keyword">values</span>(<span class="hljs-number">1</span>, <span class="hljs-string">'{
    "name": "Martin",
    "company": "Oracle",
    "colors": ["red", "white", "blue"],
    "age": 25,
    "index": "demo_index"
}'</span>);
<span class="hljs-keyword">commit</span>;

<span class="hljs-keyword">select</span>
    json_value(d.json_data, <span class="hljs-string">'$.name'</span>) <span class="hljs-keyword">name</span>,
    json_value(d.json_data, <span class="hljs-string">'$.colors[2]'</span>) third_color,
    json_value(d.json_data, <span class="hljs-string">'$.age.number()'</span>) age
<span class="hljs-keyword">from</span> demo_json d
;

NAME   THIRD_COLOR AGE 
<span class="hljs-comment">------ ----------- --- </span>
Martin blue         25
</code></pre>
<p>Oracle has the ability to use <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/adjsn/simple-dot-notation-access-to-json-data.html#GUID-7249417B-A337-4854-8040-192D5CEFD576">simple dot-notation access to json data</a>. They're two requirements for this to work:</p>
<ul>
<li><p>The column type must either be type <code>json</code> or it must have an <code>is json</code> check constraint.</p>
</li>
<li><p>Must alias the table when selecting (<em>in the demo using</em> <code>d</code> as table alias name)</p>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-comment">-- Highlight that a check constraint is </span>
<span class="hljs-keyword">select</span> d.json_data.name
<span class="hljs-keyword">from</span> demo_json d
;


00904. 00000 -  "%s: invalid identifier"
*Cause:    
*Action:
Error at Line: 1 Column: 6
;

<span class="hljs-comment">-- Add check constraint</span>
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> demo_json <span class="hljs-keyword">add</span> <span class="hljs-keyword">constraint</span> demo_json_ck1 <span class="hljs-keyword">check</span> (json_data <span class="hljs-keyword">is</span> <span class="hljs-keyword">json</span>);

<span class="hljs-keyword">select</span> 
    d.json_data.name,
    d.json_data.colors[<span class="hljs-number">2</span>] third_color,
    d.json_data.age.number() age
<span class="hljs-keyword">from</span> demo_json d
;

NAME   THIRD_COLOR AGE 
<span class="hljs-comment">------ ----------- --- </span>
Martin blue         25
</code></pre>
<p>It's recommended to read the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/adjsn/simple-dot-notation-access-to-json-data.html#GUID-7249417B-A337-4854-8040-192D5CEFD576">documentation</a> as it covers all the features about JSON dot notation in SQL.</p>
<p><em>Thanks to</em> <a target="_blank" href="https://twitter.com/stefan__dobre"><em>Stefan Dobre</em></a> <em>for pointing this feature out to me.</em></p>
]]></content:encoded></item><item><title><![CDATA[How to customize Page Designer layout in APEX]]></title><description><![CDATA[They're many new and seasoned APEX developers who spend all day using Page Designer with the default settings as shown below. The default view is good for new users or users who want the low-code drag & drop nature of Page Designer.

If you spend a l...]]></description><link>https://talkapex.com/how-to-customize-page-designer-layout-in-apex</link><guid isPermaLink="true">https://talkapex.com/how-to-customize-page-designer-layout-in-apex</guid><category><![CDATA[#oracle-apex]]></category><category><![CDATA[orclapex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Mon, 27 Nov 2023 12:39:14 GMT</pubDate><content:encoded><![CDATA[<p>They're many new and seasoned APEX developers who spend all day using Page Designer with the default settings as shown below. The default view is good for new users or users who want the low-code drag &amp; drop nature of Page Designer.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701058560915/53aec895-efcb-4c80-a6e0-4a206b58fa3f.png" alt class="image--center mx-auto" /></p>
<p>If you spend a lot of time with code (SQL and PL/SQL) or settings in Page Designer you may tend to spend more of your time on the right hand side of the page. For example report queries, PL/SQL code blocks, settings, etc. In this case you may want move the sections around so that the settings are in the main pane of Page Designer. The video below highlights how to do this. I also move help text to the right pane so each time I click on a field I can quickly see inline help text.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701088628546/0577335f-1e99-4852-b2d8-775ac4a43300.gif" alt class="image--center mx-auto" /></p>
<p>To reset the layout click on the <code>Utilities (wrench) &gt; Layout &gt; Reset Layout</code> as shown below.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701088282470/261442a3-44c4-48cd-9bc2-8ecf53b72ac0.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[APEX_STRING.SPLIT, Pipelined Functions, and Pickler Fetches]]></title><description><![CDATA[Well that's a loaded title! Most developers may not know what each term means and as such this article will break down each item then group it all together.
apex_string.split
apex_string.split allows you to split a delimited string into rows (similar...]]></description><link>https://talkapex.com/apexstringsplit-pipelined-functions-and-pickler-fetches</link><guid isPermaLink="true">https://talkapex.com/apexstringsplit-pipelined-functions-and-pickler-fetches</guid><category><![CDATA[oracle-sql]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[#oracle-apex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sun, 26 Nov 2023 17:18:10 GMT</pubDate><content:encoded><![CDATA[<p>Well that's a loaded title! Most developers may not know what each term means and as such this article will break down each item then group it all together.</p>
<h1 id="heading-apexstringsplit"><code>apex_string.split</code></h1>
<p><a target="_blank" href="https://docs.oracle.com/en/database/oracle/apex/23.2/aeapi/SPLIT-Function-Signature-1.html#GUID-3BE7FF37-E54F-4503-91B8-94F374E243E6"><code>apex_string.split</code></a> allows you to split a delimited string into rows (similar procedures exists for <code>numbers</code> and <code>clobs</code>).</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> s.column_value
<span class="hljs-keyword">from</span> apex_string.split(<span class="hljs-string">'emp,dept'</span>, <span class="hljs-string">','</span>) s
;

COLUMN_VALUE 
<span class="hljs-comment">------------ </span>
emp          
dept
</code></pre>
<h1 id="heading-pipelined-functions">Pipelined Functions</h1>
<p>Pipelined functions are PL/SQL functions that "pipe rows" and are selectable as a table in a SQL. <code>apex_string.split</code> is a pipelined function. Oracle Base has a great article all about <a target="_blank" href="https://oracle-base.com/articles/misc/pipelined-table-functions">pipelined table functions</a> which you're encouraged to read and learn more about.</p>
<h2 id="heading-pickler-fetch">Pickler Fetch</h2>
<p>You may have seen <code>COLLECTION ITERATOR PICKLER FETCH</code> in an Oracle explain plan but never understood what they are. Martin Widlake has a great post about this <a target="_blank" href="https://mwidlake.wordpress.com/2011/08/11/pickler-fetch-what-is-it/">here</a>. TL;DR Oracle is processing something of unknown number of rows and needs to provide the optimizer with a number of rows. It defaults to <code>8168</code> rows.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1701017405279/648061af-3437-46ba-ae0a-b6653e7f842a.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-apexstringsplit-pipelined-functions-and-pickler-fetches"><code>apex_string.split</code>, Pipelined Functions, and Pickler Fetches</h1>
<p>Putting it altogether now. When using <code>apex_string.split</code> in your queries you could be introducing a performance problem without even realize it. The following example highlights this.</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Create a large table</span>
<span class="hljs-comment">-- This may take a while to run</span>
<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> all_objs <span class="hljs-keyword">as</span> 
<span class="hljs-keyword">select</span> object_name
<span class="hljs-keyword">from</span> all_objects
;

<span class="hljs-comment">-- If using SQL Dev Web run the next three all at the same time</span>
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">session</span> <span class="hljs-keyword">set</span> optimizer_capture_sql_plan_baselines=<span class="hljs-literal">true</span>;

<span class="hljs-keyword">select</span> ao.object_name
<span class="hljs-keyword">from</span> all_objs ao
    <span class="hljs-keyword">join</span> apex_string.split(<span class="hljs-string">'EMP,DEPT'</span>,<span class="hljs-string">','</span>) s <span class="hljs-keyword">on</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
        <span class="hljs-keyword">and</span> s.column_value = ao.object_name
;

<span class="hljs-keyword">select</span> plan_table_output
<span class="hljs-keyword">from</span> <span class="hljs-keyword">table</span>(dbms_xplan.display_cursor(<span class="hljs-literal">null</span>,<span class="hljs-literal">null</span>,<span class="hljs-string">'typical'</span>))
;

SQL_ID  cq8vm0pb4jacn, child number 0
<span class="hljs-comment">-------------------------------------</span>
<span class="hljs-keyword">select</span> ao.object_name <span class="hljs-keyword">from</span> all_objs ao     <span class="hljs-keyword">join</span> 
apex_string.split(<span class="hljs-string">'EMP,DEPT'</span>,<span class="hljs-string">','</span>) s <span class="hljs-keyword">on</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>         <span class="hljs-keyword">and</span> s.column_value = 
ao.object_name

Plan <span class="hljs-keyword">hash</span> <span class="hljs-keyword">value</span>: <span class="hljs-number">1741181693</span>

<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>
| <span class="hljs-keyword">Id</span>  | Operation                           | <span class="hljs-keyword">Name</span>     | <span class="hljs-keyword">Rows</span>  | <span class="hljs-keyword">Bytes</span> | <span class="hljs-keyword">Cost</span> (%CPU)| <span class="hljs-built_in">Time</span>     |
<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>
|   <span class="hljs-number">0</span> | <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">STATEMENT</span>                    |          |       |       |   <span class="hljs-number">112</span> (<span class="hljs-number">100</span>)|          |
|*  <span class="hljs-number">1</span> |  <span class="hljs-keyword">HASH</span> <span class="hljs-keyword">JOIN</span>                          |          | <span class="hljs-number">13550</span> |   <span class="hljs-number">529</span>K|   <span class="hljs-number">112</span>   (<span class="hljs-number">1</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">2</span> |   <span class="hljs-keyword">JOIN</span> FILTER <span class="hljs-keyword">CREATE</span>                | :BF0000  |  <span class="hljs-number">8168</span> | <span class="hljs-number">16336</span> |    <span class="hljs-number">29</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">3</span> |    COLLECTION ITERATOR PICKLER <span class="hljs-keyword">FETCH</span>| <span class="hljs-keyword">SPLIT</span>    |  <span class="hljs-number">8168</span> | <span class="hljs-number">16336</span> |    <span class="hljs-number">29</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">4</span> |   <span class="hljs-keyword">JOIN</span> FILTER <span class="hljs-keyword">USE</span>                   | :BF0000  | <span class="hljs-number">46481</span> |  <span class="hljs-number">1724</span>K|    <span class="hljs-number">82</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|*  <span class="hljs-number">5</span> |    <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">ACCESS</span> <span class="hljs-keyword">STORAGE</span> <span class="hljs-keyword">FULL</span>        | ALL_OBJS | <span class="hljs-number">46481</span> |  <span class="hljs-number">1724</span>K|    <span class="hljs-number">82</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>

Predicate Information (<span class="hljs-keyword">identified</span> <span class="hljs-keyword">by</span> operation <span class="hljs-keyword">id</span>):
<span class="hljs-comment">---------------------------------------------------</span>

   <span class="hljs-number">1</span> - <span class="hljs-keyword">access</span>(<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>=<span class="hljs-keyword">VALUE</span>(KOKBF$))
   <span class="hljs-number">5</span> - <span class="hljs-keyword">storage</span>(SYS_OP_BLOOM_FILTER(:BF0000,<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>))
       filter(SYS_OP_BLOOM_FILTER(:BF0000,<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>))
</code></pre>
<p>The plan from the example above highlights that the overall cardinality estimate is <code>13550</code> rows.</p>
<p>If you know the approximate number of items in the <code>apex_string.split</code> then a SQL hint can be applied to the query to tell the optimizer what to expect. The following demo highlights this. Using the previous example, it is a known fixed list of two items.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> 
    <span class="hljs-comment">-- Note "s" refers to the table alias below</span>
    <span class="hljs-comment">/*+ cardinality (s, 2) */</span>
    ao.object_name
<span class="hljs-keyword">from</span> all_objs ao
    <span class="hljs-keyword">join</span> apex_string.split(<span class="hljs-string">'EMP,DEPT'</span>,<span class="hljs-string">','</span>) s <span class="hljs-keyword">on</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
        <span class="hljs-keyword">and</span> s.column_value = ao.object_name
;

<span class="hljs-keyword">select</span> plan_table_output
<span class="hljs-keyword">from</span> <span class="hljs-keyword">table</span>(dbms_xplan.display_cursor(<span class="hljs-literal">null</span>,<span class="hljs-literal">null</span>,<span class="hljs-string">'typical'</span>));


SQL_ID  80h36ywn1rjjn, child number 0
<span class="hljs-comment">-------------------------------------</span>
<span class="hljs-keyword">select</span>      <span class="hljs-comment">-- Note "s" refers to the table alias below     </span>
<span class="hljs-comment">/*+ cardinality (s, 2) */</span>     ao.object_name <span class="hljs-keyword">from</span> all_objs ao     <span class="hljs-keyword">join</span> 
apex_string.split(<span class="hljs-string">'EMP,DEPT'</span>,<span class="hljs-string">','</span>) s <span class="hljs-keyword">on</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>         <span class="hljs-keyword">and</span> s.column_value = 
ao.object_name

Plan <span class="hljs-keyword">hash</span> <span class="hljs-keyword">value</span>: <span class="hljs-number">1741181693</span>

<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>
| <span class="hljs-keyword">Id</span>  | Operation                           | <span class="hljs-keyword">Name</span>     | <span class="hljs-keyword">Rows</span>  | <span class="hljs-keyword">Bytes</span> | <span class="hljs-keyword">Cost</span> (%CPU)| <span class="hljs-built_in">Time</span>     |
<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>
|   <span class="hljs-number">0</span> | <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">STATEMENT</span>                    |          |       |       |   <span class="hljs-number">111</span> (<span class="hljs-number">100</span>)|          |
|*  <span class="hljs-number">1</span> |  <span class="hljs-keyword">HASH</span> <span class="hljs-keyword">JOIN</span>                          |          |     <span class="hljs-number">3</span> |   <span class="hljs-number">120</span> |   <span class="hljs-number">111</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">2</span> |   <span class="hljs-keyword">JOIN</span> FILTER <span class="hljs-keyword">CREATE</span>                | :BF0000  |     <span class="hljs-number">2</span> |     <span class="hljs-number">4</span> |    <span class="hljs-number">29</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">3</span> |    COLLECTION ITERATOR PICKLER <span class="hljs-keyword">FETCH</span>| <span class="hljs-keyword">SPLIT</span>    |     <span class="hljs-number">2</span> |     <span class="hljs-number">4</span> |    <span class="hljs-number">29</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|   <span class="hljs-number">4</span> |   <span class="hljs-keyword">JOIN</span> FILTER <span class="hljs-keyword">USE</span>                   | :BF0000  | <span class="hljs-number">46481</span> |  <span class="hljs-number">1724</span>K|    <span class="hljs-number">82</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
|*  <span class="hljs-number">5</span> |    <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">ACCESS</span> <span class="hljs-keyword">STORAGE</span> <span class="hljs-keyword">FULL</span>        | ALL_OBJS | <span class="hljs-number">46481</span> |  <span class="hljs-number">1724</span>K|    <span class="hljs-number">82</span>   (<span class="hljs-number">0</span>)| <span class="hljs-number">00</span>:<span class="hljs-number">00</span>:<span class="hljs-number">01</span> |
<span class="hljs-comment">------------------------------------------------------------------------------------------------</span>

Predicate Information (<span class="hljs-keyword">identified</span> <span class="hljs-keyword">by</span> operation <span class="hljs-keyword">id</span>):
<span class="hljs-comment">---------------------------------------------------</span>

   <span class="hljs-number">1</span> - <span class="hljs-keyword">access</span>(<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>=<span class="hljs-keyword">VALUE</span>(KOKBF$))
   <span class="hljs-number">5</span> - <span class="hljs-keyword">storage</span>(SYS_OP_BLOOM_FILTER(:BF0000,<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>))
       filter(SYS_OP_BLOOM_FILTER(:BF0000,<span class="hljs-string">"AO"</span>.<span class="hljs-string">"OBJECT_NAME"</span>))


<span class="hljs-comment">-- Cleanup</span>
<span class="hljs-keyword">drop</span> <span class="hljs-keyword">table</span> all_objs;
</code></pre>
<p>The key difference in this that the optimizer now expects <code>3</code> rows (in the <code>HASH JOIN</code> row) instead of <code>13550</code>! When doing more complex joins this can really improve performance.</p>
<p>In an APEX form where the user may be selecting a several objects I tend to use orders of magnitude estimate (ex: 10, 100, 1000) for a hint.</p>
<p>A few key notes about SQL hints:</p>
<ul>
<li><p>Just because you can doesn't mean you should. Use then when necessary (and in the above example it is justified as the optimizer has no way of knowing the expected number of rows)</p>
</li>
<li><p>Good intro article about SQL hints by David Njoku <a target="_blank" href="https://www.red-gate.com/simple-talk/databases/oracle-databases/a-beginners-guide-to-optimizer-hints/">here</a></p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Regular expression patterns [[:upper:]] vs [A-Z]]]></title><description><![CDATA[Note: the comparisons in this article also applies to [[:lower:]] vs [a-z] regexp patterns.
Oracle regular expressions (regexp) support both [[:upper:]] or [A-Z] to find uppercase letters. At first glance they appear the same. Even regex101.com defin...]]></description><link>https://talkapex.com/regular-expression-patterns-upper-vs-a-z</link><guid isPermaLink="true">https://talkapex.com/regular-expression-patterns-upper-vs-a-z</guid><category><![CDATA[SQL]]></category><category><![CDATA[Regular Expressions]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[PL/SQL]]></category><category><![CDATA[oracle pl/sql]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sun, 26 Nov 2023 13:47:43 GMT</pubDate><content:encoded><![CDATA[<p><em>Note: the comparisons in this article also applies to</em> <code>[[:lower:]]</code> <em>vs</em> <code>[a-z]</code> <em>regexp patterns.</em></p>
<p>Oracle regular expressions (regexp) support both <code>[[:upper:]]</code> or <code>[A-Z]</code> to find uppercase letters. At first glance they appear the same. Even <a target="_blank" href="https://regex101.com/">regex101.com</a> defines them as the same:</p>
<blockquote>
<p><code>[[:upper:]]</code>: Matches uppercase letters. Equivalent to [A-Z]. The double square brackets is not a typo, POSIX notation demands it.</p>
</blockquote>
<p>There is a slight difference between the two. <code>[A-Z]</code> only deals with the 26 letters in the English alphabet whereas <code>[[:upper:]]</code> deals with special alphabet characters such as <code>Ê</code> - accent circumflex (<em>or as we learned in French glass "e avec un chapeau ").</em> The following example highlights the differences using the <a target="_blank" href="https://livesql.oracle.com/apex/livesql/file/content_O5AEB2HE08PYEPTGCFLZU9YCV.html">demo Oracle <code>emp</code> table</a>:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Change the "A" in Martin to A with an accent on it</span>
<span class="hljs-keyword">update</span> emp
<span class="hljs-keyword">set</span> ename = <span class="hljs-string">'MÄRTIN'</span>
<span class="hljs-keyword">where</span> empno = <span class="hljs-number">7654</span>;


<span class="hljs-comment">-- [A-Z]</span>
<span class="hljs-keyword">select</span> *
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
    <span class="hljs-keyword">and</span> empno = <span class="hljs-number">7654</span>
    <span class="hljs-keyword">and</span> <span class="hljs-keyword">regexp_like</span>(ename, <span class="hljs-string">'^[A-Z]+$'</span>)
;

<span class="hljs-comment">-- Returns</span>
<span class="hljs-comment">/*
No data found
*/</span>

<span class="hljs-comment">-- [[:upper:]]</span>
<span class="hljs-keyword">select</span> ename
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
    <span class="hljs-keyword">and</span> empno = <span class="hljs-number">7654</span>
    <span class="hljs-keyword">and</span> <span class="hljs-keyword">regexp_like</span>(ename, <span class="hljs-string">'^[[:upper:]]+$'</span>)
;

<span class="hljs-comment">-- Returns:</span>
<span class="hljs-comment">/*
ENAME  
------ 
MÄRTIN 
*/</span>


<span class="hljs-comment">-- Look at ASCII characters</span>
<span class="hljs-keyword">select</span> ename, dump(ename)
<span class="hljs-keyword">from</span> emp
<span class="hljs-keyword">where</span> empno = <span class="hljs-number">7654</span>
;

<span class="hljs-comment">/*
ENAME  DUMP(ENAME)                         
------ ----------------------------------- 
MÄRTIN Typ=1 Len=7: 77,195,132,82,84,73,78 
*/</span>

<span class="hljs-comment">-- You can see the second characters out of normal a-Z ASCII characters</span>

<span class="hljs-comment">-- Reset</span>
<span class="hljs-keyword">update</span> emp
<span class="hljs-keyword">set</span> ename = <span class="hljs-string">'MARTIN'</span>
<span class="hljs-keyword">where</span> empno = <span class="hljs-number">7654</span>
;
</code></pre>
<p>As you can see the results are different and <code>[[:upper:]]</code> matched the special characters. The following description from this <a target="_blank" href="https://emacs.stackexchange.com/questions/35160/is-there-any-principal-difference-between-a-z-and-upper">Stackoverflow</a> post highlights the differences:</p>
<blockquote>
<p><code>[A-Z]</code> matches only an ASCII uppercase letter, that is, a letter from A through Z. There are other, non-ASCII uppercase letters (e.g., in languages other than English).</p>
</blockquote>
<p>If you use regular expressions in your code do not go change everything from <code>[A-Z]</code> without consideration. They're some times where you may want to keep it in place (example lookup codes, etc). I tend to use <code>[[:upper:]]</code> for when dealing with user inputed fields when it makes sense.</p>
]]></content:encoded></item><item><title><![CDATA[Oracle SQL Listagg for Clobs]]></title><description><![CDATA[In Oracle the listagg function is limited to 4000 characters. If the resulting string exceeds 4000 characters then an error is raised. Example:
with 
    data as (
        -- Generates:
        -- - Numbers each 20 digits in length 
        -- - Alph...]]></description><link>https://talkapex.com/oracle-sql-listagg-for-clobs</link><guid isPermaLink="true">https://talkapex.com/oracle-sql-listagg-for-clobs</guid><category><![CDATA[oracle-sql]]></category><category><![CDATA[SQL]]></category><category><![CDATA[json]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sat, 25 Nov 2023 15:03:46 GMT</pubDate><content:encoded><![CDATA[<p>In Oracle the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/LISTAGG.html#GUID-B6E50D8E-F467-425B-9436-F7F8BF38D466"><code>listagg</code></a> function is limited to <code>4000</code> characters. If the resulting string exceeds <code>4000</code> characters then an error is raised. Example:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">with</span> 
    <span class="hljs-keyword">data</span> <span class="hljs-keyword">as</span> (
        <span class="hljs-comment">-- Generates:</span>
        <span class="hljs-comment">-- - Numbers each 20 digits in length </span>
        <span class="hljs-comment">-- - Alpha numeric strings 20 characters in length</span>
        <span class="hljs-keyword">select</span> 
            trunc(dbms_random.value() * <span class="hljs-keyword">power</span>(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>)) <span class="hljs-keyword">num</span>,
            dbms_random.string(<span class="hljs-string">'x'</span>, <span class="hljs-number">20</span>) <span class="hljs-keyword">str</span>
        <span class="hljs-keyword">from</span> dual
        <span class="hljs-keyword">connect</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">level</span> &lt;= <span class="hljs-number">10000</span>
    )
<span class="hljs-keyword">select</span> 
    <span class="hljs-keyword">listagg</span>(d.num, <span class="hljs-string">','</span>) <span class="hljs-keyword">within</span> <span class="hljs-keyword">group</span> (<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> d.num <span class="hljs-keyword">asc</span>) all_nums,
    <span class="hljs-keyword">listagg</span>(d.str, <span class="hljs-string">','</span>) <span class="hljs-keyword">within</span> <span class="hljs-keyword">group</span> (<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> d.num <span class="hljs-keyword">asc</span>) all_strs
<span class="hljs-keyword">from</span> <span class="hljs-keyword">data</span> d
;

<span class="hljs-comment">-- Raises</span>
01489. 00000 - "result of string concatenation is too long"
*Cause: String concatenation result is more than the maximum size.
*Action: Make sure that the result is less than the maximum size.
Error at Line: 0 Column: 0
</code></pre>
<p>You can control whether an error is raised (<code>on overflow error</code>) or truncate (<code>on overflow truncate ...</code>) the string using the <code>listagg_overflow_clause</code>. The <code>truncate</code> solution still doesn't solve the issue if you have a lot of content you want to concatenate.</p>
<p>An alternative approach is to use <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_ARRAYAGG.html#GUID-6D56077D-78DE-4CC0-9498-225DDC42E054"><code>json_arrayagg</code></a> along with the <code>returning clob</code> option. This trick only works in the following conditions:</p>
<ul>
<li><p>Numbers, dates, etc (i.e. structured date types)</p>
</li>
<li><p>For strings they have some known format and do not contain quotes or commas</p>
</li>
</ul>
<p>The example above can be converted from <code>listagg</code> to <code>json_arrayagg</code>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">with</span> 
    <span class="hljs-keyword">data</span> <span class="hljs-keyword">as</span> (
        <span class="hljs-keyword">select</span> 
            trunc(dbms_random.value() * <span class="hljs-keyword">power</span>(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>)) <span class="hljs-keyword">num</span>,
            dbms_random.string(<span class="hljs-string">'x'</span>, <span class="hljs-number">20</span>) <span class="hljs-keyword">str</span>
        <span class="hljs-keyword">from</span> dual
        <span class="hljs-keyword">connect</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">level</span> &lt;= <span class="hljs-number">10000</span>
    )
<span class="hljs-keyword">select</span> 
    json_arrayagg(d.num <span class="hljs-keyword">returning</span> <span class="hljs-keyword">clob</span>) json_array_nums,
    json_arrayagg(d.str <span class="hljs-keyword">returning</span> <span class="hljs-keyword">clob</span>) json_array_strs
<span class="hljs-keyword">from</span> <span class="hljs-keyword">data</span> d
;

<span class="hljs-comment">-- Returns </span>
<span class="hljs-comment">-- [16390942703930704741,72925476174569629179,98810344353460307813,...]</span>
<span class="hljs-comment">-- ["OJV24DVTF6EIF80F76NE","CY6T5L8S3BZW2CN0CHJY",...]</span>
</code></pre>
<p>The result still contains the square brackets and quotes around the strings. To remove them to just have comma delimited list we can use regular expressions (can also do with a few <code>replace</code> statements):</p>
<pre><code class="lang-sql"><span class="hljs-keyword">with</span> 
    <span class="hljs-keyword">data</span> <span class="hljs-keyword">as</span> (
        <span class="hljs-keyword">select</span> 
            trunc(dbms_random.value() * <span class="hljs-keyword">power</span>(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>)) <span class="hljs-keyword">num</span>,
            dbms_random.string(<span class="hljs-string">'x'</span>, <span class="hljs-number">20</span>) <span class="hljs-keyword">str</span>
        <span class="hljs-keyword">from</span> dual
        <span class="hljs-keyword">connect</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">level</span> &lt;= <span class="hljs-number">10000</span>
    )
<span class="hljs-keyword">select</span> 
    <span class="hljs-comment">-- <span class="hljs-doctag">Note:</span> the order of the square brackets in the regexp string: []["] matters</span>
    <span class="hljs-comment">-- as SQL doesn't require escaping strings within square bracket operators</span>
    regexp_replace(json_arrayagg(d.num <span class="hljs-keyword">returning</span> <span class="hljs-keyword">clob</span>), <span class="hljs-string">'[]["]'</span>, <span class="hljs-string">''</span>) nums,
    regexp_replace(json_arrayagg(d.str <span class="hljs-keyword">returning</span> <span class="hljs-keyword">clob</span>), <span class="hljs-string">'[]["]'</span>, <span class="hljs-string">''</span>) strs
<span class="hljs-keyword">from</span> <span class="hljs-keyword">data</span> d
;

<span class="hljs-comment">-- Returns </span>
<span class="hljs-comment">-- 16390942703930704741,72925476174569629179,98810344353460307813,... </span>
<span class="hljs-comment">-- OJV24DVTF6EIF80F76NE,CY6T5L8S3BZW2CN0CHJY,...</span>
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to merge JSON objects in SQL]]></title><description><![CDATA[When working with APEX, handling JSON data structures is a common task, particularly when interacting with REST APIs. There may be instances where you need to merge or layer one JSON object onto another, such as when using default configurations in a...]]></description><link>https://talkapex.com/how-to-merge-json-objects-in-sql</link><guid isPermaLink="true">https://talkapex.com/how-to-merge-json-objects-in-sql</guid><category><![CDATA[SQL]]></category><category><![CDATA[oracle-sql]]></category><category><![CDATA[json]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sat, 25 Nov 2023 13:43:32 GMT</pubDate><content:encoded><![CDATA[<p>When working with APEX, handling JSON data structures is a common task, particularly when interacting with REST APIs. There may be instances where you need to merge or layer one JSON object onto another, such as when using default configurations in an application and allowing for customizations. This functionality is typically achieved in JavaScript functions using the <code>options</code> parameter.</p>
<p>You can easily do this in SQL using the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/JSON_MERGEPATCH.html#GUID-2004F536-BE60-4457-A1A8-AB908FFF5399"><code>json_mergepatch</code></a> function. The following example highlights how to modify and also add items to a JSON object:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> 
    json_mergepatch(
<span class="hljs-comment">-- default value</span>
<span class="hljs-string">'
{
    "foo": 123,
    "bar": {
        "firstName": "default",
        "lastName": "default"
    }
}
'</span>,
<span class="hljs-comment">-- custom value</span>
<span class="hljs-comment">-- replace "lastName"</span>
<span class="hljs-comment">-- adds "middleName"</span>
<span class="hljs-comment">-- adds "language"</span>
<span class="hljs-string">'
{
    "bar": {
        "lastName": "dsouza",
        "middleName": "giffy"
    },
    "language": "english"
}
'</span>
<span class="hljs-comment">-- If you your resulting JSON will be larger than 4000 then return a clob by uncommented below</span>
<span class="hljs-comment">-- returning clob</span>
<span class="hljs-comment">--</span>
<span class="hljs-comment">-- The "pretty" keyword is optional and just helps with display</span>
pretty
) demo
<span class="hljs-keyword">from</span> sys.dual;
</code></pre>
<p>The resulting JSON object is:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"foo"</span>: <span class="hljs-number">123</span>,
    <span class="hljs-attr">"bar"</span>: {
        <span class="hljs-attr">"firstName"</span>: <span class="hljs-string">"default"</span>,
        <span class="hljs-attr">"lastName"</span>: <span class="hljs-string">"dsouza"</span>,
        <span class="hljs-attr">"middleName"</span>: <span class="hljs-string">"giffy"</span>
    },
    <span class="hljs-attr">"language"</span>: <span class="hljs-string">"english"</span>
}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[How to find which item has been changed in APEX]]></title><description><![CDATA[Note: I previously had a similar post about this which I somehow lost so re-writing it. The original link was: https://talkapex.com/2021/01/how-to-find-which-item-has-been-changed-in-apex
By default APEX will warn users when leaving a page. When user...]]></description><link>https://talkapex.com/how-to-find-which-item-has-been-changed-in-apex</link><guid isPermaLink="true">https://talkapex.com/how-to-find-which-item-has-been-changed-in-apex</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[#oracle-apex]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sat, 25 Nov 2023 13:14:04 GMT</pubDate><content:encoded><![CDATA[<p><em>Note: I previously had a similar post about this which I somehow lost so re-writing it. The original link was: https://talkapex.com/2021/01/how-to-find-which-item-has-been-changed-in-apex</em></p>
<p>By default APEX will warn users when leaving a page. When users modify a page with input and try to close the tab or refresh the page they will be shown the following dialog (<em>note: each browser will show a slightly different warning message):</em></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700917230088/b2fc95d9-0ace-4659-b3e6-d07023737643.jpeg" alt class="image--center mx-auto" /></p>
<p>The default options for this can be changed at the page level by toggling the <code>Warn on Unsaved Changes</code> option:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700917316162/2a0bef95-e2eb-4c7f-9dd2-de5ae4e940ac.png" alt class="image--center mx-auto" /></p>
<p>Alternatively you can control each page item to determine if it'll trigger a warn on unsaved message:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700917354100/35b313a4-e112-46aa-9fc3-a7c2d0526ef2.png" alt class="image--center mx-auto" /></p>
<p>There are some times when you may want to manually determine if a page item has been modified. In JavaScript the <code>apex.item</code> object contains a function called <a target="_blank" href="https://docs.oracle.com/en/database/oracle/apex/23.2/aexjs/apex.item.html"><code>isChanged()</code></a> Using the new<em>(ish)</em> <code>apex.items</code> object (which contains all the page items) you can quickly identify which page items have been changed. The following function will return an array of APEX item names that have been changed:</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">Object</span>.keys(apex.items).filter(<span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> apex.items[key].isChanged());
</code></pre>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1700917690492/e134be7e-9672-4e3d-bdd8-5caf037a9e11.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://twitter.com/haydenhhudson">Hayden Hudson</a> has a neat <a target="_blank" href="https://gist.github.com/hhudson/c2d57dcbdbbc7e01021aada718fe4506#file-apex-detect-changed-items-js">snippet</a> that shows some custom warnings when a page item has been changed.</p>
]]></content:encoded></item><item><title><![CDATA[The Biggest Hidden Bug in Your APEX Application]]></title><description><![CDATA[Let's start with a little quiz. Without checking, what do you think happens/returns running the following query:
-- Note the two digit year
select to_date('24-Jan-21', 'DD-MON-YYYY')
from dual;

I recently asked this same question on Twitter and the ...]]></description><link>https://talkapex.com/the-biggest-hidden-bug-in-your-apex-application</link><guid isPermaLink="true">https://talkapex.com/the-biggest-hidden-bug-in-your-apex-application</guid><category><![CDATA[#oracle-apex]]></category><category><![CDATA[SQL]]></category><category><![CDATA[oracle-sql]]></category><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sun, 31 Jan 2021 13:03:23 GMT</pubDate><content:encoded><![CDATA[<p>Let's start with a little quiz. Without checking, what do you think happens/returns running the following query:</p>
<pre><code class="lang-sql"><span class="hljs-comment">-- Note the two digit year</span>
<span class="hljs-keyword">select</span> <span class="hljs-keyword">to_date</span>(<span class="hljs-string">'24-Jan-21'</span>, <span class="hljs-string">'DD-MON-YYYY'</span>)
<span class="hljs-keyword">from</span> dual;
</code></pre>
<p>I recently <a target="_blank" href="https://twitter.com/martindsouza/status/1353550672919023617">asked this same question</a> on Twitter and the results were quite interesting given that this <em>should</em> be a very straight forward answer. Over 50% of the people got it wrong:</p>
<p>{% asset_img twitter-results.png %}</p>
<p>The correct answer is that the query returns: <code>24-Jan-0021</code>. If you thought an error would be raised you're in good company. I reached out to several <a target="_blank" href="https://developer.oracle.com/ace/">Oracle ACE</a>s (i.e. world leading Oracle experts) and they all got it wrong as well (myself included).</p>
<p>By using a date format of <code>DD-MON-YYYY</code> if a user does not explicitly enter a four digit year, Oracle will left pad the number with <code>0</code>s. I.e. <code>21</code> becomes <code>0021</code>. This makes sense that one would expect <code>5-Jan-2021</code> to really be <code>05-Jan-2021</code>. </p>
<p>What does this have to do with your APEX application? In all my applications I tend to set the default date format to <code>DD-MON-YYYY</code> as it's very explicit. <em>Note: to set the default date format in APEX go to <code>Shared Components &gt; Globalization &gt; Application Date Format</code>.</em> Most of the Date items that I use allow users to either enter the date manually or select from the date picker (via button click). Since entering a two digit year is a valid date neither APEX or Oracle raises an error so it's extremely difficult to catch.</p>
<p>To get around this issue you can change the Application Date Format to: <code>DD-MON-RRRR</code>. From the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Format-Models.html#GUID-6C75461E-2E18-4C35-9EB4-038A7E1C9C1F">Oracle documentation</a>:</p>
<p><em>The RR datetime format element is similar to the YY datetime format element, but it provides additional flexibility for storing date values in other centuries. The RR datetime format element lets you store 20th century dates in the 21st century by specifying only the last two digits of the year.</em> </p>
<p>Using our initial example</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> <span class="hljs-keyword">to_date</span>(<span class="hljs-string">'24-Jan-21'</span>, <span class="hljs-string">'DD-MON-YYYY'</span>)
<span class="hljs-keyword">from</span> dual;

<span class="hljs-comment">-- Returns</span>
24-jan-2021
</code></pre>
<p>The nice thing about using the <code>RRRR</code> format is you can still display years as four digits but users can enter them in as two digits with expected results. If users are entering past dates (ex: cataloging old library books) I suggest you read the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Format-Models.html#GUID-6C75461E-2E18-4C35-9EB4-038A7E1C9C1F">documentation</a> to see if the <code>RRRR</code> format is the right setting for your application.</p>
]]></content:encoded></item><item><title><![CDATA[Inline Function in Dynamic Action JavaScript Expression in APEX]]></title><description><![CDATA[In some Dynamic Actions (DA) settings in APEX you have the option to use a JavaScript (JS) Expression. A common example of this is setting a value where the Set Type is JavaScript:
{% asset_img js-expression.png %}
As the name (JavaScript Expression)...]]></description><link>https://talkapex.com/inline-function-in-dynamic-action-javascript-expression-in-apex</link><guid isPermaLink="true">https://talkapex.com/inline-function-in-dynamic-action-javascript-expression-in-apex</guid><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sun, 31 Jan 2021 12:37:09 GMT</pubDate><content:encoded><![CDATA[<p>In some Dynamic Actions (DA) settings in APEX you have the option to use a JavaScript (JS) Expression. A common example of this is setting a value where the <code>Set Type</code> is <code>JavaScript</code>:</p>
<p>{% asset_img js-expression.png %}</p>
<p>As the name (JavaScript Expression) suggests this should be an expression such as <code>1 + 2;</code> If you try to run multiple lines of code with a <code>return</code> statement (as shown above) the following error is raised:</p>
<pre><code class="lang-javascript">Uncaught <span class="hljs-built_in">TypeError</span>: apex.da.initDaEventList is not a <span class="hljs-function"><span class="hljs-keyword">function</span></span>
</code></pre>
<p>To resolve this issue you can use an <a target="_blank" href="https://flaviocopes.com/javascript-iife/">immediately-invoked Function Expressions (IIFE)</a> as the JavasScript Expression. The following example re-writes the code from the first example as an IIFE and will work as a valid JavaScript Expression in the DA:</p>
<pre><code class="lang-javascript">(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">var</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();
  <span class="hljs-keyword">return</span> today.getFullYear();
})()
</code></pre>
<p>Thanks to <a target="_blank" href="https://twitter.com/fuzziebrain">Adrian Png</a> and <a target="_blank" href="https://twitter.com/trentschafer">Trent Schafer</a> for the help on this.</p>
]]></content:encoded></item><item><title><![CDATA[How to Quickly Build Templates in APEX Office Print]]></title><description><![CDATA[If you ever need to generate PDF reports, Excel files, or Word documents based on data from your APEX application (or from within your Oracle database) APEX Office Print (AOP) is the tool for you. If you've never used or heard of AOP before you shoul...]]></description><link>https://talkapex.com/how-to-quickly-build-templates-in-apex-office-print</link><guid isPermaLink="true">https://talkapex.com/how-to-quickly-build-templates-in-apex-office-print</guid><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sun, 17 Jan 2021 17:24:25 GMT</pubDate><content:encoded><![CDATA[<p>If you ever need to generate PDF reports, Excel files, or Word documents based on data from your APEX application (or from within your Oracle database) <a target="_blank" href="https://www.apexofficeprint.com/">APEX Office Print</a> (AOP) is the tool for you. If you've never used or heard of AOP before you should review it before continuing with this article.</p>
<p>This is the desired outcome of the article below. To summarize the solution will allow me to quickly edit a Word template using Office 365. Then view the generated PDF in APEX with a minimal amount of effort.</p>
<p>{% asset_img aop-template.gif %}</p>
<h2 id="heading-background">Background</h2>
<p>AOP has two common uses cases (personal opinion). The first is providing users the ability to download their reports exactly as they're shown in any format they'd like. For example if they build an Interactive Report (IR) with row highlighting etc and click "Download", AOP will make the report look exactly as the user defined it. The second, which this article focuses on, is to create customizable documents based on data in Oracle. A good example of this is generating a invoice for a customer.</p>
<h2 id="heading-problem">Problem</h2>
<p>Using the later case, to generate a PDF invoice you need to create a Word or Excel file and use substitution string for AOP to replace data with. For example, to show the Invoice Number based on the column <code>invoice_num</code> you would reference <code>{invoice_num}</code> in your Word document. AOP with then "fill in" the substitution strings with your data. In this case the process is as follows:</p>
<ol>
<li>Define your query (with all the data)<ul>
<li>One time task unless more data is required </li>
</ul>
</li>
<li>Upload Word template file<ul>
<li>They're various options as to where to store a file (more on this later)</li>
</ul>
</li>
<li>Generate the PDF<ul>
<li>This will involved downloading and then opening the PDF on your laptop</li>
</ul>
</li>
</ol>
<p>When initially building a template file the last two steps can happen many times. I.e. I want to see what the invoice would look like if I move some text over a bit. I'd need to re-upload the file then re-generate and download the PDF. This process has a few problems:</p>
<ul>
<li>It's not good for multiple people working on the same template at the same time</li>
<li>There's lots of steps involved once making a change to the template to see the resulting PDF</li>
</ul>
<h2 id="heading-solution">Solution</h2>
<p>To resolve the issues described above I created the following process to allow me to quickly build and view invoices using AOP and APEX. These steps require that you have an <a target="_blank" href="https://www.office.com">Office 365</a> account. (<em>Note: if using a coporate Office 365 you'll need the ability to share files with people outside of your organization. If you're not allowed to use a personal account to host the template file</em>)</p>
<h3 id="heading-host-template-on-onedrive">Host Template on OneDrive</h3>
<ul>
<li>Go to <a target="_blank" href="https://onedrive.live.com/">One Drive</a> and upload a new Word document<ul>
<li><strong>Note:</strong> <em>For some reason when creating the Word document directly in One Drive AOP can't process the file. To get around this create a Word document on your desktop and upload</em></li>
<li><em>Note: the same steps can be used for any type of Office document (ex: Excel)</em></li>
</ul>
</li>
<li>Generate Share URL<ul>
<li>This will be a link to give others to work on modifying the Word template file</li>
<li>We'll refer to this URL as the "Share URL"</li>
<li>Right click on the file and click <code>Share</code></li>
</ul>
</li>
</ul>
<p>{% asset_img office-365-share-01.png %}</p>
<ul>
<li>A new modal box pops up. Make sure to select the appropriate permissions (not critical that everyone can edit but does make it easy to share the link)</li>
<li>Select <code>Copy link</code> and save this link somewhere<ul>
<li>In my example the link is: <code>https://1drv.ms/w/s!AmD6WK_LJIvHq8xyy0JR-JT6ihJdPQ?e=xC88lZ</code></li>
</ul>
</li>
</ul>
<p>{% asset_img office-365-share-02.png %}</p>
<ul>
<li>Generate Embed URL<ul>
<li>This will be used by your database to download the template file and send to AOP</li>
<li>We'll refer to this URL as the "Embed URL"</li>
<li>Right click on the file and click <code>Embed</code></li>
</ul>
</li>
</ul>
<p>{% asset_img office-365-embed-01.png %}</p>
<ul>
<li>A new slider pops up on the right hand side. Click <code>Generate</code></li>
</ul>
<p>{% asset_img office-365-embed-02.png %}</p>
<ul>
<li>Copy the <code>iframe</code> HTML tag and save this link<ul>
<li>In my example the HTML is: <code>&lt;iframe src="https://onedrive.live.com/embed?cid=C78B24CBAF58FA60&amp;resid=C78B24CBAF58FA60%21714354&amp;authkey=AKLKkXB0W5OfUZ4&amp;em=2" width="476" height="288" frameborder="0" scrolling="no"&gt;&lt;/iframe&gt;</code></li>
</ul>
</li>
</ul>
<p>{% asset_img office-365-embed-03.png %}</p>
<h3 id="heading-apex-setup">APEX Setup</h3>
<p>Make sure that your APEX application has the latest <a target="_blank" href="http://www.apexofficeprint.com/docs/#apex-plug-in">AOP plugins</a> installed. </p>
<p>Create a new page with a new region. Add the following text page items (<em>Note: you can change to hidden later on / load from a table</em>)</p>
<ul>
<li><code>P3120_ONEDRIVE_EMBED_IFRAME</code></li>
</ul>
<p>In the region add the following in the <code>Source &gt; Text</code> box:</p>
<p><code>&lt;div id="pdf" data-aop-inline-pdf="onClick: Generate PDF" style="width:100%;height:600px;"&gt;&lt;/div&gt;</code></p>
<p>{% asset_img apex-setup-region.png %}</p>
<p>Create a new region button called <code>GENERATE_PDF</code></p>
<ul>
<li>Set the <code>Button Position</code> to <code>Edit</code></li>
<li>Right click on the button and <code>Create Dynamic Action</code><ul>
<li><code>Name</code>: <code>onClick: Generate PDF</code><ul>
<li>Note: <strong>Do Not</strong> this name must match the <code>data-aop-inline-pdf</code> from the previous <code>div</code> tag</li>
</ul>
</li>
<li>Add the following actions:<ul>
<li><code>Execute Server Side Code</code><ul>
<li>Items to Submit: <code>P3120_ONEDRIVE_EMBED_IFRAME</code></li>
<li>PL/SQL Code:</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">declare</span>
  l_download_url <span class="hljs-built_in">varchar2</span>(<span class="hljs-number">4000</span>);
  l_blob blob;
  l_file_name varchar2(4000);
<span class="hljs-keyword">begin</span>
  <span class="hljs-comment">-- Need to replace ampersand with amp; for XML parsing</span>
  <span class="hljs-comment">-- Need to do in SQL as extractvalue not PL/SQL</span>
  <span class="hljs-keyword">select</span> <span class="hljs-keyword">replace</span>(extractvalue(<span class="hljs-keyword">xmltype</span>(<span class="hljs-keyword">replace</span>(:p3120_onedrive_embed_iframe, <span class="hljs-keyword">chr</span>(<span class="hljs-number">38</span>), <span class="hljs-keyword">chr</span>(<span class="hljs-number">38</span>) || <span class="hljs-string">'amp;'</span>)), <span class="hljs-string">'/iframe/@src'</span>), <span class="hljs-string">'embed'</span>, <span class="hljs-string">'download'</span>)
  <span class="hljs-keyword">into</span> l_download_url
  <span class="hljs-keyword">from</span> dual;


  <span class="hljs-comment">-- Need to download the blob here as AOP expects a filename in the URL. The Office downloads don't have that.</span>

  <span class="hljs-comment">-- Download the file into a blob</span>
  l_blob := apex_web_service.make_rest_request_b(
    p_url =&gt; l_download_url,
    p_http_method =&gt; 'GET');

  <span class="hljs-comment">-- Need to extract filename so that we can get the file extension</span>
  for i in 1.. apex_web_service.g_headers.count loop
    if apex_web_service.g_headers(i).name = 'Content-Disposition' then
      <span class="hljs-comment">-- Value will look like: attachment; filename="wo-test.docx"</span>
      l_file_name := trim('"' from  regexp_substr(apex_web_service.g_headers(i).value, '".*"'));
      exit; <span class="hljs-comment">-- Don't need to parse headers anymore</span>
    <span class="hljs-keyword">end</span> <span class="hljs-keyword">if</span>;
  <span class="hljs-keyword">end</span> <span class="hljs-keyword">loop</span>;

  <span class="hljs-comment">-- Store in Collection for retrieval from AOP</span>
  apex_collection.create_or_truncate_collection(
    p_collection_name =&gt; 'AOP_TEMPLATE');

  <span class="hljs-comment">-- Load</span>
  apex_collection.add_member(
    p_collection_name =&gt; 'AOP_TEMPLATE',
    p_c001 =&gt; l_file_name,
    p_c002 =&gt; apex_string_util.get_file_extension(l_file_name),
    p_blob001 =&gt; l_blob
  );
<span class="hljs-keyword">end</span>;
</code></pre>
<p>{% asset_img apex-da-execute-plsql.png %}</p>
<ul>
<li>Plugin: <code>UC - APEX Office Print (AOP) - DA [Plug-In]</code><ul>
<li><em>Note: You'll to have installed the AOP Plugins for this to show up in list</em></li>
<li>Settings:<ul>
<li>Template Type: <code>SQL</code></li>
<li>Template Source:</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span>
  ac.c002 file_ext,
  ac.blob001 blob_contents
<span class="hljs-keyword">from</span> apex_collections ac
<span class="hljs-keyword">where</span> <span class="hljs-number">1</span>=<span class="hljs-number">1</span>
  <span class="hljs-keyword">and</span> ac.collection_name = <span class="hljs-string">'AOP_TEMPLATE'</span>
</code></pre>
<ul>
<li>Data Type: <code>SQL</code></li>
<li>Data Source:</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-comment">-- Change this to reference you tables etc</span>
<span class="hljs-keyword">select</span>
  <span class="hljs-string">'demo'</span> <span class="hljs-keyword">as</span> <span class="hljs-string">"filename"</span>,
  <span class="hljs-keyword">cursor</span>(
    <span class="hljs-keyword">select</span> to_char(<span class="hljs-keyword">sysdate</span>, <span class="hljs-string">'DD-MON-YYYY'</span>) <span class="hljs-keyword">as</span> <span class="hljs-string">"today"</span>
    <span class="hljs-keyword">from</span> dual
  ) <span class="hljs-keyword">as</span> <span class="hljs-string">"data"</span>
<span class="hljs-keyword">from</span> dual
</code></pre>
<ul>
<li>Output Type: <code>PDF</code><ul>
<li><em>Note you can change this to a different type of file later but for the preview in APEX, PDF is required</em></li>
</ul>
</li>
<li>Output To: <code>Inline Region (pdf/html/md/txt only)</code><ul>
<li><em>This is what will make the PDF show up in APEX</em></li>
</ul>
</li>
</ul>
<p>{% asset_img apex-da-aop-plugin.png %}</p>
<p>Using the iFrame URL, users should be able to see the preview in APEX based on changes made in Office 365 (see video at top of article)</p>
]]></content:encoded></item><item><title><![CDATA[SQLDeveloper in macOS Big Sur]]></title><description><![CDATA[After upgrading to macOS Big Sur I couldn't run Oracle SQL Developer. I got the following error: The application “SQLDeveloper.app” can’t be opened
{% asset_img macos-error.png %}
To resolve this issue I got help from Kris Rice and Niels de Bruijn. T...]]></description><link>https://talkapex.com/sqldeveloper-in-macos-big-sur</link><guid isPermaLink="true">https://talkapex.com/sqldeveloper-in-macos-big-sur</guid><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Sat, 14 Nov 2020 13:58:13 GMT</pubDate><content:encoded><![CDATA[<p>After upgrading to <a target="_blank" href="https://www.apple.com/ca/macos/big-sur/">macOS Big Sur</a> I couldn't run <a target="_blank" href="https://www.oracle.com/database/technologies/appdev/sqldeveloper-landing.html">Oracle SQL Developer</a>. I got the following error: <code>The application “SQLDeveloper.app” can’t be opened</code></p>
<p>{% asset_img macos-error.png %}</p>
<p>To resolve this issue I got help from <a target="_blank" href="https://twitter.com/krisrice">Kris Rice</a> and <a target="_blank" href="https://twitter.com/nielsdb">Niels de Bruijn</a>. The issue is caused by Apple and the Java applet plugin. </p>
<p>Here's how I got SQL Developer working:</p>
<h2 id="heading-list-your-java-versions">List your Java Versions</h2>
<pre><code class="lang-bash">/usr/libexec/java_home -V

<span class="hljs-comment"># This will give an output like:</span>

Matching Java Virtual Machines (3):
    11.0.2 (x86_64) <span class="hljs-string">"Oracle Corporation"</span> - <span class="hljs-string">"Java SE 11.0.2"</span> /Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
    1.8.271.09 (x86_64) <span class="hljs-string">"Oracle Corporation"</span> - <span class="hljs-string">"Java"</span> /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
    1.8.0_201 (x86_64) <span class="hljs-string">"Oracle Corporation"</span> - <span class="hljs-string">"Java SE 8"</span> /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
</code></pre>
<p>The one that caused the problem is the <code>"Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home</code> and you'll need to remove it.</p>
<h2 id="heading-remove-the-applet-plugin">Remove the Applet Plugin</h2>
<pre><code class="lang-bash">sudo rm -rf <span class="hljs-string">"/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/"</span>


<span class="hljs-comment"># After deleting this folder it should be removed from the Java Home list</span>
/usr/libexec/java_home -V

<span class="hljs-comment"># Output</span>
Matching Java Virtual Machines (2):
    11.0.2 (x86_64) <span class="hljs-string">"Oracle Corporation"</span> - <span class="hljs-string">"Java SE 11.0.2"</span> /Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
    1.8.0_201 (x86_64) <span class="hljs-string">"Oracle Corporation"</span> - <span class="hljs-string">"Java SE 8"</span> /Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-11.0.2.jdk/Contents/Home
</code></pre>
<p>If you run SQL Developer now it should work. I'm not sure about the side effects of removing this Java applet plugin. If I find it to be critical to other things I'll be sure to update this post.</p>
]]></content:encoded></item><item><title><![CDATA[Screenshots in macOS and Continuity Markup]]></title><description><![CDATA[In macOS Catalina (10.15) there's a new (maybe this was released in an earlier version) version of the built in Screenshot app has some really cool features that addresses most of my screenshot needs. Before continuing you should read the following A...]]></description><link>https://talkapex.com/screenshots-in-macos-and-continuity-markup</link><guid isPermaLink="true">https://talkapex.com/screenshots-in-macos-and-continuity-markup</guid><dc:creator><![CDATA[Martin Giffy D'Souza]]></dc:creator><pubDate>Tue, 28 Apr 2020 04:41:10 GMT</pubDate><content:encoded><![CDATA[<p>In macOS Catalina (10.15) there's a new (<em>maybe this was released in an earlier version</em>) version of the built in Screenshot app has some really cool features that addresses most of my screenshot needs. Before continuing you should read the following Apple support articles about the built in Screenshot tool:</p>
<ul>
<li><a target="_blank" href="https://support.apple.com/en-ca/HT201361">How to take a screenshot</a></li>
<li><a target="_blank" href="https://support.apple.com/en-ca/guide/mac-help/mh26782/10.15/mac/10.15">Screenshot tool features</a></li>
</ul>
<p>One thing that is a bit of a hidden gem is the ability to quickly markup screen shots both using the Markup tool. To activate the markup tool click the thumbnail icon of the screenshot (right after it's taken). The following video highlights this:</p>
<p>{% asset_img macos-screenshot-markup.gif %}</p>
<p>The Markup tool has a very cool feature called <a target="_blank" href="https://support.apple.com/en-ca/guide/mac-help/mchl1fd88863/10.15/mac/10.15">Continuity Markup</a> that allows you to markup the screenshot either on your iPad or iPhone. The following video shows this. <em>Note: once I clicked on the thumbnail of my screenshot, in the bottom right corner on my screen, it automatically turned on my iPad for me!</em></p>


<p>You can also use Continuity Markup when using <a target="_blank" href="https://support.apple.com/en-ca/guide/mac-help/mh14119/10.15/mac/10.15">Quick Look</a> to preview a file (ex: hitting <code>spacebar</code> on a file in Finder).</p>
]]></content:encoded></item></channel></rss>