web-dev-qa-db-ja.com

LC_CTYPEがPostgreSQLデータベースに与える影響は何ですか?

そのため、PostgreSQLを搭載したDebianサーバーはほとんどありません。歴史的に、これらのサーバーとPostgreSQLはLatin 9文字セットでローカライズされていましたが、当時は問題ありませんでした。今では、ポーランド語、ギリシャ語、中国語などを処理する必要があるため、変更することは大きな問題になります。

UTF8データベースを作成しようとすると、次のメッセージが表示されました。

エラー:UTF8のエンコードがロケールfr_FRと一致しません詳細:選択されたLC_CTYPE設定にはLATIN9のエンコードが必要です。

古い同僚のGoogleでこの件についていくつかの調査を行ったところ、Debian LANGの更新、正しい文字セットでのPostgreSQLの再コンパイル、すべてのLC_システム変数およびその他のあいまいなソリューション。とりあえず、この問題は脇に置いておきます。

最近、それは再び戻ってきました、ギリシャ人はものを望んでいて、ラテン9は望んでいません。そして、この問題をもう一度調査しているときに、ある同僚が私のところに来て、「いや、簡単です、見て」と言いました。

彼は何も編集せず、手品をしませんでした、彼はこのSQLクエリを作成しました:

CREATE DATABASE my_utf8_db
  WITH ENCODING='UTF8'
       OWNER=admin
       TEMPLATE=template0
       LC_COLLATE='C'
       LC_CTYPE='C'
       CONNECTION LIMIT=-1
       TABLESPACE=pg_default;

そして、それはうまくいきました。

私は実際にLC_CTYPE='C'について知りませんでした。これを使用することがGoogleの最初のソリューションではなく、スタックオーバーフローでさえもなかったことに驚きました。周りを見回したところ、PostgreSQLのドキュメントでのみ言及が見つかりました。

LC_CTYPEがCまたはPOSIXの場合、任意の文字セットが許可されますが、LC_CTYPEの他の設定では、正しく機能する文字セットは1つだけです。 LC_CTYPE設定はinitdbによって凍結されるため、CまたはPOSIXロケールを選択する場合(したがって、実際のロケール認識を無効にする場合)を除いて、クラスターのさまざまなデータベースで異なるエンコーディングを使用する明らかな柔軟性は、実際よりも理論的です。

それで、これは簡単すぎる、完璧すぎる、不利な点は何ですか?そして、私はまだ答えを見つけるのに苦労しています。だからここに私はここに投稿します:

tl; dr:特定のローカリゼーションでLC_CTYPE='C'を使用することの欠点は何ですか?そうすることは悪いことですか?何が壊れると予想されますか?

28
Gregoire D.

特定のローカリゼーションでLC_CTYPE = 'C'を使用することの欠点は何ですか

ドキュメントでは、ロケールとSQL機能の関係について Locale Support で言及しています。

ロケール設定は、次のSQL機能に影響します。

  • テキストデータのORDER BYまたは標準の比較演算子を使用したクエリの並べ替え順序

  • Upper、lower、およびinitcap関数

  • パターンマッチング演算子(LIKE、SIMILAR TO、POSIXスタイルの正規表現);ロケールは、大文字と小文字を区別しないマッチングと、文字クラスの正規表現による文字の分類の両方に影響します

  • To_charファミリーの関数

  • LIKE句でインデックスを使用する機能

最初の項目(ソート順)は_LC_COLLATE_についてであり、他の項目はすべて_LC_CTYPE_についてであるようです。

LC_COLLATE

_LC_COLLATE_は、文字列間の比較に影響します。実際には、最も目に見える効果はソート順です。 _LC_COLLATE='C'_(または同義語であるPOSIX)は、比較を駆動するのがバイト順であることを意味しますが、_language_REGION_形式のロケールは、文化的ルールが比較を駆動することを意味します。

UTF-8データベース内から実行されたフランス語の名前の例:

_select firstname from (values ('bernard'), ('béréNice'), ('béatrice'), ('boris'))
 AS l(firstname)
order by firstname collate "fr_FR";
_

結果:

名
 ----------- 
ベアトリス
ベールニース
ベルナール
ボリス

_béatrice_はborisの前にあります。アクセント付きのEは、アクセント付きではないかのようにOと比較されるためです。それは文化的なルールです。

これは、Cロケールで発生するものとは異なります。

_select firstname from (values ('bernard'), ('béréNice'), ('béatrice'), ('boris')) 
 AS l(firstname)
order by firstname collate "C";
_

結果:

 firstname 
 ----------- 
 bernard 
 boris 
béatrice
béréNice
 

これで、アクセント付きEの名前がリストの最後にプッシュされます。 UTF-8での_é_のバイト表現は16進数の_C3 A9_であり、oの場合は_6f_です。 _c3_は_6f_よりも大きいので、Cロケールでは_'béatrice' > 'boris'_になります。

ただのアクセントではありません。ハイフネーション、句読点、および_œ_のような奇妙な文字を使用するより複雑なルールがあります。すべてのロケールで奇妙な文化的ルールが予想されます。

比較する文字列が異なる言語が混在している場合、他の世界の人々のfirstname列がある場合、いずれにせよ、特定のロケールが支配するべきではないかもしれません。互いに並べ替えられるように設計されていません。

この場合、Cは合理的な選択であり、純粋なバイト比較に勝るものはないため、高速であるという利点があります。

LC_CTYPE

_LC_CTYPE_を 'C'に設定すると、isupper(c)またはtolower(c)などのC関数は、US-ASCII範囲(つまり、コードポイントまで)の文字に対してのみ期待される結果を提供しますUnicodeでは0x7F)。

upper()lower()またはinitcapなどのSQL関数はこれらのlibc関数に加えてPostgresに実装されているため、US以外のものがあるとすぐに影響を受けます-文字列内のASCII文字。

例:

_test=> show lc_ctype;
  lc_ctype   
-------------
 fr_FR.UTF-8
(1 row)

-- Good result
test=> select initcap('élysée');
 initcap 
---------
 Élysée
(1 row)

-- Wrong result
-- collate "C" is the same as if the db has been created with lc_ctype='C'
test=> select initcap('élysée' collate "C");
 initcap 
---------
 éLyséE
(1 row)
_

Cロケールの場合、_é_は分類できない文字として扱われます。

同様に、正規表現でも間違った結果が得られます。

_test=> select 'élysée' ~ '^\w+$';
 ?column? 
----------
 t
(1 row)

test=> select 'élysée' COLLATE "C" ~ '^\w+$';
 ?column? 
----------
 f
(1 row)
_
31
Daniel Vérité

照合を使用したソートに関するDanielの承認された回答を参照して、MacでPostgreSQLを実行している場合、オペレーティングシステムレベルで一部の照合の設定が不適切であるため、優先照合が期待どおりに機能しない可能性があることに注意してください。あなたはここで問題の詳細を読むことができます:

http://www.postgresql.org/message-id/[email protected]

これは特にPostgreSQL固有の問題ではなく、照合設定のMacのデフォルト設定に関する問題です。私の現在のシステムは、OS X El Capitanバージョン10.11でPostgreSQL 9.3を実行しており、この問題に悩まされています。 「fr_FR」または「en_US」のどちらの照合順序を使用しても、システムは同じクエリ結果を返します。例えば:

「fr_FR」照合の使用:

select firstname from (values ('bernard'), ('béréNice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "fr_FR";

results:
==============
bernard
boris
béatrice
béréNice

「en_US」照合の使用:

select firstname from (values ('bernard'), ('béréNice'), ('béatrice'), ('boris'))
AS l(firstname)
order by firstname collate "en_US";

results:
==============
bernard
boris
béatrice
béréNice

私のシステムでは、(オペレーティングシステムレベルでの)照合設定は、シェルでdiffを実行して示したように、「fr_FR」と「en_US」で同じです。

cd /usr/share/locale
diff fr_FR.UTF-8/LC_COLLATE en_US.UTF-8/LC_COLLATE

うまくいけば、この追加情報は、この問題に苦しんでいるMacでPostgreSQLを使用している、これを読んでいる人にとって有用です。

10
cafecoder905