<?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[Jochen's Oracle]]></title><description><![CDATA[I came into contact with Oracle Relational Database Management System at the end of the 90's at school.
Ever since then I've worked with it. Some old and more r]]></description><link>https://blog.jochenvandenbossche.dev</link><generator>RSS for Node</generator><lastBuildDate>Wed, 15 Apr 2026 10:57:00 GMT</lastBuildDate><atom:link href="https://blog.jochenvandenbossche.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Oracle 26ai insert into select returning]]></title><description><![CDATA[In the 21c SQL Language Reference the following can be found regarding the INSERT statement:

But in the 26ai SQL Language Reference that becomes

and that small change opens a world of opportunity, reducing the number of context switches in certain ...]]></description><link>https://blog.jochenvandenbossche.dev/oracle-26ai-insert-into-select-returning</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/oracle-26ai-insert-into-select-returning</guid><category><![CDATA[Oracle]]></category><category><![CDATA[Oracle 26ai]]></category><category><![CDATA[oracle db 26ai]]></category><category><![CDATA[SQL]]></category><category><![CDATA[#insert]]></category><category><![CDATA[PL/SQL]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Sun, 15 Feb 2026 13:44:34 GMT</pubDate><content:encoded><![CDATA[<p>In the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/INSERT.html">21c SQL Language Reference</a> the following can be found regarding the <code>INSERT</code> statement:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771159764198/60306a11-61ad-4b94-a5d6-5681c72f9474.png" alt class="image--center mx-auto" /></p>
<p>But in the <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/26/sqlrf/INSERT.html">26ai SQL Language Reference</a> that becomes</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771159818740/fb30af08-b7ab-451f-8de9-7073781c5e3c.png" alt class="image--center mx-auto" /></p>
<p>and that small change opens a world of opportunity, reducing the number of context switches in certain cases, as shown below. But there are also some ceveats!</p>
<p>The classic set-up:</p>
<p>Suppose you have a table with an identity column as primary key and another table with a foreign key pointing to it. Further, suppose you need PL/SQL to insert records in both that parent and child table. In that case, you’ll need to to use <code>insert … returning</code> in orer to get the PK-value created during that insert in order to use it for the subsequent insert(s) into the child table.</p>
<p>Before 26ai, the <code>retunring_clause</code> was only possible when using the <code>values_clause</code>. If you wanted to use a subquery (an “insert-select”), there was no way to also have a <code>retunring</code> clause.</p>
<p>For example, when using <em>collections</em> in Oracle Apex, it is common to use PL/SQL to select from <code>apex_collections</code> to get the data stored in collections into records in tables.</p>
<p>Before 26ai, this required</p>
<ol>
<li><p>select …<br /> from apex_collection<br /> into :plsql_variables_or_records<br /> where …</p>
</li>
<li><p>insert into destination_parent_table (…)<br /> values (:plsql_variables_or_records, …)<br /> returning new ID as :variable_for_parent_key</p>
</li>
<li><p>insert into destination_child_table (…)<br /> values (:variable_for_parent_key, …)</p>
</li>
</ol>
<p>So, that are 3 SQL calls in PL/SQL and therefore also 3 context switches…</p>
<p>In 26ai this can be reduced to</p>
<ol>
<li><p>insert into destination_parent_table (…) values (<br /> select … from apex_collection where …<br /> ) returning new ID as :variable_for_parent_key</p>
</li>
<li><p>insert into destination_child_table (…)<br /> select :variable_for_parent_key, …<br /> from apex_collections<br /> where …</p>
</li>
</ol>
<p>So that is</p>
<ul>
<li><p>1 query less</p>
</li>
<li><p>1 context switch less</p>
</li>
<li><p>less data in PL/SQL =&gt; less memory usage</p>
</li>
</ul>
<p>Some code to illustrate this and expose some issues:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">drop</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">exists</span> src <span class="hljs-keyword">purge</span>;
<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> src (<span class="hljs-keyword">id</span> <span class="hljs-built_in">number</span>, val <span class="hljs-built_in">varchar2</span>(<span class="hljs-number">30</span>));

<span class="hljs-comment">-- 1) Multi record insert</span>
   <span class="hljs-comment">-- Very nice for creating test data or (initial) data loading.</span>
   <span class="hljs-comment">-- Probably less usefull in application development.</span>
   <span class="hljs-comment">-- Here it is used to populate a source table (to select from)</span>
<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> src (<span class="hljs-keyword">id</span>, val) <span class="hljs-keyword">values</span>
   (<span class="hljs-number">1</span>, <span class="hljs-string">'One'</span>),
   (<span class="hljs-number">2</span>, <span class="hljs-string">'Two'</span>),
   (<span class="hljs-number">3</span>, <span class="hljs-string">'Three'</span>);
<span class="hljs-keyword">commit</span>;

<span class="hljs-comment">/*
The new way of using subqueries when inserting:
There has always been
   insert into dest (col1, col2)
   select col_a, col_b from src;
It was previously not possible to add a returning clause onto this,
probably since that select potentially returns multiple records.
That fact results in having to do 2 sql-calls, in PL/SQL:
   1 to select the values into variables
   2 to do the insert using the "insert values"-way or working:
         select col_a, col_b
            into :param1, :param2
            from src
            where id = 1;
         insert into dest (col1, col2)
            values (:param1, :param2)
            returning id into :id;
But now, the values(...) can contain a select
*/</span>

<span class="hljs-comment">-- Testcase for the new functionality:</span>
<span class="hljs-keyword">drop</span> <span class="hljs-keyword">table</span> <span class="hljs-keyword">if</span> <span class="hljs-keyword">exists</span> dest <span class="hljs-keyword">purge</span>;
<span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> dest (<span class="hljs-keyword">id</span> <span class="hljs-built_in">number</span> <span class="hljs-keyword">GENERATED</span> <span class="hljs-keyword">ALWAYS</span> <span class="hljs-keyword">AS</span> <span class="hljs-keyword">IDENTITY</span>, src_id <span class="hljs-built_in">number</span>, val <span class="hljs-built_in">varchar2</span>(<span class="hljs-number">30</span>));

<span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> dest (src_id, val) <span class="hljs-keyword">values</span> (
   <span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, val <span class="hljs-keyword">from</span> src <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">1</span>
);
<span class="hljs-comment">-- Note that it has to be a single row query (or "ORA-01427: single-row subquery returns more than one row" is raised)</span>
<span class="hljs-comment">-- If you want to insert multiple rows (implying you don't need "returning") just use the old style insert-select.</span>
<span class="hljs-comment">-- This really only adds value when using "returning into":</span>

<span class="hljs-keyword">set</span> serveroutput <span class="hljs-keyword">on</span>
<span class="hljs-keyword">declare</span>
   dst_id <span class="hljs-built_in">number</span>;
<span class="hljs-keyword">begin</span>
   <span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> dest (src_id, val) <span class="hljs-keyword">values</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, val <span class="hljs-keyword">from</span> src <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">3</span>
   )
   <span class="hljs-keyword">returning</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">into</span> dst_id;
   dbms_output.put_line('New dest.id is: '||dst_id);

   <span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> dest (src_id, val) <span class="hljs-keyword">values</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, val <span class="hljs-keyword">from</span> src <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">2</span>
   )
   <span class="hljs-keyword">returning</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">into</span> dst_id;
   dbms_output.put_line('New dest.id is: '||dst_id);

   <span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> dest (src_id, val) <span class="hljs-keyword">values</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, val <span class="hljs-keyword">from</span> src <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">1</span>
   )
   <span class="hljs-keyword">returning</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">into</span> dst_id;
   dbms_output.put_line('New dest.id is: '||dst_id);

   <span class="hljs-keyword">insert</span> <span class="hljs-keyword">into</span> dest (src_id, val) <span class="hljs-keyword">values</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-keyword">id</span>, val <span class="hljs-keyword">from</span> src <span class="hljs-keyword">where</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">42</span> <span class="hljs-comment">--does not exist</span>
   )
   <span class="hljs-keyword">returning</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">into</span> dst_id;
   dbms_output.put_line('New dest.id is: '||dst_id);
<span class="hljs-keyword">end</span>;
/

<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> dest;
</code></pre>
<p>The caveat is that this returns</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1771162249013/2758f6ae-e42b-43c6-8ebd-556d74a8a036.png" alt class="image--center mx-auto" /></p>
<p>So, also when the subquery returns no records, the insert is performed without raising errors…</p>
<p>(Note that in the PL/SQL sample above, the returned PK-value is not used to insert a child record. This part has been intentionally left out since it is not relevent to the new 26ai functionality of the <code>insert</code> statement.)</p>
]]></content:encoded></item><item><title><![CDATA[Oracle23cFREE and ORDS/APEX on Windows using Podman]]></title><description><![CDATA[My company laptop is large enough (32Gb RAM) to host the containerized versions of the Oracle 23c FREE edition and ORDS. But it has also some limitations on installing and configuring things. I mainly ran into networking issues, which are probably re...]]></description><link>https://blog.jochenvandenbossche.dev/oracle23cfree-and-ordsapex-on-windows-using-podman</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/oracle23cfree-and-ordsapex-on-windows-using-podman</guid><category><![CDATA[Oracle]]></category><category><![CDATA[Apex]]></category><category><![CDATA[ords]]></category><category><![CDATA[podman]]></category><category><![CDATA[23c]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Wed, 18 Oct 2023 16:42:26 GMT</pubDate><content:encoded><![CDATA[<p>My company laptop is large enough (32Gb RAM) to host the containerized versions of the Oracle 23c FREE edition and ORDS. But it has also some limitations on installing and configuring things. I mainly ran into networking issues, which are probably related to the fact that I can't change firewall settings.</p>
<p>This is one of the reasons that led me to use Podman instead of Docker Desktop: multiple containers in a <em>pod</em> can talk to each other over <code>localhost:port</code>. The second reason is that <a target="_blank" href="https://container-registry.oracle.com/ords/ocr/ba/database">https://container-registry.oracle.com/ords/ocr/ba/database</a> also uses Podman in its examples.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<h2 id="heading-wsl2">WSL2</h2>
<p>You must have WSL2 (Windows Subsystem for Linux) installed. I'm not going to describe that here.</p>
<h2 id="heading-podman">Podman</h2>
<p>On my company laptop, the easiest way to install Podman was to run</p>
<pre><code class="lang-plaintext">winget install RedHat.Podman
</code></pre>
<p>from a DOS_box.</p>
<p>For those that want a GUI to go along with that, use Podman Desktop:</p>
<pre><code class="lang-plaintext"># Optional:
winget install RedHat.Podman-Desktop
</code></pre>
<p>While using that GUI, I found it <em>not</em> showing the things I created with it. Pull an image and it does not appear in the list of images. Create a container and it does not appear as a container. Only by going through a rather obscure menu could I refresh the GUI to show the created items. So I switched to the command line.</p>
<p>Being used to Portainer, I opted to use that instead to check on things. Portainer works with Podman, since Podman exposes the same API as Docker does. The only thing Portainer does not show is <em>Pod</em>s, but the <em>Containers</em> in a Pod are all individually available.</p>
<h1 id="heading-setting-up">Setting up</h1>
<p>After having installed Podman (just another Windows CLI program) you have to initialize the default <em>Podman Machine</em>.</p>
<p>This is another reason why I prefer Podman over Docker Desktop: The Podman Machine is a small WSL distribution. Docker Desktop, on the other hand, soiled my default WSL distribution (which happened to be an Oracle Linux) by adding Docker stuff into that.</p>
<p>So only once, you have to run</p>
<pre><code class="lang-plaintext">podman machine init
</code></pre>
<p>Running <code>wsl --list</code> shows that a new distribution called <code>podman-machine-default</code> was created.</p>
<p>If you'd like to add that little machine to Windows Terminal, use <code>wsl.exe -d podman-machine-default</code> as command line.</p>
<h1 id="heading-starting-the-podman-machine">Starting the Podman Machine</h1>
<p>While the command line <code>podman</code> program is always available, Podman (the system) needs the Podman Machine to be running (under WSL). So, after the <em>initialization</em>, whenever WSL was <em>shutdown</em> or whenever the Windows host was <em>rebooted</em>, you must start the Podman Machine using</p>
<pre><code class="lang-plaintext">podman machine start
</code></pre>
<h1 id="heading-preparations">Preparations</h1>
<h2 id="heading-pulling-images">Pulling images</h2>
<p>Images are like installation packages. While it is possible to create a container without <em>pulling</em> (downloading) the image it will be created from first, I like to pull images separately. Seeing that the one for Oracle 23c FREE is &gt; 9Gb is just one of the reasons.</p>
<pre><code class="lang-plaintext"># 1) The database
podman pull container-registry.oracle.com/database/free:latest
# 2) ORDS (including Apex and Database Actions etc)
podman pull container-registry.oracle.com/database/ords:latest
# 3) Optional: Portianer
podman pull docker.io/portainer/portainer-ce:latest
</code></pre>
<p>These downloads end up in the filesystem of the <code>podman-machine-default</code> WSL distribution.</p>
<p>You can list the available images using <code>podman images</code>.</p>
<h2 id="heading-volumes">Volumes</h2>
<p>A <em>Volume</em> is persisted storage for a <em>Container</em>. It is possible to destroy a Container, but keep its Volume and use that Volume for another Container (even one based on a different Image). It is possible to use ordinary Windows folders as persisted storage, but real Volumes have advantages: performance and security.</p>
<p>The database needs a Volume to store its data files (etc), and ORDS also needs a Volume to store its configuration. (Portainer also uses a Volume, but I didn't bother creating that beforehand.)</p>
<pre><code class="lang-plaintext">podman volume create orcl23c_data
podman volume create ords_config
</code></pre>
<p>At this moment those Volumes, <code>orcl23c_data</code> and <code>ords_config</code>, are nothing but names. (And maybe empty directories in the <code>podman-machine-default</code> WSL distribution.)</p>
<p>Use <code>podman volume ls</code> to list the volumes.</p>
<h2 id="heading-the-pod">The Pod</h2>
<p>A Pod is somewhere to put containers together. The main benefit, for me, is that containers in a Pod are like distinct programs on one computer:</p>
<ul>
<li><p>they can communicate with each other over <code>localhost:port</code>.</p>
</li>
<li><p>they share a single process space</p>
</li>
<li><p>...</p>
</li>
</ul>
<p>One drawback of a Podman Pod is that it is the Pod that describes which ports (used by the Container inside the Pod and determined by the images they are based on) are mapped/exposed to which ports on Windows.</p>
<p>It is not possible to add a port later on. (While it is possible to add another container to a pod, later in the pod's life).</p>
<p>From the documentation on <a target="_blank" href="https://container-registry.oracle.com/ords/ocr/ba/database/free">https://container-registry.oracle.com/ords/ocr/ba/database/free</a> and <a target="_blank" href="https://container-registry.oracle.com/ords/ocr/ba/database/ords">https://container-registry.oracle.com/ords/ocr/ba/database/ords</a> I know that only 1521 (DB Listener) and 8181 (http cleartext ORDS) are used. But sometime in the future, I'd like to talk https to ORDS, so I foresee already using 8443 as well.</p>
<p>I will be mapping ports in the pod (1521, 8181 and 8443) to the <em>same</em> ports at the Windows level since I have nothing else using those ports already.</p>
<p>The commend to create the Pod:</p>
<pre><code class="lang-plaintext">podman pod create --name orcl --infra-name pod_orcl_infra -p 1521:1521 -p 8181:8181 -p 8443:8443
</code></pre>
<h3 id="heading-explanation">Explanation</h3>
<p><code>podman pod create</code> The command to create a pod using Podman. Wow!</p>
<p><code>--name orcl</code> The pod will be named <code>orcl</code>.</p>
<p><code>--infra-name pod_orcl_infra</code> Inside every Pod, there is always an extra Container, called the Infrastructure Container, that takes care of pod-relative functionality. This option gives that Container a name, <code>pod_orcl_infra</code>, indicating that it is the Infra Container for Pod <code>orcl</code>. This is mainly handy when looking at Portainer since it will show all Containers individually, not as part of a Pod.</p>
<p><code>-p 1521:1521</code>, <code>-p 8181:8181</code>, <code>-p 8443:8443</code> Only those three ports will be exposed. I expose them on the same port numbers. Using for example <code>-p 8521:1521</code> would expose port <strong>1</strong>521 (from inside the container) as port <strong>8</strong>521 (in Windows). Using a Windows client, I would then connect using <code>sys/password@localhost:8521/FREE</code>, while inside the Container the listener is listening to port 1521 as usual.</p>
<h1 id="heading-the-db-container">The DB container</h1>
<p>The Containers are created using an Image (already pulled) and in our case, they will go in Pod (already created). They will use Volumes that also have already been created.</p>
<p>The ORDS container (which includes Apex) will be created later. First, let's create the DB Container:</p>
<pre><code class="lang-plaintext">podman run -d --pod orcl --name=orcl23c -v orcl23c_data:/opt/oracle/oradata --tz=Europe/Brussels container-registry.oracle.com/database/free:latest
</code></pre>
<h2 id="heading-explanation-1">Explanation</h2>
<p><code>podman run</code> The container will not only be created but also immediately run.</p>
<p><code>-d</code> run in detached mode. Otherwise, you wouldn't get the Windows command prompt back...</p>
<p><code>--pod orcl</code> Put this Container in a Pod, the one called <code>orcl</code>.</p>
<p><code>--name=orcl23c</code> Call this Container <code>orcl23c</code>. This name is needed to reference this container later on.</p>
<p><code>-v orcl23c_data:/opt/oracle/oradata</code> Mount the Volume called <code>orcl23c_data</code> (already created) as directory <code>/opt/oracle/oradata</code> in the Container. That directory will be created based on the Image.</p>
<p><code>--tz=Europe/Brussels</code> Use that timezone. Otherwise, file timestamps (and DB-time!) in the container would be different from the Windows time.</p>
<p><code>container-registry.oracle.com/database/free:latest</code> Use this Image to create the Container. So in the container registry of Oracle (located at <a target="_blank" href="http://container-registry.oracle.com">container-registry.oracle.com</a>) find Image <code>database/free</code> and get its <code>latest</code> version. In our case we have already pulled that image, no further download is needed.</p>
<h3 id="heading-some-things-that-are-not-in-there">Some things that are <em>not</em> in there:</h3>
<ul>
<li><p>I did not set a password (<code>ORACLE_PWD</code>).<br />  The container provides a way to that later (<code>setPassword.sh</code>). This way, there will be no environment variable exposing the password.<br />  Using <code>podman secret</code> did not work for me. Presumably, it is a Windows thing regarding quotes etc.</p>
</li>
<li><p>I did not set <code>ORACLE_CHARACTERSET</code>. The default of <code>AL32UTF8</code> suits me fine.</p>
</li>
<li><p>I did not add volumes for additional <em>startup</em> or <em>setup</em> scripts.</p>
</li>
</ul>
<h2 id="heading-verification">Verification</h2>
<p>The <code>podman run</code> command takes a surprisingly short amount of time to start the database: just a few seconds.</p>
<p>You can use another DOS prompt to execute <code>podman logs orcl23c</code>. This will show the logs written by the Container called <code>orcl23c</code>. Quite soon this will show</p>
<pre><code class="lang-plaintext">#########################
DATABASE IS READY TO USE!
#########################
</code></pre>
<p>This log also reveals that the instance is called <code>FREE</code> and that there is a PDB called <code>FREEPDB1</code>.</p>
<h3 id="heading-optional">Optional:</h3>
<p>From inside the Container, you can already connect to the database (remember that no password has been set yet) using the Bequath connection.</p>
<p>To do this, either create a new Windows Terminal profile with as command line <code>podman exec -it orcl23c bash</code>.</p>
<p>Or use that same command straight from the DOS command line.</p>
<p>This tells <code>podman</code> to start an interactive (<code>-i</code>) terminal (<code>-t</code>) (a TTY) to the <code>orcl23c</code> Container and launch the <code>bash</code> command in there. In other words: start the shell in that Container.</p>
<p>In that bash shell, you can now run <code>sqlplus / as sysdba</code>.</p>
<h2 id="heading-set-the-passwords">Set the passwords</h2>
<p>Oracle provides in the container a utility to set the passwords for <code>SYS</code>, <code>SYSTEM</code> and <code>PDBADMIN</code>. In the end, it does nothing more than</p>
<pre><code class="lang-plaintext">sqlplus / as sysdba
      ALTER USER SYS IDENTIFIED BY ...;
      ALTER USER SYSTEM IDENTIFIED BY ...;
      ALTER SESSION SET CONTAINER=FREEPDB1;
      ALTER USER PDBADMIN IDENTIFIED BY ...;
</code></pre>
<p>Which you could do in the bash shell shown above. But instead of running bash, you can tell Podman to run that script:</p>
<pre><code class="lang-plaintext">podman exec orcl23c ./setPassword.sh S€cr€tP4ssword
</code></pre>
<p>In other words: Dear <code>podman</code>, in Container <code>orcl23c</code>, please <code>exec</code>ute script <code>./setPassword.sh</code> which takes as its first parameter my new password: <code>S€cr€tP4ssword</code>. There is no <code>-it</code> in there since we're not interested in interactivity. The output is returned though.</p>
<h2 id="heading-connect">Connect</h2>
<p>At this moment it should be possible to use your favorite DB client (on your Windows machine) and connect to the database.</p>
<p>Using the following connection settings in SQL Developer</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697638996635/09c4f98d-30b5-43b2-8324-452895a35c44.png" alt class="image--center mx-auto" /></p>
<p>I can do</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697638937835/a5db6c83-a35f-4894-88ab-137e90891963.png" alt class="image--center mx-auto" /></p>
<p>Notice the lack of <code>from dual</code>, a new 23c feature.</p>
<p>I guess that <em>supposing I were able to open port 1521 on my laptop to the network</em>, remote machines would also be able to use my DB.</p>
<h1 id="heading-the-ords-container">The ORDS Container</h1>
<p>Which also includes setting up Apex in the DB!</p>
<h2 id="heading-preparation">Preparation</h2>
<p>This container requires setting up a <code>conn_string.txt</code> file that contains the connection string to the DB. The directory containing that file will be mounted as a volume. Next, the starting container will read that file, and if all goes well, it will delete the file.</p>
<p>Contrary to the prior volumes created for performance and persistence, this one is just for one-time usage. It is therefore okay to mount an ordinary <em>host folder</em>.</p>
<p>So make a new folder. Its location and name do not really matter but mine was <code>C:\Users\jvdboss1\Documents\Podman\ords_secrets</code>. You'll find that path later on in the Container creation command.</p>
<p>In that folder, I created the expected <code>conn_string.txt</code> file. And that file contained</p>
<pre><code class="lang-plaintext">CONN_STRING=sys/S€cr€tP4ssword@localhost:1521/FREEPDB1
</code></pre>
<p>The connection must be for <code>sys</code> since an <code>as sysdba</code> connection is used when installing Apex.</p>
<h2 id="heading-creating-the-container">Creating the container</h2>
<p>As explained on <a target="_blank" href="https://container-registry.oracle.com/ords/ocr/ba/database/ords">https://container-registry.oracle.com/ords/ocr/ba/database/ords</a> these are all the preparations needed.</p>
<pre><code class="lang-plaintext">podman run -d --pod orcl --name ords -v C:\Users\jvdboss1\Documents\Podman\ords_secrets:/opt/oracle/variables -v ords_config:/etc/ords/config/ container-registry.oracle.com/database/ords:latest
</code></pre>
<h3 id="heading-explanation-2">Explanation</h3>
<p><code>podman run</code> The container will not only be created but also immediately run.</p>
<p><code>-d</code> run in detached mode. Otherwise, you wouldn't get the Windows command prompt back...</p>
<p><code>--pod orcl</code> Put this Container in a Pod, the one called <code>orcl</code>. (Which now already has the <code>orcl23c</code> container running inside of it)</p>
<p><code>--name=ords</code> Call this Container <code>ords</code>. This name is needed to reference this container later on.</p>
<p><code>-v C:\Users\jvdboss1\Documents\Podman\ords_secrets:/opt/oracle/variables</code> Mount the Windows folder <code>C:\Users\jvdboss1\Documents\Podman\ords_secrets</code> as directory <code>/opt/oracle/variables</code> in the Container. That directory will be created based on the Image.</p>
<p><code>-v ords_config:/etc/ords/config/</code> Mount the Volume called <code>ords_config</code> (already created) as directory <code>/etc/ords/config/</code> in the Container. That directory will be created based on the Image.</p>
<p><code>container-registry.oracle.com/database/ords:latest</code> Use this Image to create the Container. So in the container registry of Oracle (located at <a target="_blank" href="http://container-registry.oracle.com">container-registry.oracle.com</a>) find Image <code>database/ords</code> and get its <code>latest</code> version. In our case we have already pulled that image, no further download is needed.</p>
<h2 id="heading-keeping-an-eye-on-things">Keeping an eye on things</h2>
<p>The installation of Apex takes quite some time, so it is nice to be able to see the output of it.</p>
<p>From a separate dos-prompt, run <code>podman logs -f ords</code>. The <code>-f</code> option makes sure you keep <em>following</em> the logs, not just output the current state of the log, once.</p>
<p>This will tell you</p>
<pre><code class="lang-plaintext">INFO : This container will start a service running ORDS 23.2.3 and APEX 23.1.0.
INFO : CONN_STRING has been found in the container variables file.
INFO : Database connection established.
INFO : Apex is not installed on your database.
INFO : Installing APEX on your DB please be patient.
INFO : You can check the logs by running the command below in a new terminal window:
        docker exec -it orcl tail -f /tmp/install_container.log
WARN : APEX can be installed remotely on PDBs, If you want to install it on a CDB,
       install it directly on the Database and not remotely.
</code></pre>
<p>So, it explains there is a command to follow what is being done for the Apex install. But the listed command needs some adapting: We're using Podman instead of Docker and our container is called <code>ords</code> instead of <code>orcl</code>. So,</p>
<pre><code class="lang-plaintext">podman exec -it orcl tail -f /tmp/install_container.log
</code></pre>
<p>can be used to tail the Apex installation. Like before, this command will use container <code>ords</code> to <code>exec</code>ute the <code>tail -f /tmp/install_container.log</code> command.</p>
<p>Eventually (after a couple of minutes) the tail will end, stating</p>
<pre><code class="lang-plaintext">Disconnected from Oracle Database 23c Free Release 23.0.0.0.0 - Develop, Learn, and Run for Free
Version 23.3.0.23.09
</code></pre>
<p>In the other DOS-terminal, where the container creation was running, more important information can be seen:</p>
<pre><code class="lang-plaintext">INFO : Use below login credentials to first time login to APEX service:
        Workspace: internal
        User:      ADMIN
        Password:  Welcome_1
</code></pre>
<p>And regarding ORDS (which is installed <em>after</em> Apex):</p>
<pre><code class="lang-plaintext">ORDS: Release 23.2 Production on Wed Oct 18 16:04:15 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /etc/ords/config/

The setting named: jdbc.InitialLimit was set to: 10 in configuration: default
INFO : Starting the ORDS services with the following database details:
INFO :   localhost:1521/FREEPDB1.

ORDS: Release 23.2 Production on Wed Oct 18 16:04:17 2023

Copyright (c) 2010, 2023, Oracle.

Configuration:
  /etc/ords/config/

2023-10-18T16:04:17.529Z INFO        HTTP and HTTP/2 cleartext listening on host: 0.0.0.0 port: 8181
2023-10-18T16:04:17.579Z INFO        The document root is serving static resources located in: /etc/ords/config/global/doc_root
</code></pre>
<p>And finally, you can surf to <a target="_blank" href="http://localhost:8181/ords/">http://localhost:8181/ords</a> to get</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697645613365/9ff0ff28-0e86-457e-aa1c-3ee8626b6c88.png" alt class="image--center mx-auto" /></p>
<p>or to <a target="_blank" href="http://localhost:8181/ords/apex">http://localhost:8181/ords/apex</a> to get</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1697645492149/1923b8dd-e9aa-4a5f-83c0-9216915f6bae.png" alt class="image--center mx-auto" /></p>
]]></content:encoded></item><item><title><![CDATA[Big Delete? Use Insert instead. #JoelKallmanDay]]></title><description><![CDATA[The client I'm contracting for has a transform schema in which data from multiple governmental sources is transformed into one data model. The result is that any single table contains data that originated from multiple sources. Therefore each record ...]]></description><link>https://blog.jochenvandenbossche.dev/big-delete-joelkallmanday</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/big-delete-joelkallmanday</guid><category><![CDATA[JoelKallmanDay]]></category><category><![CDATA[Delete]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[SQL]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Tue, 10 Oct 2023 22:00:09 GMT</pubDate><content:encoded><![CDATA[<p>The client I'm contracting for has a <code>transform</code> schema in which data from multiple governmental sources is transformed into one data model. The result is that any single table contains data that originated from multiple sources. Therefore each record has a <code>source_type_id</code> to indicate where the data came from.</p>
<p>Recently the client opted to no longer fetch and treat all those sources themselves but buy the same data from a commercial vendor that does all the cleansing and aligning among the official sources for us (and their other clients).</p>
<p>The result is that tables in the <code>transform</code> schema will have to keep only the data from the new source (it is already in there to be able to check its quality cfr the other sources), while the data from all those other sources needs to be removed.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">delete</span> <span class="hljs-keyword">from</span> transform.some_source_tb
<span class="hljs-keyword">where</span> source_type_id <span class="hljs-keyword">not</span> <span class="hljs-keyword">in</span> (<span class="hljs-number">20</span><span class="hljs-comment">/*new comercial source*/</span>,
                             <span class="hljs-number">1</span><span class="hljs-comment">/*private data*/</span>,
                             <span class="hljs-number">9</span><span class="hljs-comment">/*test data*/</span>)
</code></pre>
<p>That will do the trick. but since about 50% of the (pretty large!) tables will be removed, it is not very efficient. It is also going to leave a lot of storage space wasted (and the IT crowd is already complaining about how much we use...).</p>
<p>Instead, we came up with a much more complex way of working but with a much better result: The original table is truncated (<em>with</em> a drop of the old storage space) and only the data that needs to be kept gets written back into it.</p>
<p>The scenario in short:</p>
<ul>
<li><p>Create a new table with the same layout as the original one</p>
</li>
<li><p>Fill the new table with only the data you want to keep</p>
</li>
<li><p>Truncate the original table</p>
</li>
<li><p>Add the data from the temporary table back to the original one</p>
</li>
<li><p>Drop the temporary table</p>
</li>
</ul>
<p>In reality, there are some extra steps in between those to make sure things flow efficiently (read: fast) and data remains correct.</p>
<h1 id="heading-create-a-table-for-temporary-use">Create a table for temporary use</h1>
<p>One would assume that the first two steps can be combined into one single <code>create table as select</code> (CTAS). The reality is that we often run into a shortage of PGA while attempting this ...</p>
<p>But a CTAS that does not select anything does work:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> temp_tb <span class="hljs-keyword">as</span> <span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> some_source_tb <span class="hljs-keyword">WHERE</span> <span class="hljs-number">1</span>=<span class="hljs-number">2</span>;
</code></pre>
<h1 id="heading-fill-it-with-the-needed-data">Fill it with the needed data</h1>
<p>Instead of <em>deleting</em> data that can be <em>thrown out</em>, this step only copies (i.e. <em>selects</em>) the data that <em>must remain</em>. This is how a <code>delete</code> is turned into the first of a pair of <code>inserts.</code></p>
<p>The benefit of using CTAS would be that it performs a <em>direct path</em> insert to write the data. To achieve the same performance we use the <code>/*+ append*/</code> hint.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">insert</span> <span class="hljs-comment">/*+ append*/</span> <span class="hljs-keyword">into</span> temp_tb
<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> some_source_tb
<span class="hljs-keyword">where</span> source_type_id <span class="hljs-keyword">in</span> (<span class="hljs-number">20</span><span class="hljs-comment">/*new comercial source*/</span>,
                         <span class="hljs-number">1</span><span class="hljs-comment">/*private data*/</span>,
                         <span class="hljs-number">9</span><span class="hljs-comment">/*test data*/</span>);
<span class="hljs-keyword">commit</span>;
</code></pre>
<h1 id="heading-prepare-for-downtime">Prepare for downtime.</h1>
<p>As stated above, we'll truncate the original table. This implies that for some time no data will be available to end-users: downtime. To make this time as short as possible, prepare some things in advance. To make the insertion quick (and correct) we'll</p>
<ul>
<li><p><strong>disable constraints</strong><br />  You're inserting data that came out of the table, so it will still comply with ordinary constraints. (Only *table-*level constraints might get violated by a delete like this.)</p>
</li>
<li><p><strong>disable indexes</strong><br />  It's a big insert, better (quicker) to rebuild indexes after it has been completed.</p>
</li>
<li><p><strong>disable triggers</strong><br />  Not only to not spend time running the trigger code but also to keep the data intact: we have classic <em>last-modified-by-and-on</em>-fields and triggers to maintain them. It should be clear that since we are mimicking a <code>delete</code>, the last modification (meta)data should remain untouched.</p>
</li>
<li><p><strong>Allocate space</strong>: an extent large enough for all kept data<br />  This prevents having to extend the table over and over again while blocks of records are being written.</p>
</li>
</ul>
<h2 id="heading-disable-constraints">Disable constraints</h2>
<p>Run</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> constraint_name, constraint_type, <span class="hljs-keyword">status</span>, validated,
       search_condition_vc, index_name,
       <span class="hljs-string">'alter table some_source_tb disable constraint '</span>||constraint_name||<span class="hljs-string">';'</span> disable_stmt,
       <span class="hljs-string">'alter table some_source_tb enable constraint '</span>||constraint_name||<span class="hljs-string">';'</span> enable_stmt
<span class="hljs-keyword">from</span> user_constraints
<span class="hljs-keyword">where</span> table_name=<span class="hljs-string">'some_source_tb'</span>
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> constraint_type;
</code></pre>
<p>to list all constraints on the table, along with generated statements to disable and later re-enable them.</p>
<h2 id="heading-disable-indexes">Disable indexes</h2>
<p>Run</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> index_name, index_type, <span class="hljs-keyword">status</span>,
       domidx_status, domidx_opstatus, funcidx_status,
       <span class="hljs-string">'alter index '</span>||index_name||<span class="hljs-string">' unusable;'</span> disable_stmt,
       <span class="hljs-string">'alter index '</span>||index_name||<span class="hljs-string">' rebuild;'</span> enable_stmt
<span class="hljs-keyword">from</span> user_indexes
<span class="hljs-keyword">where</span> table_name=<span class="hljs-string">'some_source_tb'</span>
<span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> index_type;
</code></pre>
<p>to list all indexes on the table, along with generated statements to disable and later re-enable them.</p>
<h2 id="heading-disable-triggers">Disable triggers</h2>
<p>Run</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> trigger_name, description, <span class="hljs-keyword">status</span>, trigger_body,
       <span class="hljs-string">'alter trigger '</span>||trigger_name||<span class="hljs-string">' disable;'</span> disable_stmt,
       <span class="hljs-string">'alter trigger '</span>||trigger_name||<span class="hljs-string">' enable;'</span> enable_stmt,
<span class="hljs-keyword">from</span> user_triggers
<span class="hljs-keyword">where</span> table_name=<span class="hljs-string">'some_source_tb'</span>;
</code></pre>
<p>to list all triggers on the table, along with generated statements to disable and later re-enable them.</p>
<h2 id="heading-allocate-space">Allocate Space</h2>
<p>Since the <code>temp_tb</code> now contains <em>all</em> data that will eventually end up back in <code>some_source_tb</code>, we can use the <em>size</em> of <code>temp_tb</code> to pre-allocate space for <code>some_source_tb</code> right before the inserting starts.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> segment_name, segment_type, dbms_xplan.format_size(<span class="hljs-keyword">bytes</span>) tab_size
<span class="hljs-keyword">from</span> user_segments
<span class="hljs-keyword">where</span> segment_name=<span class="hljs-string">'temp_tb'</span>
  <span class="hljs-keyword">and</span> segment_type=<span class="hljs-string">'TABLE'</span>;
</code></pre>
<p>will return a tab_size like <code>123M</code> or <code>234G</code>. Round it up a bit and note that value down. It will later be used to set the <code>next_extent</code> storage attribute of <code>some_source_tb</code>. But once this operation is finished, you should set it back to its original value, so make sure to also note that <code>orig_next_extent</code> down before changing it:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> table_name, tablespace_name
     , dbms_xplan.format_size(initial_extent) initial_extent
     , dbms_xplan.format_size(next_extent) orig_next_extent
<span class="hljs-keyword">from</span> user_tables <span class="hljs-keyword">where</span> table_name=<span class="hljs-string">'some_source_tb'</span>;
</code></pre>
<h1 id="heading-truncate">Truncate</h1>
<p>After having prepared the statements above, it is time to get started. Once the table is truncated, there is no way back. To reduce downtime, this should be done in one continued succession.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">truncate</span> <span class="hljs-keyword">table</span> some_source_tb <span class="hljs-keyword">DROP</span> <span class="hljs-keyword">STORAGE</span>;
</code></pre>
<p>The <code>drop storage</code> makes sure that <em>all</em> the space allocated for the big table (of which only a part will remain) is released. The table is now sized only <code>initial_extent</code> seen above. The end-users have lost their data!</p>
<p>Next, set the <code>next_exent</code>. This way, once the <code>initial_extent</code> amount of space is filled, there is <em>one</em> resize operation that will add enough space for the <em>entire</em> dataset.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> some_source_tb <span class="hljs-keyword">storage</span> (<span class="hljs-keyword">next</span> &lt;TAB_SIZE&gt;);
</code></pre>
<h1 id="heading-disable-triggers-indexes-and-constraints">Disable triggers, indexes and constraints</h1>
<p>Right before inserting, it is time to disable all the functionality that could slow down the inserting (and/or corrupt the data).</p>
<pre><code class="lang-sql"><span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> some_source_tb <span class="hljs-keyword">disable</span> <span class="hljs-keyword">constraint</span> some_constraint;
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">index</span> some_index <span class="hljs-keyword">unusable</span>;
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">trigger</span> some_trigger <span class="hljs-keyword">disable</span>;
</code></pre>
<h1 id="heading-insert">Insert</h1>
<pre><code class="lang-sql"><span class="hljs-keyword">insert</span> <span class="hljs-comment">/*+ append parallel 4*/</span> <span class="hljs-keyword">into</span> some_source_tb
<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> temp_tb;
<span class="hljs-keyword">commit</span>;
</code></pre>
<p>The <code>append</code> hint makes sure a <em>direct path</em> load is performed. Depending on your CPU and storage solution the <code>parallel</code> hint might speed up things a bit further. (Adapt the value (<code>4</code>) to your situation or leave it out altogether and Oracle will choose the optimum degree of parallelism for you.)</p>
<p>If you are working on an instance that is not under <code>forcelogging</code> mode, you might want to add the <code>nologging</code> hint. But make sure to ask your DBA about that.</p>
<h1 id="heading-wrap-up">Wrap up</h1>
<p>After the commit above, the data is back, but the table can not be used yet: the triggers and constraints need to be re-enabled and to make things perform as usual again, the indexes need to be rebuilt and statistics gathered. Oh, and resetting the <code>next_extend</code>!</p>
<pre><code class="lang-sql"><span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> some_source_tb <span class="hljs-keyword">storage</span> (<span class="hljs-keyword">next</span> &lt;ORIG_NEXT_EXTENT&gt;);

<span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> some_source_tb <span class="hljs-keyword">enable</span> <span class="hljs-keyword">constraint</span> some_constraint;
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">trigger</span> some_trigger <span class="hljs-keyword">enable</span>;
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">index</span> some_index <span class="hljs-keyword">rebuild</span>;

<span class="hljs-keyword">begin</span>
   dbms_stats.gather_table_stats (
      ownname    =&gt; <span class="hljs-keyword">user</span>,
      tabname    =&gt; <span class="hljs-string">'SOME_SOURCE_TB'</span>,
      method_opt =&gt; <span class="hljs-string">'for all indexed columns'</span>,
      <span class="hljs-keyword">cascade</span>    =&gt; <span class="hljs-literal">TRUE</span> );
<span class="hljs-keyword">end</span>;
/
</code></pre>
<p>After this, the downtime is over. End users have their data back (well, only the new source). The only thing left to do is to drop the <code>temp_tb</code>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">drop</span> <span class="hljs-keyword">table</span> temp_db <span class="hljs-keyword">purge</span>;
</code></pre>
<h1 id="heading-hey-what-about-partition-exchange">Hey! What about partition exchange?</h1>
<p>Yes, instead of doing an <code>insert select</code> swapping partitions between the two tables would be a much quicker operation and there would be no need to change the extent size etc.</p>
<p>To be able to use <code>alter table tab1 exchange partition p1 with table tab2;</code> the <code>tab1</code> needs to have at least one partition while <code>tab2</code> must be not partitioned.</p>
<p>If <code>some_source_tb</code> is partitioned, you could use <code>exchange partition</code> as shown above, but you'll need one <code>temp_tb</code> per partition: the selection of the data to keep becomes a little bit more complex. To create the various <code>temp_tb</code> tables, you can use <code>create table temp_tb1 for exchange with table some_source_tb;</code> .</p>
<p>Alternatively, if <code>some_soure_tb</code> is not partitioned, then you'll need to create <code>temp_tb</code> with a partition.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">create</span> <span class="hljs-keyword">table</span> temp_tb
<span class="hljs-keyword">partition</span> <span class="hljs-keyword">by</span> <span class="hljs-keyword">range</span> (source_type_id) (
   <span class="hljs-keyword">partition</span> p_empty <span class="hljs-keyword">values</span> <span class="hljs-keyword">less</span> <span class="hljs-keyword">than</span> (<span class="hljs-number">0</span>),
   <span class="hljs-keyword">partition</span> p_keep <span class="hljs-keyword">values</span> <span class="hljs-keyword">less</span> <span class="hljs-keyword">than</span> (<span class="hljs-number">100</span>)
)
<span class="hljs-keyword">as</span>
<span class="hljs-keyword">select</span> * <span class="hljs-keyword">from</span> some_table_tb
wherer <span class="hljs-number">1</span>=<span class="hljs-number">2</span>;
</code></pre>
<p>and the swap becomes</p>
<pre><code class="lang-sql"><span class="hljs-keyword">alter</span> <span class="hljs-keyword">table</span> temp_tb <span class="hljs-keyword">exchange</span> <span class="hljs-keyword">partition</span> p_keep <span class="hljs-keyword">with</span> <span class="hljs-keyword">table</span> some_table_tb;
</code></pre>
<p>Exchange partition has features to take along indexes as well. You might want to use those features to save downtime: you can build the indexes on <code>temp_tb</code> long before <code>some_table_tb</code> table is truncated.</p>
<p>But I didn't try any of this, because there are no partitions in the DB I generally work with. The reason for that lies in a small but important caveat of Oracle Spatial: The <em>find nearest neighbour</em> function (<code>SDO_NN()</code> to find, for example, the 5 restaurants closest to your current location), will return 5 objects <em>from each partition</em> of the table you are searching in. (see <a target="_blank" href="https://docs.oracle.com/en/database/oracle/oracle-database/23/spatl/sdo_nn.html">https://docs.oracle.com/en/database/oracle/oracle-database/23/spatl/sdo_nn.html</a>)</p>
]]></content:encoded></item><item><title><![CDATA[Setting up Oracle SQLcl under GraalVM 17]]></title><description><![CDATA[Downloading
I downloaded the Windows version of GraalVM for Java 17: I Surfed to https://www.oracle.com/java/technologies/downloads/. There I chose the tab labeled GraalVM for JDK 17 (so not JDK 20, JDK 17 nor GraalVM for JDK 20)
The subpage indicate...]]></description><link>https://blog.jochenvandenbossche.dev/setting-up-oracle-sqlcl-under-graalvm-17</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/setting-up-oracle-sqlcl-under-graalvm-17</guid><category><![CDATA[sqlcl]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[GraalVM]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Sun, 06 Aug 2023 10:37:41 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-downloading">Downloading</h2>
<p>I downloaded the Windows version of GraalVM for Java 17: I Surfed to <a target="_blank" href="https://www.oracle.com/java/technologies/downloads/">https://www.oracle.com/java/technologies/downloads/</a>. There I chose the tab labeled <code>GraalVM for JDK 17</code> (so not <code>JDK 20</code>, <code>JDK 17</code> nor <code>GraalVM for JDK 20</code>)</p>
<p>The subpage indicates that<br /><em>GraalVM for JDK 17 binaries are free to use in production and free to redistribute, at no cost, under the GraalVM Free Terms and Conditions. (</em><a target="_blank" href="https://www.oracle.com/downloads/licenses/graal-free-license.html"><em>https://www.oracle.com/downloads/licenses/graal-free-license.html</em></a><em>)</em></p>
<p>The reason I chose 17 over 20, is that the 17 is a Long Term Release (until September 2024), while the 20 is not (only until September 2023, next month, when GraalVM for JDK 21 comes out. (It looks like 21 is the next LTS.))<br />Afterward, I also read somewhere that SQLcl does not support version 20 yet.</p>
<p>I choose <code>Windows</code> to get a download for that platform. That resulted in the file <code>graalvm-jdk-17_windows-x64_bin.zip</code>.</p>
<p>I also found out that I would be needing a plugin to run Javascript in GraalVM. (Since SQLcl can make use of that.)<br />I hopped over onto <a target="_blank" href="https://www.oracle.com/downloads/graalvm-downloads.html">https://www.oracle.com/downloads/graalvm-downloads.html</a>. There, I once again indicated version 17 for Windows and somewhere in the middle of the resulting page is a link to download the <code>JavaScript Runtime</code> That resulted in <code>js-installable-jdk-17-windows-amd64-23.0.1.jar</code>. After trying the installation a first time, I found out that the <code>JavaScript Runtime</code> depends on two other plugins: <code>ICU4J Plugin</code> and <code>Tregex Plugin</code>. Downloading these (from that same page as the first plugin) resulted in <code>icu4j-installable-jdk-17-windows-amd64-23.0.1.jar</code> and <code>regex-installable-jdk-17-windows-amd64-23.0.1.jar</code>.</p>
<h2 id="heading-installing-graalvm">Installing GraalVM</h2>
<p>...boils down to unzipping the zip somewhere, adding its bin directory to the <code>PATH</code> and setting the main directory as <code>JAVA_HOME</code>. The zip contains one folder which is best preserved in its entirety (including its name). So, I made folder <code>C:\pf\GraalVM</code> and dragged <code>graalvm-jdk-17.0.8+9.1</code> from the zip into there, resulting in <code>C:\pf\GraalVM\graalvm-jdk-17.0.8+9.1\bin</code> (and others).</p>
<p>The plugins were still in my downloads folder: <code>C:\Users\jvdboss1\Downloads*-installable-jdk-17-windows-amd64-23.0.1.jar</code> They need to be installed into Graal using its <code>gu</code> command (GraalVM Component Updater), which resides in its <code>bin</code>-folder:</p>
<pre><code class="lang-powershell"><span class="hljs-built_in">cd</span> C:\pf\GraalVM\graalvm<span class="hljs-literal">-jdk</span><span class="hljs-literal">-17</span>.<span class="hljs-number">0.8</span>+<span class="hljs-number">9.1</span>\bin
<span class="hljs-built_in">gu</span> <span class="hljs-literal">-L</span> install <span class="hljs-string">"C:\Users\jvdboss1\Downloads\icu4j-installable-jdk-17-windows-amd64-23.0.1.jar"</span>
<span class="hljs-built_in">gu</span> <span class="hljs-literal">-L</span> install <span class="hljs-string">"C:\Users\jvdboss1\Downloads\regex-installable-jdk-17-windows-amd64-23.0.1.jar"</span>
<span class="hljs-built_in">gu</span> <span class="hljs-literal">-L</span> install <span class="hljs-string">"C:\Users\jvdboss1\Downloads\js-installable-jdk-17-windows-amd64-23.0.1.jar"</span>
</code></pre>
<p>Running <code>gu list</code> confirms that I have GraalVM, icu4j, regex and js installed, all version 23.0.1.</p>
<p>At this moment, from that <code>bin</code> directory, running <code>java -version</code> returned <code>Java(TM) SE Runtime Environment Oracle GraalVM 17.0.8+9.1 (build 17.0.8+9-LTS-jvmci-23.0-b14)</code> OK :)</p>
<h2 id="heading-settings">Settings</h2>
<p>Running <code>echo %path%</code> or just <code>path</code> showed that another Java was there: <code>C:\ProgramData\Oracle\Java\javapath</code>. This is probably something installed by my employer (I don't write any Java programs for my job). It turns out to be a shortcut-folder ("junction") and it contains (only) <code>java.exe</code>, <code>javaw.exe</code> and <code>javaws.exe</code>...<br />The junction points to its sibling: <code>C:\ProgramData\Oracle\Java\javapath_target_474953</code><br />I decided to leave that in the <code>PATH</code>, but put the GraalVM <code>bin</code>-folder first. So now there is <code>...;C:\pf\GraalVM\graalvm-jdk-17.0.8+9.1\bin;C:\ProgramData\Oracle\Java\javapath;...</code></p>
<p>While being in the Environment Variables GUI of Windows, I also set <code>JAVA_HOME</code> and gave it the value <code>C:\pf\GraalVM\graalvm-jdk-17.0.8+9.1</code>.</p>
<p>After restarting the command line prompt <code>echo %path%</code> and <code>echo %java_home%</code> confirm that these settings are there. From the default directory, running <code>java -version</code> tells me that GraalVM 17 is being used :)</p>
<p>I already had a SQLcl installation. I went to its <code>bin</code> directory and started it:<br /><code>cd C:\pf\sqlcl\bin sql /nolog</code><br />And it still started. :) Doing <code>show java</code> confirmed that it had picked up the <code>JAVA_HOME</code> setting.</p>
<h2 id="heading-upgrading-sqlcl">Upgrading SQLcl</h2>
<p>Installing a new version of SQLcl is also just about unzipping a zip. From <a target="_blank" href="https://www.oracle.com/database/sqldeveloper/technologies/sqlcl/download/">https://www.oracle.com/database/sqldeveloper/technologies/sqlcl/download/</a> I downloaded <code>sqlcl-23.2.0.178.1027.zip</code> After making sure no more SQLcl was running and all command windows were no longer in the folder, I renamed my existing <code>C:\pf\sqlcl</code> to <code>C:\pf\sqlcl_22_3</code> (since that was the old version I had).<br />Next, I dragged the <code>sqlcl</code> folder from the zip to my <code>C:\pf</code> folder.</p>
<p>And that is how you upgrade SQLcl :)</p>
<p>You can always use <a target="_blank" href="https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip">https://download.oracle.com/otn_software/java/sqldeveloper/sqlcl-latest.zip</a> to directly get the <em>latest</em> version, whatever it is. Ideal for DevOps.</p>
<p>SQLcl falls under the <a target="_blank" href="https://www.oracle.com/downloads/licenses/oracle-free-license.html">Oracle Free License</a></p>
<p>I already had <code>C:\pf\sqlcl\bin</code> in my path, so running <code>sql /nolog</code> from anywhere starts SQLcl :)</p>
<h2 id="heading-loginsql">login.sql</h2>
<p>While SQLcl works fine out of the box, obviously, it has many settings which you may want to apply each time. To do that, you can create a <code>glogin.sql</code> or <code>login.sql</code> script, just like for SQL*Plus.</p>
<p>Running <code>show login</code> shows you where that script can reside so that SQLcl picks it up. In my case (having an old fat client installed too) it was:</p>
<ul>
<li><p><code>C:\Oracle\product\12.2.0\client_1\sqlplus\admin\glogin.sql</code></p>
</li>
<li><p><code>.\login.sql</code></p>
</li>
<li><p><code>C:\Oracle\product\12.2.0\client_1\dbs\login.sql</code></p>
</li>
</ul>
<p>Notice that the 2nd entry implies that you can have different <code>login.sql</code> scripts per project/environment/..., just by starting SQLcl from a certain directory.</p>
<p>I set up the statusbar, syntax highlighting and DDL generation config. I relaxed the history filter a bit, confirmed the sqlformat (query output), allow empty lines in SQL-scripts and set up the Trivadis Formatter.<br />Also very important: I explicitly turn autocommit off, but exitcommit on.</p>
<p>That results in the following <code>login.sql</code> script</p>
<pre><code class="lang-plaintext">SET statusbar ON
SET statusbar editmode linecol java cwd encoding username dbid txn timing

SET highlighting ON
--SET highlighting DEFAULT RESET --duh
SET highlighting COMMENT foreground GREEN
--SET highlighting COMMENT background DEFAULT --same as default
SET highlighting STRING foreground MAGENTA
--SET highlighting STRING background DEFAULT --same as default
SET highlighting NUMBER foreground MAGENTA
--SET highlighting NUMBER background DEFAULT --same as default
--SET highlighting PUNCTUATION foreground DEFAULT --same as default
--SET highlighting PUNCTUATION background DEFAULT --same as default
SET highlighting KEYWORD foreground YELLOW
--SET highlighting KEYWORD background DEFAULT --same as default
SET highlighting IDENTIFIER foreground CYAN
--SET highlighting IDENTIFIER background DEFAULT --same as default
--SET highlighting ERROR foreground RED     --same as default
--SET highlighting ERROR background DEFAULT --same as default
--SET highlighting ERROR underline ON       --same as default

SET ddl CONSTRAINTS_AS_ALTER off
SET ddl FORCE off
SET ddl SEGMENT_ATTRIBUTES off
SET ddl SIZE_BYTE_KEYWORD off
SET ddl STORAGE off

-- Include connect and show in the history
-- Note that connect implies a security risk when passords are entered in the connect statement.
-- But normally, only stored connections are used.
-- When exceptionally making a manual connection, don't include the password in the connect statment.
-- Provide it separately. SQLcl will ask for it.
SET history FILTER history clear

SET sqlformat ANSICONSOLE
--SET sqlformet JSON-FORMATTED
--SET sqlformat delimited ; " "

-- Allow empty lines in SQL statements (Even catproc.sql has them)
SET sqlblanklines ON

-- Use command tvdformat to call the Trivadis formatter
script C:\Repos\Trivadis-PLSQL-Formatter-Settings\sqlcl\format.js --register
-- Set up tformat alias to call tvdformat with our own settings to format the buffer
alias tformat=tvdformat * xml=C:\Users\jvdboss1\Documents\Modified_trivadis_formatter.xml arbori=C:\Repos\Trivadis-PLSQL-Formatter-Settings\settings\sql_developer\trivadis_custom_format.arbori ;

SET autocommit OFF
SET exitcommit ON
</code></pre>
]]></content:encoded></item><item><title><![CDATA[Colored query results in SQLcl]]></title><description><![CDATA[For quite a while now, SQLcl has supported coloring query results.
To have colored text in a field, the magic delimiters @| and |@ need to be used. After the starting delimiter (@|) you need to provide one or more directives to color the text that fo...]]></description><link>https://blog.jochenvandenbossche.dev/colored-query-results-in-sqlcl</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/colored-query-results-in-sqlcl</guid><category><![CDATA[sqlcl]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Sun, 06 Aug 2023 09:55:21 GMT</pubDate><content:encoded><![CDATA[<p>For quite a while now, SQLcl has supported coloring query results.</p>
<p>To have colored text in a field, the magic delimiters <code>@|</code> and <code>|@</code> need to be used. After the starting delimiter (<code>@|</code>) you need to provide one or more directives to color the text that follows (after a separating space) and close the colored text with <code>|@</code>.</p>
<p>For example</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691246814864/58501244-6b35-4e72-925f-3f0edfcc675c.png" alt class="image--center mx-auto" /></p>
<p>There are only a few colors that can be used: <code>black</code>, <code>green</code>, <code>yellow</code>, <code>blue</code>, <code>magenta</code>, <code>cyan</code> and <code>white</code>.</p>
<p>As stated above, multiple directives can be used. This is necessary to control the display attributes: blinking (fast, slow or off), concealing (on/off), intensity (bold, faint or no change), italic (on/off), negative (on/off).</p>
<p>To get a blinking, italic, red field:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">select</span> <span class="hljs-string">'@|BLINK_FAST,red,italic This is red data|@'</span> colored_text <span class="hljs-keyword">from</span> dual;
</code></pre>
<p>The order of the directives is not important, except for directives of the same kind: From 2 or more colors, only the last one will be in effect. <code>BLINK_FAST</code> followed by <code>BLINK_OFF</code> will result in non-blinking text.</p>
<p>The allowed attributes are</p>
<ul>
<li><p>blink:</p>
<ul>
<li><p><code>BLINK_FAST</code></p>
</li>
<li><p><code>BLINK_SLOW</code></p>
</li>
<li><p><code>BLINK_OFF</code></p>
</li>
</ul>
</li>
<li><p>conceal:</p>
<ul>
<li><p><code>CONCEAL_ON</code> (results in hidden letters, i.e. only the background is visible)</p>
</li>
<li><p><code>CONCEAL_OFF</code></p>
</li>
</ul>
</li>
<li><p>intensity</p>
<ul>
<li><p><code>INTENSITY_BOLD</code></p>
</li>
<li><p><code>INTENSITY_FAINT</code></p>
</li>
</ul>
</li>
<li><p>italic</p>
<ul>
<li><code>ITALIC</code></li>
</ul>
</li>
<li><p>negative</p>
<ul>
<li><p><code>NEGATIVE_OFF</code></p>
</li>
<li><p><code>NEGATIVE_ON</code></p>
</li>
</ul>
</li>
<li><p>reset</p>
<ul>
<li><code>RESET</code> (undoos all the settings to the left of it. Since the colors are set per field, it should never be necessary)</li>
</ul>
</li>
</ul>
<p>Instead of using just one (foreground) color, it is of course possible to set a background color too: <code>bg_black</code>, <code>bg_green</code>, <code>bg_yellow</code>, <code>bg_blue</code>, <code>bg_magenta</code>, <code>bg_cyan</code> and <code>bg_white</code>. When setting both foreground and background colors it is best to use <code>fg_black</code>, <code>fg_green</code>, <code>fg_yellow</code>, <code>fg_blue</code>, <code>fg_magenta</code>, <code>fg_cyan</code> and <code>fg_white</code> instead of the version without the <code>fg_</code> prefix. Since the two notations for the foreground colors are equivalent, the following happens:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691248134301/45f38aad-435f-460b-a832-70363e1b4881.png" alt class="image--center mx-auto" /></p>
<p>Hey Jochen, that yellow looks kinda green! Yup, that is because while SQLcl tells the terminal to show yellow, it is still up to the terminal to decide what that will actually look like. I'm using a "Solarized Dark"-setting in Windows Terminal, hence the strange colors.</p>
<p>There are other things that simply don't work in my terminal: slow and fast blinking texts blink at exactly the same rate. Bold turns the text gray, whatever its original color was...</p>
<p>To find out what <em>combinations</em> give what effect, it is possible to come up with a few queries:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">with</span>
   colors <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'black'</span> <span class="hljs-keyword">value</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'green'</span>       <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'yellow'</span>      <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'blue'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'magenta'</span>     <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'cyan'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'white'</span>       <span class="hljs-keyword">from</span> dual
   )
 , blink <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_FAST'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_OFF'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_SLOW'</span>         <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                   <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , conceal <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'CONCEAL_OFF'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'CONCEAL_ON'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                    <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , intensity <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'INTENSITY_BOLD'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'INTENSITY_FAINT'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                       <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , italic <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'ITALIC'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>               <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , negative <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'NEGATIVE_OFF'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'NEGATIVE_ON'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                     <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
<span class="hljs-comment">/* , reset as (
    select 'RESET' setting from dual union all
    select ''              from dual --not used
RESET removes all attributes (italic, blinking,...) to the left of it, leaving only those that follow
)*/</span>
 , underline <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE_DOUBLE'</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE_OFF'</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span> <span class="hljs-keyword">from</span> dual <span class="hljs-comment">/*no attribute*/</span>
   )
<span class="hljs-keyword">select</span> <span class="hljs-keyword">lpad</span>(c.value, <span class="hljs-number">7</span>, <span class="hljs-string">' '</span>)
       || nvl2(bl.setting, <span class="hljs-string">','</span> || bl.setting, bl.setting)
       || nvl2(co.setting, <span class="hljs-string">','</span> || co.setting, co.setting)
       || nvl2(iy.setting, <span class="hljs-string">','</span> || iy.setting, iy.setting)
       || nvl2(it.setting, <span class="hljs-string">','</span> || it.setting, it.setting)
       || nvl2(ne.setting, <span class="hljs-string">','</span> || ne.setting, ne.setting)
       || nvl2(un.setting, <span class="hljs-string">','</span> || un.setting, un.setting) <span class="hljs-keyword">as</span> color_setting
     , <span class="hljs-string">'@|'</span> || c.value
       || nvl2(bl.setting, <span class="hljs-string">','</span> || bl.setting, bl.setting)
       || nvl2(co.setting, <span class="hljs-string">','</span> || co.setting, co.setting)
       || nvl2(iy.setting, <span class="hljs-string">','</span> || iy.setting, iy.setting)
       || nvl2(it.setting, <span class="hljs-string">','</span> || it.setting, it.setting)
       || nvl2(ne.setting, <span class="hljs-string">','</span> || ne.setting, ne.setting)
       || nvl2(un.setting, <span class="hljs-string">','</span> || un.setting, un.setting)
       || <span class="hljs-string">' Test Me|@'</span> <span class="hljs-keyword">as</span> show_color
  <span class="hljs-keyword">from</span>      colors    c
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> blink     bl
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> conceal   co
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> intensity iy
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> italic    it
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> negative  ne
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> underline un
 <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> c.value <span class="hljs-keyword">desc</span> <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>
        , bl.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, co.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>
        , iy.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, it.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>
        , ne.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, un.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>;
</code></pre>
<p>The query above gives all (foreground) colors, combined with all attributes. This turns out to result in 6048 combinations.</p>
<p>It can be extended to also use foreground and background colors:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">with</span>
   foreground <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_black'</span> <span class="hljs-keyword">value</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_green'</span>       <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_yellow'</span>      <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_blue'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_magenta'</span>     <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_cyan'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'fg_white'</span>       <span class="hljs-keyword">from</span> dual
   )
 , background <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_black'</span> <span class="hljs-keyword">value</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_green'</span>       <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_yellow'</span>      <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_blue'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_magenta'</span>     <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_cyan'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'bg_white'</span>       <span class="hljs-keyword">from</span> dual
   )
 , blink <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_FAST'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_OFF'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'BLINK_SLOW'</span>         <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                   <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , conceal <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'CONCEAL_OFF'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'CONCEAL_ON'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                    <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , intensity <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'INTENSITY_BOLD'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'INTENSITY_FAINT'</span>        <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                       <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , italic <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'ITALIC'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>               <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
 , negative <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'NEGATIVE_OFF'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'NEGATIVE_ON'</span>          <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span>                     <span class="hljs-keyword">from</span> dual <span class="hljs-comment">--not used</span>
   )
<span class="hljs-comment">/* , reset as (
    select 'RESET' setting from dual union all
    select ''              from dual --not used
RESET removes all attributes (italic, blinking,...) to the left of it, leaving only those that follow
)*/</span>
 , underline <span class="hljs-keyword">as</span> (
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE'</span> setting <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE_DOUBLE'</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">'UNDERLINE_OFF'</span> <span class="hljs-keyword">from</span> dual <span class="hljs-keyword">union</span> <span class="hljs-keyword">all</span>
      <span class="hljs-keyword">select</span> <span class="hljs-string">''</span> <span class="hljs-keyword">from</span> dual <span class="hljs-comment">/*no attribute*/</span>
   )
<span class="hljs-keyword">select</span> <span class="hljs-keyword">lpad</span>(b.value || <span class="hljs-string">','</span> || f.value, <span class="hljs-number">21</span>, <span class="hljs-string">' '</span>)
       || nvl2(bl.setting, <span class="hljs-string">','</span> || bl.setting, bl.setting)
       || nvl2(co.setting, <span class="hljs-string">','</span> || co.setting, co.setting)
       || nvl2(iy.setting, <span class="hljs-string">','</span> || iy.setting, iy.setting)
       || nvl2(it.setting, <span class="hljs-string">','</span> || it.setting, it.setting)
       || nvl2(ne.setting, <span class="hljs-string">','</span> || ne.setting, ne.setting)
       || nvl2(un.setting, <span class="hljs-string">','</span> || un.setting, un.setting) <span class="hljs-keyword">as</span> color_setting
     , <span class="hljs-string">'@|'</span> || b.value || <span class="hljs-string">','</span> || f.value
       || nvl2(bl.setting, <span class="hljs-string">','</span> || bl.setting, bl.setting)
       || nvl2(co.setting, <span class="hljs-string">','</span> || co.setting, co.setting)
       || nvl2(iy.setting, <span class="hljs-string">','</span> || iy.setting, iy.setting)
       || nvl2(it.setting, <span class="hljs-string">','</span> || it.setting, it.setting)
       || nvl2(ne.setting, <span class="hljs-string">','</span> || ne.setting, ne.setting)
       || nvl2(un.setting, <span class="hljs-string">','</span> || un.setting, un.setting)
       || <span class="hljs-string">' Test Me|@'</span> <span class="hljs-keyword">as</span> show_color
  <span class="hljs-keyword">from</span>      foreground f
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> background b
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> blink      bl
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> conceal    co
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> intensity  iy
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> italic     it
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> negative   ne
 <span class="hljs-keyword">cross</span> <span class="hljs-keyword">join</span> underline  un
 <span class="hljs-comment">--where f.vlaue != b.value --same background and foreground color sounds useless, but with attributes set it just might display something</span>
 <span class="hljs-keyword">order</span> <span class="hljs-keyword">by</span> f.value <span class="hljs-keyword">asc</span> <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, b.value <span class="hljs-keyword">desc</span> <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span> <span class="hljs-comment">--desc+asc so the equal bg and fg is not first.</span>
        , bl.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, co.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>
        , iy.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, it.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>
        , ne.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>, un.setting <span class="hljs-keyword">nulls</span> <span class="hljs-keyword">first</span>;
</code></pre>
<p>This gives 42336 combinations, but many of them are useless or look exactly the same as other combinations.</p>
<p>It is possible to use multiple colors in one field:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691312726144/7c8c0ce1-9f98-4192-aa0a-e34c25fa6f48.png" alt class="image--center mx-auto" /></p>
<p>Coloring can be used to dynamically highlight values:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1691313080618/74a33441-365b-4c65-b1ac-b7ba5cb84b06.png" alt class="image--center mx-auto" /></p>
<p>Remember that the ANSI color codes (that is what the <code>@|</code> is inserting to the string) can only be added to string values.</p>
<p>Note: Generic highlighting (for all queries, without writing <code>@|</code> in them) is better achieved using<br /><code>set sqlformat ansiconsole -config=highlight.json</code><br />but that is a completely separate topic.</p>
<h3 id="heading-links">Links</h3>
<p>The Trivadis Formatter and instructions on how to install it can be found at<br /><a target="_blank" href="https://github.com/Trivadis/plsql-formatter-settings">https://github.com/Trivadis/plsql-formatter-settings</a></p>
<p><em>That</em> Jeff Smith's blog post regarding SQLcl:<br /><a target="_blank" href="https://www.thatjeffsmith.com/archive/tag/sqlcl/">https://www.thatjeffsmith.com/archive/tag/sqlcl/</a><br />There is no better source than the source itself!</p>
]]></content:encoded></item><item><title><![CDATA[Website down]]></title><description><![CDATA[Someone told me my website was down. He was probably the only one using anything there: it isn't anything critical. That is why up to then I was unconcerned that it was running on an Always Free instance that was never updated since it was set up.
Bu...]]></description><link>https://blog.jochenvandenbossche.dev/website-down</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/website-down</guid><category><![CDATA[nginx]]></category><category><![CDATA[OCI]]></category><category><![CDATA[redirections]]></category><category><![CDATA[SHTS]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Sun, 26 Feb 2023 18:17:33 GMT</pubDate><content:encoded><![CDATA[<p>Someone told me my website was down. He was probably the only one using anything there: it isn't anything critical. That is why up to then I was unconcerned that it was running on an Always Free instance that was never updated since it was set up.</p>
<p>But now it is throwing connection refused errors. (PS this blog is a subdomain of that website, but <code>blog.jochenvandenbossche.dev</code> is just a <code>CNAME</code> DNS-record pointing to <code>hashnode.network</code>, so that still works.)</p>
<p>I kind of guessed the Let's Encrypt certificate wasn't renewed for some reason. I can still connect with SSH etc. When I tried to force a refresh from the command line, there was some kind of message regarding an outdated version of OpenSSL (the python package). When I tried to update that, I noticed all of this was still using Python 2.7... That is when I decided to start a new webserver from scratch.</p>
<hr />
<p>So, I stopped an instance that I wasn't really using and spun up a new one. Apparently, the Always free instances are Ampères these days? The 6GB of RAM won't hurt. I created some users (one in my name, one called <code>webserver</code>).</p>
<p>I installed NginX, according to <a target="_blank" href="https://docs.oracle.com/en/learn/oracle-linux-nginx/index.html">Install the NginX webserver in Oracle Linux (docs.oracle.com)</a> but diverted a bit to also apply the security settings described in <a target="_blank" href="https://www.getpagespeed.com/server-setup/nginx-and-php-fpm-what-my-permissions-should-be">NginX and PHP-FPM what my permission should be (www.getpagespeed.com)</a>.</p>
<hr />
<p>I added a separate <code>/etc/nginx/conf.d/jochenvandenbossche.dev.conf</code>, setting up a separate server to handle requests for <code>dev.jochenvandenbossche.dev</code>, <code>www.jochenvandenbossche.dev</code> and <code>jochenvandenbossche.dev</code>. My DNS was already pointing <code>www.jochenvandenbossche.dev</code> and <code>jochenvandenbossche.dev</code> to the old server (the one that started refusing connections recently); I added a new <code>A</code>-record pointing <code>dev.jochenvandenbossche.dev</code> to the public IP address of the newly created Ampère.</p>
<hr />
<p>From the server itself, <code>curl 127.0.0.1</code> and <code>curl 141.*.*.*</code> (the public IP) both resulted in the default NginX page, as expected: the separate server only serves domain name-based requests. And, lo and behold, <code>curl dev.jochenvandenbossche.dev</code> resulted in the separate little <code>index.html</code> that I set up. great!</p>
<p>On my laptop, I have WSL running an Ubuntu. Doing <code>curl dev.jochenvandenbossche.dev</code> from there also resulted in the expected html. Nice, everything works! Joy, joy joy!</p>
<p>So eventually I opened a browser (<a target="_blank" href="https://vivaldibrowser.com">Vivaldi</a> in my case) to surf to <code>http://dev.vandenbossche.dev</code> and I got a connection refused error. Bummer.</p>
<hr />
<p>At first, I thought it was a DNS problem. Doing an <code>nslookup</code> from both Ubuntu (under WSL) and a command prompt indeed showed my ISPs DNS being used from Ubuntu and <code>Google.dns</code> from Windows. But both returned the same correct IP address.</p>
<p>After calling out on Reddit (see <a target="_blank" href="https://www.reddit.com/r/oraclecloud/comments/11ck3pg/curl_works_browsers_dont_ol87nginx/?utm_source=share&amp;utm_medium=web2x&amp;context=3">this topic</a>) it turned out to (<em>probably</em>) be SHTS, a browser (+webservder) security feature. The <code>Network</code> tab of the <code>Developers Console</code> in Chrome showed my HTTP request resulting in a <code>307 Internal Rederect</code> to an HTTP<em>S</em> request...</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677434569904/d7da7ea0-54d9-4310-8a16-f23eecec2855.png" alt class="image--center mx-auto" /></p>
<p>And since nothing regarding HTTPS is set up on that NginX yet, it will be blocked.</p>
<hr />
<p>Opening the <code>chrome://net-internals/#hsts</code> "page" and querying <code>jochenvandenbossche.dev</code> (the top-level domain) shows that SHTS is being applied, <em>including for subdomains</em>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1677435746110/31ffda37-d77a-4bfc-87d6-14f43d0adfe1.png" alt class="image--center mx-auto" /></p>
<p>So, while the old server itself (<code>jochenvandenbossche.dev</code>) can't serve anything anymore, it is still capable of requiring the usage of HTTP<em>S</em> on all its subdomains, even on <code>dev.jochenvandenbossche.dev</code>, which didn't exist when it was set up.</p>
<p>So, now I hope that when I set up Let's Encrypt it will take into account the other two domains as well. I guess/hope that changing the DNS-records for the existing domains (pointing them to the new server) should do the trick.</p>
]]></content:encoded></item><item><title><![CDATA[So... So you think you can sort?]]></title><description><![CDATA[Like with many articles, this one found its origin in an application bug. A developer came to me saying that the ORDER BY in his SQL gave incorrect results. After some investigation, this article was the result.
ORDER BY is so easy to add at the end ...]]></description><link>https://blog.jochenvandenbossche.dev/so-so-you-think-you-can-sort</link><guid isPermaLink="true">https://blog.jochenvandenbossche.dev/so-so-you-think-you-can-sort</guid><category><![CDATA[oracle-sql]]></category><category><![CDATA[order by]]></category><dc:creator><![CDATA[Jochen Van den Bossche]]></dc:creator><pubDate>Sun, 08 Jan 2023 13:25:13 GMT</pubDate><content:encoded><![CDATA[<p>Like with many articles, this one found its origin in an application bug. A developer came to me saying that the <code>ORDER BY</code> in his <code>SQL</code> gave incorrect results. After some investigation, this article was the result.</p>
<p><code>ORDER BY</code> is so easy to add at the end of a query. And I expect you also know about <code>ASC</code> end <code>DESC</code>. Good, but that is not all there is to say about sorting (in Oracle).</p>
<p>Do you know that there is a session parameter that influences the result of an <code>ORDER BY</code> enormously? Since it is a <em>session</em> parameter, you as a developer can <em>not</em> know what value the user will have set it to (knowingly, but usually quite unknowingly). Therefore you, as a developer, should <em>set</em> this parameter whenever you write code that depends on the sort order of a query. Unless you want two distinct runs of the same code to yield different results. Or unless you want the code that behaves perfectly in your environment to crash on the environment of your colleague / the tester / the code reviewer / production / the end user / ...</p>
<p>The parameter in question is <code>NLS_SORT</code>. Note that, according to the Oracle documentation, <code>NLS_SORT</code> "<em>is derived from</em> <code>NLS_LANGUAGE</code>". What this means is that whenever you change <code>NLS_LANGUAGE</code>, you will also have changed <code>NLS_SORT</code>. Therefore make sure to set <code>NLS_LANGUAGE</code> <em>first</em> and <em>then</em> set <code>NLS_SORT</code>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">alter</span> <span class="hljs-keyword">session</span> <span class="hljs-keyword">set</span> NLS_LANGUAGE=<span class="hljs-string">'ENGLISH'</span>;
<span class="hljs-keyword">alter</span> <span class="hljs-keyword">session</span> <span class="hljs-keyword">set</span> NLS_SORT=<span class="hljs-string">'BINARY'</span>;
</code></pre>
<h1 id="heading-so-what-is-it-all-about">So what is it all about?</h1>
<h2 id="heading-sorting-per-letter">Sorting per letter.</h2>
<p>Let's sort a table with 4 records, each of 1 character: dot, underscore, lowercase a, uppercase A.</p>
<p>Let's try with 12 settings of <code>NLS_SORT</code>. Since some variants turn out to give the same result, they only get one column in the output:</p>
<ul>
<li><p><code>UNICODE_BINARY</code> gives the same result as <code>BINARY</code>.<br />  I guess that might be different on databases that are not in a Unicode character set and where the text expression being sorted contains Unicode values.</p>
</li>
<li><p>The X-versions of the languages (<code>DUTCH</code> vs <code>XDUTCH</code> / <code>FRENCH</code> and <code>XFRENCH</code> / <code>GERMAN</code> and <code>XGERMAN</code> / <code>WEST_EUROPEAN</code> vs <code>XWESTEUROPEAN</code>) give the same result</p>
</li>
</ul>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td><code>(UNICODE_)BINARY</code></td><td><code>EEC_EURO</code></td><td><code>(X)DUTCH</code></td><td><code>(X)FRENCH</code></td><td><code>(X)GERMAN</code></td><td><code>LATIN</code></td><td><code>(X)WEST_EUOPEAN</code></td><td><code>PUNCTUATION</code></td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td><td>.</td></tr>
<tr>
<td>2</td><td>A</td><td>_</td><td>_</td><td>_</td><td>_</td><td>_</td><td>_</td><td>_</td></tr>
<tr>
<td>3</td><td>_</td><td>A</td><td>A</td><td>A</td><td>a</td><td>A</td><td>A</td><td>A</td></tr>
<tr>
<td>4</td><td>a</td><td>a</td><td>a</td><td>a</td><td>A</td><td>a</td><td>a</td><td>a</td></tr>
</tbody>
</table>
</div><p>Did you know that in German lowercase comes before uppercase?</p>
<p>Do you think this is all you need to know about sorting? Well, look what happens when sorting longer strings... (below)</p>
<p>Did you know that trying to set <code>NLS_SORT</code> to <code>'AMERICAN'</code> or <code>'ENGLISH'</code> gives an error? I guess that it is because of the <code>A</code> in <code>ASCII</code>.</p>
<h2 id="heading-sorting-strings-with-punctuation-eg-codes">Sorting strings with punctuation (e.g. "codes")</h2>
<p>Here I sort eight 3-letter codes composed of A, a, B, b and the underscore.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td><code>(UNICODE_)BINARY</code></td><td><code>EEC_EURO</code></td><td><code>(X)DUTCH</code></td><td><code>(X)FRENCH</code></td><td><code>(X)GERMAN</code></td><td><code>LATIN</code></td><td><code>(X)WEST_EUROPEAN</code></td><td><code>PUNCTUATION</code></td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>AAA</td><td>A_A</td><td>A_A</td><td>A_A</td><td>a_a</td><td>A_A</td><td>A_A</td><td>A_A</td></tr>
<tr>
<td>2</td><td>AAB</td><td>A_B</td><td>a_a</td><td>a_a</td><td>A_A</td><td>a_a</td><td>a_a</td><td>a_a</td></tr>
<tr>
<td>3</td><td>A_A</td><td>AAA</td><td>AAA</td><td>AAA</td><td>aaa</td><td>AAA</td><td>AAA</td><td>A_B</td></tr>
<tr>
<td>4</td><td>A_B</td><td>AAB</td><td>aaa</td><td>aaa</td><td>AAA</td><td>aaa</td><td>aaa</td><td>a_b</td></tr>
<tr>
<td>5</td><td>a_a</td><td>a_a</td><td>AAB</td><td>AAB</td><td>aab</td><td>AAB</td><td>AAB</td><td>AAA</td></tr>
<tr>
<td>6</td><td>a_b</td><td>a_b</td><td>aab</td><td>aab</td><td>AAB</td><td>aab</td><td>aab</td><td>aaa</td></tr>
<tr>
<td>7</td><td>aaa</td><td>aaa</td><td>A_B</td><td>A_B</td><td>a_b</td><td>A_B</td><td>A_B</td><td>AAB</td></tr>
<tr>
<td>8</td><td>aab</td><td>aab</td><td>a_b</td><td>a_b</td><td>A_B</td><td>a_b</td><td>a_b</td><td>aab</td></tr>
</tbody>
</table>
</div><p>Note how in <code>DUTCH</code>, <code>XDUTCH</code>, <code>FRENCH</code>, <code>XFRENCH</code>, <code>LATIN</code>, <code>WEST_EUROPEAN</code> and <code>XWEST_EUROPEAN</code> there is an <code>_</code> <em>below</em> an <code>a</code>, while the test above shows <code>_</code> being sorted <em>above</em> the <code>a</code>! And at the same time there is also an <code>_</code> <em>above</em> an <code>A</code>, meaning that there are <code>A</code> and <code>a</code> <em>between</em> two <code>_</code> ?!?!!??!?</p>
<p>What is going on? Well, My first thought was: The <code>_</code> are <em>removed</em> from the string before sorting. Let's visualise that by removing the <code>_</code> from the sorted list (<code>DUTCH</code>):</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td><code>DUTCH</code></td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>AA</td></tr>
<tr>
<td>2</td><td>aa</td></tr>
<tr>
<td>3</td><td>AAA</td></tr>
<tr>
<td>4</td><td>aaa</td></tr>
<tr>
<td>5</td><td>AAB</td></tr>
<tr>
<td>6</td><td>aab</td></tr>
<tr>
<td>7</td><td>AB</td></tr>
<tr>
<td>8</td><td>ab</td></tr>
</tbody>
</table>
</div><p>Clearly, even that is not fully correct: Why is <code>AAA</code> <em>below</em> <code>aa</code> when <code>A</code> is <em>above</em> <code>a</code> in our first test?<br />My second guess is that not only are the <code>_</code> removed, the (first) ordering is done without looking at case (so all letters uppercased / or all letters lowercased).<br />And then, if there are strings with equal sort order, they are compared again using/including case.<br />It is normal/acceptable that a short string (like <code>AA</code>) comes before a longer string that starts with that short string (like <code>AAA</code>) So both <code>aa</code> and <code>AA</code> end up before <code>aaa</code> and <code>AAA</code>: <code>aa</code> and <code>AA</code> with order 1; <code>aaa</code> and <code>AAA</code> as order 2.<br />In the second step <code>AA</code> gets 1.1 and <code>aa</code> 1.2, while <code>AAA</code> gets 2.1 and <code>aaa</code> 2.2.</p>
<p><code>GERMAN</code> and <code>XGERMAN</code> also show letters <em>between</em> underscores: the same thing is happening as with <code>(X)DUTCH</code>, <code>(X)FRENCH</code>, <code>LATIN</code> and <code>(X)WEST_EUROPEAN</code>.<br />The only difference with those is that in German lowercase comes before uppercase (as seen before).</p>
<p>Let's add in the <code>.</code>, resulting in twelve strings (and let's remove half of the seemingly equal sorting schemes)</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td></td><td><code>(UNICODE)BINARY</code></td><td><code>EEC_EURO</code></td><td><code>DUTCH</code></td><td><code>GERMAN</code></td><td><code>PUNCTUATION</code></td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>A.A</td><td>A.A</td><td>A.A</td><td>a.a</td><td>A.A</td></tr>
<tr>
<td>2</td><td>A.B</td><td>A.B</td><td>A_A</td><td>a_a</td><td>a.a</td></tr>
<tr>
<td>3</td><td>AAA</td><td>A_A</td><td>a.a</td><td>A.A</td><td>A.B</td></tr>
<tr>
<td>4</td><td>AAB</td><td>A_B</td><td>a_a</td><td>A_A</td><td>a.b</td></tr>
<tr>
<td>5</td><td>A_A</td><td>AAA</td><td>AAA</td><td>aaa</td><td>A_A</td></tr>
<tr>
<td>6</td><td>A_B</td><td>AAB</td><td>aaa</td><td>AAA</td><td>a_a</td></tr>
<tr>
<td>7</td><td>a.a</td><td>a.a</td><td>AAB</td><td>aab</td><td>A_B</td></tr>
<tr>
<td>8</td><td>a.b</td><td>a.b</td><td>aab</td><td>AAB</td><td>a_b</td></tr>
<tr>
<td>9</td><td>a_a</td><td>a_a</td><td>A.B</td><td>a.b</td><td>AAA</td></tr>
<tr>
<td>10</td><td>a_b</td><td>a_b</td><td>A_B</td><td>a_b</td><td>aaa</td></tr>
<tr>
<td>11</td><td>aaa</td><td>aaa</td><td>a.b</td><td>A.B</td><td>AAB</td></tr>
<tr>
<td>12</td><td>aab</td><td>aab</td><td>a_b</td><td>A_B</td><td>aab</td></tr>
</tbody>
</table>
</div><h1 id="heading-what-you-should-learn-from-this">What you should learn from this:</h1>
<ol>
<li><p>Sorting is not as straightforward as it seems!</p>
</li>
<li><p>If you have trouble sorting (for example) <em>names</em> because of uppercase/lowercase, try one of the language-specific sorts: They tend to ignore case until sorting equal strings, giving you exactly what you probably want.</p>
</li>
<li><p>If you need to sort any kinds of <em>codes</em> (which may include punctuation like <code>_</code> and <code>.</code>), stay away from the language-specific sorts: they give unexpected results. <code>BINARY</code>, <code>EEC_EURO</code> and <code>PUNCTUATION</code> are all quite different but "logical" nevertheless.</p>
</li>
</ol>
]]></content:encoded></item></channel></rss>