Oracle Database 23c IVSサポートの強化について試してみた

# はじめに
23cの新機能でIVS（Ideographic Variation Sequence）のサポートの強化があります。IVSとは、漢字を表すUnicodeの直後に異体字セレクタ（Variation Selector)と呼ばれるコードを付加し、漢字の異体字を表現する方法です。例えば、こちらの[サイト](
https://moji.or.jp/seminar/aboutivs/)で紹介されている例の「葛」と「葛󠄀」が異体字に該当します。

東京都**葛**飾区
奈良県**葛󠄀**城市

異体字はこれまでもOracle Databaseにデータとして格納はできましたが、今回23cでどのような強化がされたのかを実際に試しながら確認しました。環境はOracle Base Databaseのpluggable Databaseを利用しました。

PDB名: pdb1b
ユーザ：testuser

# 23cに接続する
IVS対応をしているターミナルとしてWindows TerminalのPowerShellを使いました。sshでBase DatabaseのComputeに接続します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/134317/08bcc0d2-3791-1af0-2b8a-79e4f8e41c16.png)


その後、Oracleユーザにかわってからsqlplusで作成済みのPDBに任意のユーザーでログインします。
```sql
-- oracleユーザにスイッチし、環境変数をセット
sudo su - oracle
export NLS_LANG=JAPANESE_JAPAN.AL32UTF8
-- SQL*Plusでログイン
sqlplus / nolog
SQL> connect testuser/WElcome12345##@localhost:1521/pdb1.public1.vcn1.oraclevcn.com
接続されました。
```
# 異体字を含んだ表を作成する
表を作成し、「葛」とその異体字をunistr関数を用いてUnicodeで指定して挿入しました。
```sql
-- 表の作成
create table ivstest (id number,value varchar2(20));
-- データの挿入
insert into ivstest values (0, '葛󠄀');
insert into ivstest values (1, unistr('\845B\DB40\DD00'));
insert into ivstest values (2, unistr('\845B\DB40\DD01'));
insert into ivstest values (3, unistr('\845B\DB40\DD02'));
commit;
```

確認してみます。2と3は使用しているローカルPC上にフォントがないため1と同じ文字が表示されていますが、dump関数で確認すると異なる文字が格納されていることがわかります。
```sql
SQL> select * from ivstest;
        ID VALUE
---------- --------------------
         0 葛
         1 葛󠄀
         2 葛󠄁
         3 葛󠄂

SQL>  select id,dump(value,16)  from ivstest;
        ID DUMP(VALUE,16)
---------- --------------------------------------------------
         0 Typ=1 Len=3: e8,91,9b
         1 Typ=1 Len=7: e8,91,9b,f3,a0,84,80
         2 Typ=1 Len=7: e8,91,9b,f3,a0,84,81
         3 Typ=1 Len=7: e8,91,9b,f3,a0,84,82
```

1文字だけではないデータも追加しておきます。
```sql
insert into ivstest values (4,'南葛飾');
insert into ivstest values (5,'南'||unistr('\845B\DB40\DD00')||'飾');
insert into ivstest values (6,'南'||unistr('\845B\DB40\DD01')||'飾');
commit;
```


# 23c新機能:IVSをサポートする関数や演算子
23cでは関数や演算子がIVSをサポートするようになりました。
## lengthc関数、instrc関数、substrc関数のIVSサポート
Unicodeの文字列を扱う関数として、lengthc関数、instrc関数、substrc関数がありますが、いずれの関数においても、異体字が含まれると19cまでは正しく結果を得ることはできませんでした。投入したデータで確認すると次のようになります。

+ 19cの場合
``` sql
-- データ長の確認⇒2文字とカウントされる
SQL> select id,value ,length(value),lengthb(value),lengthc(value) from ivstest where id <=3;

        ID VALUE                LENGTH(VALUE) LENGTHB(VALUE) LENGTHC(VALUE)
---------- -------------------- ------------- -------------- --------------
         0 葛󠄀                              2              7              2
         1 葛󠄀                              2              7              2
         2 葛󠄁                              2              7              2
         3 葛󠄂                              2              7              2

-- 文字の位置の確認：IDが4以降の行で「飾」の位置を検索⇒異体字を含むID5、6では4番目となっている
SQL> select id,value,instr(value,'飾'),instrb(value,'飾') ,instrc(value,'飾')from ivstest where id >3;

        ID VALUE                INSTR(VALUE,'飾') INSTRB(VALUE,'飾') INSTRC(VALUE,'飾')
---------- -------------------- ----------------- ------------------ ------------------
         4 南葛飾                               3                 7                  3
         5 南葛󠄀飾                              4                 11                  4　　
         6 南葛󠄁飾                              4                 11                  4　　
         
-- 文字の取り出し:IDが4以降の行で「飾」の文字を取り出す⇒異体字を含むID5、6では正しく取り出せない
SQL> select id,value,substr(value,3,1) SUB,substrb(value,7,3) SUB_B,substrc(value,3,1) SUB_C from ivstest where id>3;


        ID VALUE                SUB      SUB_B    SUB_C
---------- -------------------- -------- -------- --------
         4 南葛飾               飾       飾       飾
         5 南葛󠄀飾              󠄀                 󠄀
         6 南葛󠄁飾              󠄁                 󠄁
```


23cではIVSサポートが強化され、その問題を解決しています。

+ 23cの場合
```sql
-- データ長の確認
SQL> select id,value ,length(value),lengthb(value),lengthc(value) from ivstest where id <=3;
　
        ID VALUE                LENGTH(VALUE) LENGTHB(VALUE) LENGTHC(VALUE)
---------- -------------------- ------------- -------------- --------------
         0 葛                              1              3              1
         1 葛󠄀                              2              7              1
         2 葛󠄁                              2              7              1
         3 葛󠄂                              2              7              1

-- 文字の位置の確認：IDが4以降の行で「飾」の位置を検索
select id,value,instr(value,'飾'),instrb(value,'飾') ,instrc(value,'飾')from ivstest where id >3;

        ID VALUE                INSTR(VALUE,'飾') INSTRB(VALUE,'飾') INSTRC(VALUE,'飾')
---------- -------------------- ----------------- ------------------ ------------------
         4 南葛飾                              3                  7                  3
         5 南葛󠄀飾                              4                 11                  3
         6 南葛󠄁飾                              4                 11                  3

-- 文字の取り出し:IDが4以降の行で「飾」の文字を取り出す
SQL> select id,value,substr(value,3,1) SUB,substrb(value,7,3) SUB_B,substrc(value,3,1) SUB_C from ivstest where id>3;

        ID VALUE                SUB        SUB_B      SUB_C
---------- -------------------- ---------- ---------- ----------
         4 南葛飾               飾         飾         飾
         5 南葛󠄀飾              󠄀                     飾
         6 南葛󠄁飾              󠄁                     飾     
```
## like演算子
like演算子はパターンによる検索を可能にします。指定するパターンにおいて、アンダースコア文字 ( _) は1つの文字を示すものですが、異体字の場合は1つの文字と判断することができず、Unicode対応のlikec演算子でも期待した結果を得ることはできませんでした。
+ 19cの場合
``` sql
-- likec演算子⇒ID5、6の異体字は1つの文字としてみなされない
SQL> select * from ivstest where value likec '南_飾';

        ID VALUE
---------- --------------------
         4 南葛飾
```

23cではlikec演算子がIVSを対応するよう拡張され、1つの文字として扱うことができるようになっています。
+ 23cの場合
```sql
-- likec演算子⇒ID5、6の異体字が1文字とみなされている
SQL> select * from ivstest where value likec '南_飾';

        ID VALUE
---------- --------------------
         4 南葛飾
         5 南葛󠄀飾
         6 南葛󠄁飾
```

# 23c新機能 IVS対応Unicode照合アルゴリズム UCA1210_JAPANESE_IVS
言語ソートは文字データのソートや等しいかを判断するときの基準です。文字コード体系で定義された文字の数値を基準にするバイナリ照合と言語に適した順序を反映した数字で行う言語照合の2つの動作が選択できます。
日本語テキストの処理には、言語照合の1つであるUnicode標準であるunicode照合アルゴリズムとしてUCA121_JAPANESE（19cではUCA0700_JAPANESE)が提供されていましたが、異体字セレクタがつかないベースの文字である基底文字を基準に処理を行うため、異体字が別の文字と判断されていませんでした。23cでは日本語テキストを処理する際のIVSサポート用に新しいUnicode照合アルゴリズムUCA1210_JAPANESE_IVSが追加され、異体文字を区別できるようになっています。実際に確認してみます。

まず、デフォルトの言語ソートの動作（NLS_COMP）はバイナリであるため、言語照合に変更します。

```sql
-- 現在の設定を確認
SQL>  select parameter,value from v$nls_parameters where parameter in ('NLS_SORT','NLS_COMP');

PARAMETER                      VALUE
------------------------------ ----------------------------------------------------------------
NLS_SORT                       BINARY
NLS_COMP                       BINARY

-- デフォルトの言語ソートの動作（NLS_COMP）はバイナリであるため、言語照合に変更
SQL> alter session set nls_comp = linguistic;
セッションが変更されました。
```

次に言語ソートをBINARYからUCA121_JAPANESEに変更して、「葛」で検索してみます。

```sql
-- UCA121_JAPANESEに変更
SQL> alter session set nls_sort = uca1210_japanese;
セッションが変更されました。

-- 確認
SQL> select parameter,value from v$nls_parameters where parameter in ('NLS_SORT','NLS_COMP');

PARAMETER                      VALUE
------------------------------ ----------------------------------------------------------------
NLS_SORT                       UCA1210_JAPANESE_S4_VS_BN_NY_EN_FN_HY_DN_MN
NLS_COMP                       LINGUISTIC

-- 検索
SQL> select * from ivstest where value = '葛';
        ID VALUE
---------- --------------------
         0 葛
         1 葛󠄀
         2 葛󠄁
         3 葛󠄂
```
異体字も含めて結果が返されてしまいました。別の文字として判断はされていないということになります。
次に新しいUCA1210_JAPANESE_IVSで試してみます。

```sql
-- UCA121_JAPANESE_IVSに変更
SQL> alter session set nls_sort = uca1210_japanese_ivs;
セッションが変更されました。
-- 確認
SQL> select parameter,value from v$nls_parameters where parameter in ('NLS_SORT','NLS_COMP');

PARAMETER                      VALUE
------------------------------ ----------------------------------------------------------------
NLS_SORT                       UCA1210_JAPANESE_IVS_S4_VS_BN_NY_EN_FN_HY_DN_MN
NLS_COMP                       LINGUISTIC

-- 検索
SQL>  select * from ivstest where value = '葛';

        ID VALUE
---------- --------------------
         0 葛
```
ID0のデータだけ返されました。ID1～3の異体字は別の文字として判断されていることがわかります。

# おわりに
23cでIVSの強化として提供された新機能を確認してみました。地名や名前などで異体字が使われることがあるシステムには喜ばれる新機能ではないかと思いました。

# 参考資料
+ [Unicode IVS (Ideographic Variation Sequence) Support](https://docs.oracle.com/en/database/oracle/oracle-database/23/nfcoa/application-development.html#GUID-FA32381B-E187-458C-B214-0FBBE9B7F723)
+ [9.2.2 SQL Functions for Different Length Semantics](https://docs.oracle.com/en/database/oracle/oracle-database/23/nlspg/sql-and-plsql-programming-in-global-environment.html?source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723&source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723#GUID-FC7BC2B1-DC06-4439-B422-5AB80D675BAB)
+ [9.2.3 LIKE Conditions for Different Length Semantics](https://docs.oracle.com/en/database/oracle/oracle-database/23/nlspg/sql-and-plsql-programming-in-global-environment.html?source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723&source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723&source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723#GUID-0D3BAA34-CA1E-43EE-B36B-A64616EB33FE)
+ [5.3.3.3 The UCA Japanese IVS Collation](https://docs.oracle.com/en/database/oracle/oracle-database/23/nlspg/linguistic-sorting-and-matching.html?source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723&source=%3Aow%3Alp%3Acpo%3A%3Arc_bumk180724p00001%3Alpd100743723#GUID-2F825CD0-B51A-4127-9530-C47FEA89518E)