Mota susmagarriak

Ez dago ezer susmagarririk haien itxuraz. Gainera, ezagunak ere iruditzen zaizkizu ondo eta denbora luzez. Baina hori egiaztatu arte bakarrik da. Hor erakusten dute beren izaera maltzurra, espero zenuena baino guztiz ezberdina lan eginez. Eta batzuetan ilea tente jartzen dizun zerbait egiten dute; adibidez, haiek emandako datu sekretuak galtzen dituzte. Haiei aurre egiten diezunean, elkar ezagutzen ez dutela aldarrikatzen dute, nahiz eta itzalpean kaputxa beraren azpian gogor lan egiten duten. Azkenean ur garbira eramateko garaia da. Mota susmagarri horiei ere aurre egin diezaiegun.

PostgreSQL-n datuak idazteak, logika guztiarekin, oso sorpresa arraroak ematen ditu batzuetan. Artikulu honetan haien bitxikeria batzuk argitzen saiatuko gara, haien portaera arraroaren zergatia ulertzen eta eguneroko praktikan arazoekin nola ez aurkitu ulertzen. Egia esateko, artikulu hau niretzako erreferentzia-liburu moduko bat bezala ere bildu dut, kasu polemikoetan erraz aipa zitekeen kontsulta liburu bat. Hori dela eta, berritu egingo da mota susmagarrien sorpresa berriak aurkitu ahala. Beraz, goazen, oi datu-baseen jarraitzaile nekaezinak!

Dosier zenbaki bat. benetako/zehaztasun bikoitza/zenbakizkoa/dirua

Badirudi zenbakizko motak direla arazo gutxien jokabidearen ezustekoei dagokienez. Baina ez du axola nola den. Beraz, has gaitezen haiekin. Beraz…

Nola zenbatu ahaztu zait

SELECT 0.1::real = 0.1

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

Zein da ba arazoa? Arazoa da PostgreSQL-k idatzi gabeko 0.1 konstantea doitasun bikoitzean bihurtzen duela eta mota errealeko 0.1arekin konparatzen saiatzen dela. Eta hauek esanahi guztiz desberdinak dira! Makinaren memorian zenbaki errealak irudikatzea da ideia. 0.1 zatiki bitar finitu gisa irudikatu ezin denez (0.0(0011) izango litzateke bitarrean), bit-sakonera ezberdina duten zenbakiak desberdinak izango dira, beraz, berdinak ez direlako emaitza. Orokorrean, artikulu berezi baterako gaia da; ez dut hemen zehatzago idatziko.

Nondik dator akatsa?

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

Jende askok badaki PostgreSQL-k idazkera funtzionala ahalbidetzen duela mota igortzeko. Hau da, 1::int ez ezik, int(1) ere idatz dezakezu, baliokidea izango dena. Baina ez izenak hainbat hitzez osatutako motentzat! Hori dela eta, zenbakizko balio bat doitasun mota bikoitzeko forma funtzionalean igorri nahi baduzu, erabili float8 mota honen ezizena, hau da, SELECT float8(1).

Zer da infinitua baino handiagoa?

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

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

Begira nolakoa den! Infinitua baino zerbait handiagoa da eta NaN da! Aldi berean, PostgreSQL dokumentazioak begi zintzoekin begiratzen gaitu eta NaN beste edozein zenbaki baino handiagoa dela dio, eta, beraz, infinitua. Kontrakoa gertatzen da -NaN-entzat ere. Kaixo, matematika zaleok! Baina gogoratu behar dugu honek guztiak zenbaki errealen testuinguruan funtzionatzen duela.

Begiak biribiltzea

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

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

Beste ustekabeko agur bat oinarritik. Berriz ere, gogoratu doitasun bikoitzak eta zenbakizko motak biribilketa-efektu desberdinak dituztela. Zenbakizkoetarako - ohikoa, 0,5 gora biribiltzen denean, eta zehaztasun bikoitzerako - 0,5 osoko bikoiti hurbilenera biribiltzen da.

Dirua zerbait berezia da

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-ren arabera, dirua ez da benetako zenbaki bat. Pertsona batzuen ustez, gainera. Gogoratu behar dugu diru-mota igortzea zenbaki-motara soilik posible dela, zenbaki-mota soilik diru-motara bota daitekeen bezala. Baina orain zure bihotzak nahi duen moduan jolastu dezakezu. Baina ez da diru bera izango.

Smallint eta sekuentziaren sorrera

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-k ez du gustatzen txikikeriatan denbora galtzea. Zeintzuk dira sekuentzia hauek smallint-ean oinarrituta? int, ez gutxiago! Hori dela eta, goiko kontsulta exekutatzen saiatzean, datu-basea beste zenbaki oso batera txikia igortzen saiatzen da, eta horrelako hainbat casting egon daitezkeela ikusten du. Zein aktore aukeratu? Ezin du hau erabaki, eta, beraz, errore batekin huts egiten du.

Bi zenbakiko fitxategia. "char"/char/varchar/text

Pertsonaia motetan ere bitxikeria batzuk daude. Ezagutu ditzagun haiek ere.

Nolako trikimailuak dira hauek?

SELECT 'ΠŸΠ•Π’Π―'::"char"
     , 'ΠŸΠ•Π’Π―'::"char"::bytea
     , 'ΠŸΠ•Π’Π―'::char
     , 'ΠŸΠ•Π’Π―'::char::bytea

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

Zer "char" mota da hau, zein pailazo mota da hau? Ez ditugu horiek behar... Char arrunt baten itxura duelako, komatxo artean egon arren. Eta karaktere erregular batetik desberdina da, komatxorik gabea, katearen irudikapenaren lehen bytea bakarrik ateratzen duelako, karaktere arrunt batek lehenengo karakterea ateratzen duen bitartean. Gure kasuan, lehen karakterea P letra da, Unicode irudikapenean 2 byte hartzen dituena, emaitza bytea motara bihurtzeak frogatzen duen moduan. Eta "char" motak Unicode irudikapen honen lehen bytea bakarrik hartzen du. Orduan, zergatik behar da mota hau? PostgreSQL dokumentazioak dio behar berezietarako erabiltzen den mota berezi bat dela. Beraz, nekez beharko dugu. Baina begiratu bere begietara eta ez zara oker egongo bere jokaera bereziarekin topo egiten duzunean.

Espazio gehigarriak. Bistatik kanpo, gogotik kanpo

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

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

Begiratu emandako adibideari. Bereziki emaitza guztiak bytea motara bihurtu nituen, hor zegoena argi ikus zedin. Non daude atzeko tarteak varchar(6)-ra igorri ondoren? Dokumentazioak labur-labur dio: "Carakterearen balioa beste karaktere mota batera igortzen denean, atzealdeko zuriuneak baztertzen dira". Ez-gogo hori gogoratu behar da. Kontuan izan komatxoen konstante bat varchar(6) motara zuzenean botatzen bada, azkeneko espazioak gordetzen direla. Halakoak dira mirariak.

Hiru zenbakiko espedientea. json/jsonb

JSON bere bizitza propioa duen egitura bereizia da. Hori dela eta, bere entitateak eta PostgreSQLenak zertxobait desberdinak dira. Hona hemen adibideak.

Johnson eta Johnson. sentitu aldea

SELECT 'null'::jsonb IS NULL

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

Gauza da JSON-ek bere entitate nulua duela, hau da, PostgreSQL-n NULL-en analogoa ez dena. Aldi berean, JSON objektuak berak NULL balioa izan dezake, beraz, SELECT null::jsonb IS NULL adierazpenak (kontuan izan komatxo bakunen eza) egia itzuliko du oraingoan.

Letra batek dena aldatzen du

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]}

Gauza da json eta jsonb egitura guztiz desberdinak direla. Json-en, objektua dagoen bezala gordetzen da, eta jsonb-en dagoeneko indexatutako egitura baten moduan gordetzen da. Horregatik, bigarren kasuan, objektuaren balioa 1 gakoarekin ordezkatu zen [1, 2, 3]-tik [7, 8, 9]-ra, azken aldean egituran sartu zen gako berarekin.

Ez edan aurpegitik urik

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

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

PostgreSQL-k bere JSONB inplementazioan zenbaki errealen formatua aldatzen du, forma klasikora eramanez. Hau ez da gertatzen JSON motarako. Arraro samarra, baina arrazoia du.

Lau zenbakiko espedientea. data/ordua/ordu-zigilua

Data/ordu motekin bitxikeria batzuk ere badaude. Ikus ditzagun. Erreserba egin dezadan berehala jokabide-ezaugarri batzuk argi geratzen direla ordu-eremuekin lan egitearen funtsa ondo ulertzen baduzu. Baina hau aparteko artikulu baterako gaia ere bada.

Nire zureak ez du ulertzen

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

Badirudi hemen zer den ulertezina? Baina datu-baseak oraindik ez du ulertzen zer jartzen dugun hemen lehen postuan: urtea ala eguna? Eta 99ko urtarrilaren 2008a dela erabakitzen du, eta horrek gogorarazten dio. Orokorrean, datak testu-formatuan igortzean, kontu handiz egiaztatu behar duzu datu-baseak nola ongi ezagutzen dituen (bereziki, aztertu datastyle parametroa SHOW datestyle komandoarekin), gai honetan anbiguotasunak oso garestiak izan daitezkeelako.

Nondik atera duzu hau?

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

Zergatik ezin du datu-baseak ulertu esplizituki zehaztutako denbora? Ordu-eremuak ez baitu laburdurarik, izen osoa baizik, eta horrek data baten testuinguruan bakarrik dauka zentzua, ordu-eremuaren aldaketen historia kontuan hartzen baitu eta datarik gabe ez du funtzionatzen. Eta denbora-lerroaren hitzak berak zalantzak sortzen ditu: zer esan nahi zuen programatzaileak benetan? Beraz, hemen dena logikoa da, begiratuz gero.

Zer gertatzen zaio?

Imajinatu egoera. Zure taulan timestamptz motako eremu bat duzu. indexatu nahi duzu. Baina ulertzen duzu eremu honetan indize bat eraikitzea ez dela beti justifikatzen bere selektibitate handia dela eta (mota honetako balio ia guztiak bakarrak izango dira). Beraz, indizearen selektibitatea murriztea erabakitzen duzu mota data batera botaz. Eta sorpresa bat jasoko duzu:

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

Zein da ba arazoa? Izan ere, timestamptz mota bat data mota batera botatzeko, TimeZone sistema-parametroaren balioa erabiltzen dela, eta horrek mota bihurtzeko funtzioa parametro pertsonalizatu baten menpe jartzen du, hau da. lurrunkorra. Horrelako funtzioak ez dira onartzen indizean. Kasu honetan, espresuki adierazi behar duzu zein ordu-eremutan egiten den cast mota.

Orain ez denean batere orain

Orain() uneko data/ordua itzultzera ohituta gaude, ordu-eremua kontuan hartuta. Baina begiratu honako kontsulta hauek:

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;

Data/ordua berdin itzultzen da aurreko eskaeratik zenbat denbora igaro den! Zein da ba arazoa? Kontua da orain() ez dela uneko ordua, uneko transakzioaren hasiera-ordua baizik. Hori dela eta, ez da aldatzen transakzioaren barruan. Transakzio baten esparrutik kanpo abiarazitako edozein kontsulta transakzio batean biltzen da inplizituki, horregatik ez dugu ohartzen SELECT now() soil batek itzultzen duen denbora; izan ere, ez oraingoa... Uneko ordu zintzoa lortu nahi baduzu, clock_timestamp() funtzioa erabili behar duzu.

Bost zenbakiko espedientea. bit

Pixka bat bitxia

SELECT '111'::bit(4)

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

Zein alde gehitu behar dira bitak mota luzapenaren kasuan? Ezkerrean dagoela dirudi. Baina oinarriak bakarrik dauka beste iritzi bat gai honi buruz. Kontuz: zifra kopurua ez badator bat mota bat igortzean, ez duzu nahi zenuena lortuko. Hau eskuinera bitak gehitzeari eta bitak mozteari aplikatzen zaio. Eskuinean ere...

Sei zenbakiko espedientea. Arrayak

NULL-ek ere ez zuen tiro egin

SELECT ARRAY[1, 2] || NULL

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

SQL-n planteatutako pertsona normal gisa, adierazpen honen emaitza NULL izatea espero dugu. Baina ez zegoen han. Array bat itzultzen da. Zergatik? Kasu honetan oinarriak NULL igortzen baitu osoko matrize batera eta inplizituki array_cat funtzioari deitzen dio. Baina oraindik ez dago argi zergatik "array cat" honek ez duen matrizea berrezartzen. Jokabide hori ere gogoratu behar da.

Laburtu. Gauza arraro asko daude. Gehienak, noski, ez dira hain kritikoak jokabide nabarmen desegokiez hitz egiteko. Eta beste batzuk erabiltzeko erraztasunagatik edo egoera jakin batzuetan duten aplikagarritasunaren maiztasunagatik azaltzen dira. Baina, aldi berean, sorpresa asko daude. Hori dela eta, horiei buruz jakin behar duzu. Edozein motatako portaeran beste ezer arraroa edo ezohikoa iruditzen bazaizu, idatzi iruzkinetan, pozik gehituko ditut haietan dauden espedienteei.

Iturria: www.habr.com

Gehitu iruzkin berria