疑わしいタむプ

圌らの倖芋には䜕の疑いもありたせん。 さらに、それらはあなたにずっお、長い間よく知られおいるようにさえ思えたす。 ただし、それは確認するたでの話です。 ここで圌らの陰湿な本性が珟れ、予想ずはたったく異なる動䜜をしたす。 そしお時には、圌らは髪の毛が逆立぀ようなこずをするこずがありたす。たずえば、圌らに蚗された機密デヌタが倱われるなどです。 圌らに察峙するず、圌らはお互いのこずを知らないず䞻匵したすが、圱では同じフヌドの䞋で熱心に働いおいたす。 ぀いに圌らをきれいな氎に連れお行く時が来たした。 こうした䞍審なタむプにも察凊したしょう。

PostgreSQL でのデヌタの型付けは、そのロゞック党䜓から芋お、非垞に奇劙な驚きをもたらすこずがありたす。 この蚘事では、圌らの癖のいく぀かを明らかにし、圌らの奇劙な行動の理由を理解し、日垞生掻で問題に遭遇しない方法を理解しようずしたす。 実を蚀うず、この蚘事は私自身のための䞀皮の参考曞、論争の際に参照しやすい参考曞ずしおたずめたものでもありたす。 したがっお、疑わしいタむプからの新しい驚きが発芋されるず、補充されたす。 それでは、行きたしょう、おお疲れ知らずのデヌタベヌス远跡者たち!

曞類その。 実数/倍粟床/数倀/通貚

動䜜の意倖性ずいう点では、数倀型が最も問題が少ないように思えたす。 しかし、それがどのようなものであっおも。 それでは、それらから始めたしょう。 それで 

数え方を忘れた

SELECT 0.1::real = 0.1

?column?
boolean
---------
f

どうしたの 問題は、PostgreSQL が型なし定数 0.1 を倍粟床に倉換し、それを実数型の 0.1 ず比范しようずするこずです。 そしおこれらはたったく異なる意味です アむデアは、マシンのメモリ内で実数を衚珟するこずです。 0.1 は有限の 0.0 進数の小数ずしお衚すこずができないため (0011 進数では XNUMX(XNUMX) になりたす)、桁が異なる数倀は異なるため、結果は等しくなくなりたす。 䞀般的に、これは別の蚘事のトピックなので、ここでは詳しく曞きたせん。

゚ラヌはどこから来たのでしょうか?

SELECT double precision(1)

ERROR:  syntax error at or near "("
LINE 1: SELECT double precision(1)
                               ^
********** ОшОбка **********
ERROR: syntax error at or near "("
SQL-сПстПяМОе: 42601
СОЌвПл: 24

PostgreSQL では型キャストの関数衚蚘が可胜であるこずは倚くの人が知っおいたす。 ぀たり、1::int だけでなく、同等の int(1) も曞くこずができたす。 ただし、名前が耇数の単語で構成されるタむプの堎合はそうではありたせん。 したがっお、関数圢匏で数倀を倍粟床型にキャストする堎合は、この型 float8 の゚むリアス、぀たり SELECT float8(1) を䜿甚したす。

無限倧より倧きいものは䜕ですか?

SELECT 'Infinity'::double precision < 'NaN'::double precision

?column?
boolean
---------
t

それがどんなものかを芋おください 無限倧よりも倧きなものが存圚するこずが刀明したした。それは NaN です。 同時に、PostgreSQL のドキュメントは私たちを正盎な目で芋お、NaN は他の数倀よりも明らかに倧きく、したがっお無限倧であるず䞻匵しおいたす。 -NaN に぀いおはその逆も圓おはたりたす。 数孊愛奜家の皆さん、こんにちは しかし、これはすべお実数のコンテキストで機胜するこずを芚えおおく必芁がありたす。

目を䞞くする

SELECT round('2.5'::double precision)
     , round('2.5'::numeric)

      round      |  round
double precision | numeric
-----------------+---------
2                | 3

基地からたた予期せぬ挚拶が。 繰り返しになりたすが、倍粟床型ず数倀型には異なる䞞め効果があるこずに泚意しおください。 数倀の堎合は通垞の方法で 0,5 が切り䞊げられ、倍粟床の堎合は 0,5 が最も近い偶数の敎数に䞞められたす。

お金ずいうのは特別なものです

SELECT '10'::money::float8

ERROR:  cannot cast type money to double precision
LINE 1: SELECT '10'::money::float8
                          ^
********** ОшОбка **********
ERROR: cannot cast type money to double precision
SQL-сПстПяМОе: 42846
СОЌвПл: 19

PostgreSQL によれば、お金は実数ではありたせん。 䞀郚の個人によるず、こうも蚀われおいたす。 数倀型のみが通貚型にキャストできるのず同様に、通貚型のキャストは数倀型にのみ可胜であるこずを芚えおおく必芁がありたす。 しかし今では、心の望むたたに遊ぶこずができたす。 しかし、同じお金にはなりたせん。

Smallint ずシヌケンスの生成

SELECT *
  FROM generate_series(1::smallint, 5::smallint, 1::smallint)

ERROR:  function generate_series(smallint, smallint, smallint) is not unique
LINE 2:   FROM generate_series(1::smallint, 5::smallint, 1::smallint...
               ^
HINT:  Could not choose a best candidate function. You might need to add explicit type casts.
********** ОшОбка **********
ERROR: function generate_series(smallint, smallint, smallint) is not unique
SQL-сПстПяМОе: 42725
ППЎсказка: Could not choose a best candidate function. You might need to add explicit type casts.
СОЌвПл: 18

PostgreSQL は些现なこずで時間を無駄にするこずを奜みたせん。 これらのシヌケンスは smallint に基づいおいるのでしょうか? int、それ以䞊です したがっお、䞊蚘のク゚リを実行しようずするず、デヌタベヌスは smallint を他の敎数型にキャストしようずし、そのようなキャストがいく぀かある可胜性があるこずを確認したす。 どのキャストを遞ぶか 圌女はこれを決定できず、゚ラヌでクラッシュしたす。

ファむル番号 XNUMX。 "char"/char/varchar/text

文字タむプにも倚くの奇劙な点がありたす。 圌らに぀いおも知っおみたしょう。

これらはどのようなトリックですか

SELECT 'ПЕТЯ'::"char"
     , 'ПЕТЯ'::"char"::bytea
     , 'ПЕТЯ'::char
     , 'ПЕТЯ'::char::bytea

 char  | bytea |    bpchar    | bytea
"char" | bytea | character(1) | bytea
-------+-------+--------------+--------
 ╹     | xd0  | П            | xd09f

この「むワナ」は䜕の皮類ですか、これは䜕のピ゚ロですか これらは必芁ありたせん...匕甚笊で囲たれおいるにもかかわらず、通垞の char であるかのように芋せかけおいるためです。 たた、通垞の char は最初の文字を出力するのに察し、文字列衚珟の最初のバむトのみを出力するずいう点で、匕甚笊のない通垞の char ずは異なりたす。 この堎合、最初の文字は文字 P であり、結果を bytea 型に倉換するずわかるように、Unicode 衚珟では 2 バむトを占めたす。 そしお、「char」タむプは、この Unicode 衚珟の最初のバむトのみを受け取りたす。 では、なぜこのタむプが必芁なのでしょうか PostgreSQL のドキュメントには、これは特別なニヌズに䜿甚される特別なタむプであるず蚘茉されおいたす。 したがっお、それが必芁になる可胜性は䜎いです。 しかし、圌の目を芋れば、圌の特別な行動に出䌚っおも間違うこずはありたせん。

䜙分なスペヌス。 芖界の倖、心の倖

SELECT 'abc   '::char(6)::bytea
     , 'abc   '::char(6)::varchar(6)::bytea
     , 'abc   '::varchar(6)::bytea

     bytea     |   bytea  |     bytea
     bytea     |   bytea  |     bytea
---------------+----------+----------------
x616263202020 | x616263 | x616263202020

䞎えられた䟋を芋おください。 すべおの結果を特別に bytea 型に倉換しお、そこに䜕があるかが明確に芋えるようにしたした。 varchar(6) にキャストした埌の末尟のスペヌスはどこにありたすか? ドキュメントには、「character の倀を別の文字型にキャストする堎合、末尟の空癜は砎棄されたす。」ず簡朔に蚘茉されおいたす。 この嫌悪感は忘れおはならない。 たた、匕甚笊で囲たれた文字列定数が varchar(6) 型に盎接キャストされる堎合、末尟のスペヌスは保持されるこずに泚意しおください。 それが奇跡なのです。

ファむル番号 XNUMX。 json/jsonb

JSON は、独自の生呜を維持する独立した構造です。 したがっお、その゚ンティティず PostgreSQL の゚ンティティは若干異なりたす。 以䞋に䟋を瀺したす。

ゞョン゜ン・アンド・ゞョン゜ン。 違いを感じたす

SELECT 'null'::jsonb IS NULL

?column?
boolean
---------
f

問題は、JSON には独自の null ゚ンティティがあり、PostgreSQL の NULL に盞圓するものではないずいうこずです。 同時に、JSON オブゞェクト自䜓が NULL 倀を持぀可胜性があるため、匏 SELECT null::jsonb IS NULL (䞀重匕甚笊がないこずに泚意しおください) は今回は true を返したす。

䞀文字がすべおを倉える

SELECT '{"1": [1, 2, 3], "2": [4, 5, 6], "1": [7, 8, 9]}'::json

                     json
                     json
------------------------------------------------
{"1": [1, 2, 3], "2": [4, 5, 6], "1": [7, 8, 9]}

---

SELECT '{"1": [1, 2, 3], "2": [4, 5, 6], "1": [7, 8, 9]}'::jsonb

             jsonb
             jsonb
--------------------------------
{"1": [7, 8, 9], "2": [4, 5, 6]}

重芁なのは、json ず jsonb はたったく異なる構造であるずいうこずです。 json では、オブゞェクトはそのたた保存されたすが、jsonb では、解析枈みのむンデックス付き構造の圢匏ですでに保存されおいたす。 そのため、1 番目のケヌスでは、キヌ 1 によるオブゞェクトの倀が [2, 3, 7] から [8, 9, XNUMX] に眮き換えられ、同じキヌで構造䜓の最埌に远加されたした。

顔に぀いた氎を飲たないでください

SELECT '{"reading": 1.230e-5}'::jsonb
     , '{"reading": 1.230e-5}'::json

          jsonb         |         json
          jsonb         |         json
------------------------+----------------------
{"reading": 0.00001230} | {"reading": 1.230e-5}

PostgreSQL の JSONB 実装では、実数の曞匏蚭定が倉曎され、実数が叀兞的な圢匏になりたす。 JSON タむプではこれは発生したせん。 少し奇劙ですが、圌は正しいです。

ファむル番号 XNUMX。 日付/時刻/タむムスタンプ

日付/時刻型にも奇劙な点がいく぀かありたす。 それらを芋おみたしょう。 タむムゟヌンを扱うこずの本質をよく理解すれば、行動の特城のいく぀かが明らかになるずいうこずをすぐに予玄させおください。 しかし、これも別の蚘事で取り䞊げたす。

私はあなたのこずを理解しおいたせん

SELECT '08-Jan-99'::date

ERROR:  date/time field value out of range: "08-Jan-99"
LINE 1: SELECT '08-Jan-99'::date
               ^
HINT:  Perhaps you need a different "datestyle" setting.
********** ОшОбка **********
ERROR: date/time field value out of range: "08-Jan-99"
SQL-сПстПяМОе: 22008
ППЎсказка: Perhaps you need a different "datestyle" setting.
СОЌвПл: 8

ここで䜕が理解できないず思われるでしょうか しかしデヌタベヌスは、ここで最初に䜕を入れたのか、぀たり幎なのか、それずも日なのかをただ理解しおいたせん。 そしお圌女は、それが 99 幎 2008 月 XNUMX 日であるず刀断し、衝撃を受けたした。 䞀般に、日付をテキスト圢匏で送信する堎合は、この問題に関するあいたいさが非垞に高く぀く可胜性があるため、デヌタベヌスが日付をどの皋床正確に認識したかを非垞に慎重に確認する必芁がありたす (特に、SHOW datestyle コマンドを䜿甚しお datestyle パラメヌタを分析する)。

あなたの出身地はどちらですか

SELECT '04:05 Europe/Moscow'::time

ERROR:  invalid input syntax for type time: "04:05 Europe/Moscow"
LINE 1: SELECT '04:05 Europe/Moscow'::time
               ^
********** ОшОбка **********
ERROR: invalid input syntax for type time: "04:05 Europe/Moscow"
SQL-сПстПяМОе: 22007
СОЌвПл: 8

デヌタベヌスが明瀺的に指定された時間を理解できないのはなぜですか? タむム ゟヌンには省略圢ではなく完党な名前があるため、タむム ゟヌンの倉曎履歎が考慮され、日付がないず機胜しないため、日付のコンテキストでのみ意味を持ちたす。 そしお、タむムラむンの文蚀自䜓が疑問を匕き起こしたす - プログラマヌは本圓に䜕を蚀いたかったのでしょうか? したがっお、ここで芋るず、すべおが論理的です。

圌の䜕が問題なのでしょうか

状況を想像しおみおください。 テヌブルに timestamptz 型のフィヌルドがありたす。 むンデックスを付けたいず考えおいたす。 ただし、このフィヌルドの遞択性が高いため、このフィヌルドにむンデックスを構築するこずが垞に正圓化されるわけではないこずを理解しおいたす (このタむプのほがすべおの倀は䞀意になりたす)。 そこで、型を日付にキャストしおむンデックスの遞択性を䞋げるこずにしたした。 するず驚くべきこずが分かりたす:

CREATE INDEX "iIdent-DateLastUpdate"
  ON public."Ident" USING btree
  (("DTLastUpdate"::date));

ERROR:  functions in index expression must be marked IMMUTABLE
********** ОшОбка **********
ERROR: functions in index expression must be marked IMMUTABLE
SQL-сПстПяМОе: 42P17

どうしたの 実際には、timestamptz 型を date 型にキャストするには、TimeZone システム パラメヌタヌの倀が䜿甚されたす。これにより、型倉換関数がカスタム パラメヌタヌに䟝存したす。 揮発性。 このような関数はむンデックスでは蚱可されたせん。 この堎合、型キャストがどのタむムゟヌンで実行されるかを明瀺的に指定する必芁がありたす。

今がたったく今でもないずき

私たちは、タむムゟヌンを考慮しお、now() が珟圚の日付/時刻を返すこずに慣れおいたす。 しかし、次のク゚リを芋おください。

START TRANSACTION;
SELECT now();

            now
  timestamp with time zone
-----------------------------
2019-11-26 13:13:04.271419+03

...

SELECT now();

            now
  timestamp with time zone
-----------------------------
2019-11-26 13:13:04.271419+03

...

SELECT now();

            now
  timestamp with time zone
-----------------------------
2019-11-26 13:13:04.271419+03

COMMIT;

前回のリク゚ストからどれだけ時間が経過しおも、同じ日付/時刻が返されたす。 どうしたの 実際には、now() は珟圚の時刻ではなく、珟圚のトランザクションの開始時刻です。 したがっお、トランザクション内では倉曎されたせん。 トランザクションの範囲倖で起動されたク゚リは暗黙的にトランザクションにラップされたす。そのため、単玔な SELECT now(); によっお返される時刻に気付かないのはこのためです。 実際には、珟圚の時刻ではありたせん...正確な珟圚時刻を取埗したい堎合は、 Clock_timestamp() 関数を䜿甚する必芁がありたす。

ファむル番号 XNUMX。 少し

少し奇劙です

SELECT '111'::bit(4)

 bit
bit(4)
------
1110

型拡匵の堎合はどちら偎にビットを远加すればよいでしょうか 巊偎にあるようです。 しかし、この件に関しおは基地だけが異なる意芋を持っおいる。 泚意: 型をキャストするずきに桁数が䞀臎しないず、期埅した結果が埗られたせん。 これは、右偎ぞのビットの远加ずビットのトリミングの䞡方に圓おはたりたす。 右偎にも 

ファむル番号 XNUMX。 配列

NULLでも発火しなかった

SELECT ARRAY[1, 2] || NULL

?column?
integer[]
---------
{1,2}

SQL で育った普通の人々ずしお、私たちはこの匏の結果が NULL であるこずを期埅したす。 しかし、そこにはありたせんでした。 配列が返されたす。 なぜ この堎合、ベヌスは NULL を敎数配列にキャストし、暗黙的に array_cat 関数を呌び出すためです。 しかし、なぜこの「配列猫」が配列をリセットしないのかはただ䞍明です。 この動䜜も芚えおおく必芁がありたす。

芁玄したす。 䞍思議なこずはたくさんありたす。 もちろん、それらのほずんどは、あからさたに䞍適切な行為に぀いお語るほど批刀的なものではありたせん。 たた、䜿いやすさや特定の状況での適甚頻床によっお説明されるものもありたす。 しかし同時に、倚くの驚きもありたす。 したがっお、それらに぀いお知る必芁がありたす。 任意のタむプの動䜜で他に奇劙たたは異垞な点を芋぀けた堎合は、コメントに曞き蟌んでください。入手可胜な資料に喜んで远加したす。

出所 habr.com

コメントを远加したす