Aizdomīgi tipi

Viņu izskatā nav nekā aizdomÄ«ga. Turklāt tie jums pat Ŕķiet labi un ilgi pazÄ«stami. Bet tas ir tikai lÄ«dz brÄ«dim, kad tos pārbaudÄ«sit. Å eit viņi parāda savu mānÄ«go raksturu, strādājot pilnÄ«gi savādāk, nekā jÅ«s gaidÄ«jāt. Un dažreiz viņi dara kaut ko tādu, kas liek jums mati stāvēt stāvus - piemēram, viņi pazaudē viņiem uzticētos slepenos datus. Kad jÅ«s saskaraties ar viņiem, viņi apgalvo, ka viņi viens otru nepazÄ«st, lai gan ēnā viņi smagi strādā zem viena pārsega. Ir pienācis laiks beidzot nogādāt tos tÄ«rā Å«denÄ«. Ä»aujiet mums arÄ« tikt galā ar Å”iem aizdomÄ«gajiem tipiem.

Datu ierakstÄ«Å”ana programmā PostgreSQL, neskatoties uz visu tās loÄ£iku, dažreiz sagādā ļoti dÄ«vainus pārsteigumus. Å ajā rakstā mēs centÄ«simies noskaidrot dažas viņu dÄ«vainÄ«bas, izprast viņu dÄ«vainās uzvedÄ«bas iemeslus un saprast, kā ikdienas praksē nesastapties ar problēmām. TaisnÄ«bu sakot, es Å”o rakstu sastādÄ«ju arÄ« kā sava veida uzziņu grāmatu sev, uzziņu grāmatu, uz kuru var viegli atsaukties strÄ«dÄ«gos gadÄ«jumos. Tāpēc tas tiks papildināts, jo tiks atklāti jauni pārsteigumi no aizdomÄ«giem tipiem. Tātad, ejam, ak, nenogurstoÅ”ie datu bāzes izsekotāji!

Dokumentācija numur viens. reāla/dubultā precizitāte/ciparu/nauda

Å Ä·iet, ka skaitļu tipi ir vismazāk problemātiski attiecÄ«bā uz pārsteigumiem uzvedÄ«bā. Bet neatkarÄ«gi no tā, kā tas ir. Tāpēc sāksim ar viņiem. Tātadā€¦

Aizmirsu, kā skaitīt

SELECT 0.1::real = 0.1

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

Kas noticis? Problēma ir tāda, ka PostgreSQL pārveido neierakstÄ«to konstanti 0.1 ar dubultu precizitāti un mēģina to salÄ«dzināt ar reālā tipa 0.1. Un tās ir pilnÄ«gi atŔķirÄ«gas nozÄ«mes! Ideja ir attēlot reālus skaitļus maŔīnas atmiņā. Tā kā 0.1 nevar attēlot kā galÄ«gu bināru daļu (binārā tā bÅ«tu 0.0(0011), skaitļi ar dažādiem cipariem bÅ«s atŔķirÄ«gi, tāpēc tie nav vienādi. VispārÄ«gi runājot, Ŕī ir atseviŔķa raksta tēma, es Å”eit nerakstÄ«Å”u sÄ«kāk.

No kurienes rodas kļūda?

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

Daudzi cilvēki zina, ka PostgreSQL pieļauj funkcionālu apzÄ«mējumu tipu lieÅ”anai. Tas ir, jÅ«s varat rakstÄ«t ne tikai 1::int, bet arÄ« int(1), kas bÅ«s lÄ«dzvērtÄ«gs. Bet ne tipiem, kuru nosaukumi sastāv no vairākiem vārdiem! Tāpēc, ja vēlaties funkcionālā formā nodot skaitlisko vērtÄ«bu dubultās precizitātes tipam, izmantojiet Ŕī tipa aizstājvārdu float8, tas ir, SELECT float8(1).

Kas ir lielāks par bezgalību?

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

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

Paskaties, kā tas ir! Izrādās, ka ir kaut kas lielāks par bezgalÄ«bu, un tas ir NaN! Tajā paŔā laikā PostgreSQL dokumentācija skatās uz mums ar godÄ«gām acÄ«m un apgalvo, ka NaN ir acÄ«mredzami lielāks par jebkuru citu skaitli un lÄ«dz ar to bezgalÄ«bu. Pretēji ir arÄ« -NaN. Sveiki, matemātikas cienÄ«tāji! Bet mums jāatceras, ka tas viss darbojas reālo skaitļu kontekstā.

Acu noapaļoŔana

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

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

Vēl viens negaidÄ«ts sveiciens no bāzes. Atkal atcerieties, ka dubultās precizitātes un skaitļu veidiem ir dažādi noapaļoÅ”anas efekti. Skaitļiem - parastais veids, kad 0,5 tiek noapaļots uz augÅ”u, un dubultai precizitātei - 0,5 tiek noapaļots uz tuvāko pāra veselo skaitli.

Nauda ir kaut kas īpaŔs

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

Saskaņā ar PostgreSQL, nauda nav reāls skaitlis. Pēc dažu cilvēku domām, arī. Jāatceras, ka naudas veidu var nodot tikai skaitļu veidam, tāpat kā naudas veidu var nodot tikai ciparu tipam. Bet tagad ar to var spēlēties, kā sirds kāro. Bet tā nebūs tā pati nauda.

Smallint un secību ģenerēŔana

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 nepatÄ«k tērēt laiku sÄ«kumiem. Kādas ir Ŕīs secÄ«bas, kuru pamatā ir Smallint? int, ne mazāk! Tāpēc, mēģinot izpildÄ«t iepriekÅ” minēto vaicājumu, datu bāze mēģina nodot smallint uz kādu citu veselu skaitļu tipu un redz, ka var bÅ«t vairāki Ŕādi skaitļi. Kuru cast izvēlēties? Viņa nevar to izlemt, un tāpēc avarē kļūdas dēļ.

Faila numurs divi. "char"/char/varchar/text

Rakstzīmju tipos ir arī vairākas dīvainības. Iepazīsim arī viņus.

Kādi ir Ŕie triki?

SELECT 'ŠŸŠ•Š¢ŠÆ'::"char"
     , 'ŠŸŠ•Š¢ŠÆ'::"char"::bytea
     , 'ŠŸŠ•Š¢ŠÆ'::char
     , 'ŠŸŠ•Š¢ŠÆ'::char::bytea

 char  | bytea |    bpchar    | bytea
"char" | bytea | character(1) | bytea
-------+-------+--------------+--------
 ā•Ø     | xd0  | ŠŸ            | xd09f

Kas tas par "čakaru", kas tas par klaunu? Mums tie nav vajadzÄ«gi... Jo tas izliekas par parastu zÄ«muli, lai gan tas ir pēdiņās. Un tas atŔķiras no parastās rakstzÄ«mes, kas ir bez pēdiņām, ar to, ka tā izvada tikai virknes attēlojuma pirmo baitu, bet parasta rakstzÄ«me izvada pirmo rakstzÄ«mi. MÅ«su gadÄ«jumā pirmā rakstzÄ«me ir burts P, kas unikoda attēlojumā aizņem 2 baitus, par ko liecina rezultāta pārvērÅ”ana par baitu tipu. Un ā€œcharā€ tips aizņem tikai pirmo Ŕī unikoda attēlojuma baitu. Tad kāpēc Å”is tips ir vajadzÄ«gs? PostgreSQL dokumentācijā teikts, ka Å”is ir Ä«paÅ”s veids, ko izmanto Ä«paŔām vajadzÄ«bām. Tāpēc diez vai mums tas bÅ«s vajadzÄ«gs. Bet paskaties viņam acÄ«s, un tu nekļūdÄ«sies, kad satiksi viņu ar viņa Ä«paÅ”o uzvedÄ«bu.

Papildu vietas. No redzesloka, no prāta

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

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

Apskatiet sniegto piemēru. Es speciāli konvertēju visus rezultātus uz baitu tipu, lai bÅ«tu skaidri redzams, kas tur ir. Kur ir beigu atstarpes pēc atlaiÅ”anas varchar(6)? Dokumentācijā Ä«si teikts: "Izmantojot rakstzÄ«mes vērtÄ«bu citam rakstzÄ«mju veidam, beigu atstarpes tiek izmestas." Å Ä« nepatika ir jāatceras. Un ņemiet vērā, ka, ja pēdiņās norādÄ«tā virknes konstante tiek nodota tieÅ”i tipam varchar (6), beigu atstarpes tiek saglabātas. Tādi ir brÄ«numi.

Faila numurs trīs. json/jsonb

JSON ir atseviŔķa struktÅ«ra, kas dzÄ«vo savu dzÄ«vi. Tāpēc tās entÄ«tijas un PostgreSQL entÄ«tijas nedaudz atŔķiras. Å eit ir piemēri.

Džonsons un Džonsons. sajust atŔķirību

SELECT 'null'::jsonb IS NULL

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

Lieta ir tāda, ka JSON ir sava nulles entÄ«tija, kas nav NULL analogs programmā PostgreSQL. Tajā paŔā laikā paÅ”am JSON objektam var bÅ«t vērtÄ«ba NULL, tāpēc izteiksme SELECT null::jsonb IS NULL (ņemiet vērā, ka nav vienpēdiņu) Å”oreiz atgriezÄ«sies patiesa.

Viens burts maina visu

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

Lieta ir tāda, ka json un jsonb ir pilnÄ«gi atŔķirÄ«gas struktÅ«ras. Json versijā objekts tiek saglabāts tāds, kāds tas ir, un jsonb tas jau ir saglabāts parsētas, indeksētas struktÅ«ras veidā. Tāpēc otrajā gadÄ«jumā objekta vērtÄ«ba ar atslēgu 1 tika aizstāta no [1, 2, 3] uz [7, 8, 9], kas struktÅ«rā nonāca paŔās beigās ar to paÅ”u atslēgu.

Nedzeriet Å«deni no sejas

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

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

PostgreSQL savā JSONB ievieÅ”anā maina reālo skaitļu formatējumu, ievieÅ”ot tos klasiskajā formā. Tas nenotiek JSON tipam. Nedaudz dÄ«vaini, bet viņam ir taisnÄ«ba.

Faila numurs četri. datums/laiks/laika zīmogs

Ir arÄ« dažas dÄ«vainÄ«bas ar datuma/laika veidiem. ApskatÄ«sim tos. Ä»aujiet man uzreiz izdarÄ«t atrunu, ka dažas uzvedÄ«bas pazÄ«mes kļūst skaidras, ja labi saprotat darba ar laika joslām bÅ«tÄ«bu. Bet Ŕī ir arÄ« atseviŔķa raksta tēma.

Manējais nesaprot

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

Å Ä·iet, kas gan te nesaprotams? Bet datubāze joprojām nesaprot, ko mēs Å”eit ieliekam pirmajā vietā ā€” gadu vai dienu? Un viņa nolemj, ka ir 99. gada 2008. janvāris, kas viņai satriec prātu. VispārÄ«gi runājot, pārsÅ«tot datumus teksta formātā, jums ļoti rÅ«pÄ«gi jāpārbauda, ā€‹ā€‹cik pareizi datu bāze tos atpazina (jo Ä«paÅ”i analizējiet parametru datestyle ar komandu SHOW datestyle), jo neskaidrÄ«bas Å”ajā jautājumā var bÅ«t ļoti dārgas.

No kurienes tu to dabūji?

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

Kāpēc datu bāze nevar saprast skaidri norādÄ«to laiku? Jo laika joslai ir nevis saÄ«sinājums, bet pilns nosaukums, kam ir jēga tikai datuma kontekstā, jo tiek ņemta vērā laika joslu izmaiņu vēsture un bez datuma tas nedarbojas. Un jau pats laika lÄ«nijas formulējums rada jautājumus ā€“ ko Ä«sti programmētājs domāja? Tāpēc Å”eit viss ir loÄ£iski, ja paskatās.

Kas viņam kaiÅ”?

Iedomājieties situāciju. Tabulā ir lauks ar veidu timestampz. JÅ«s vēlaties to indeksēt. Bet jÅ«s saprotat, ka indeksa veidoÅ”ana Å”ajā laukā ne vienmēr ir pamatota tā augstās selektivitātes dēļ (gandrÄ«z visas Ŕāda veida vērtÄ«bas bÅ«s unikālas). Tāpēc jÅ«s nolemjat samazināt indeksa selektivitāti, atlasot veidu uz datumu. Un jÅ«s saņemat pārsteigumu:

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

Kas noticis? Fakts ir tāds, ka, lai datuma tipam nodotu laika zÄ«moga veidu, tiek izmantota TimeZone sistēmas parametra vērtÄ«ba, kas padara tipa konvertÄ“Å”anas funkciju atkarÄ«gu no pielāgota parametra, t.i. nepastāvÄ«gs. Šādas funkcijas indeksā nav atļautas. Å ajā gadÄ«jumā jums ir skaidri jānorāda, kurā laika joslā tiek veikta tipa apraide.

Kad tagad nemaz nav pat tagad

Mēs esam pieraduÅ”i tagad() atgriezt paÅ”reizējo datumu/laiku, ņemot vērā laika joslu. Bet apskatiet Ŕādus jautājumus:

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;

Datums/laiks tiek atgriezts vienāds neatkarÄ«gi no tā, cik daudz laika ir pagājis kopÅ” iepriekŔējā pieprasÄ«juma! Kas noticis? Fakts ir tāds, ka tagad() nav paÅ”reizējais laiks, bet gan paÅ”reizējā darÄ«juma sākuma laiks. LÄ«dz ar to darÄ«juma ietvaros tas nemainās. JebkurÅ” vaicājums, kas tiek palaists ārpus darÄ«juma darbÄ«bas jomas, tiek iekļauts transakcijā netieÅ”i, tāpēc mēs nepamanām, ka laiks tiek atgriezts ar vienkārÅ”u SELECT now(); patiesÄ«bā, nevis paÅ”reizējo... Ja vēlaties iegÅ«t godÄ«gu paÅ”reizējo laiku, jums ir jāizmanto funkcija clock_timestamp().

Faila numurs pieci. mazliet

Nedaudz dīvaini

SELECT '111'::bit(4)

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

Kurā pusē jāpievieno biti tipa paplaÅ”inājuma gadÄ«jumā? Å Ä·iet, ka tas ir kreisajā pusē. Bet tikai bāzei Å”ajā jautājumā ir atŔķirÄ«gs viedoklis. Esiet piesardzÄ«gs: ja ciparu skaits nesakrÄ«t, nododot veidu, jÅ«s nesaņemsit to, ko gribējāt. Tas attiecas gan uz bitu pievienoÅ”anu labajā pusē, gan uz bitu apgrieÅ”anu. ArÄ« pa labi...

Faila numurs seŔi. Masīvi

Pat NULL neizŔāva

SELECT ARRAY[1, 2] || NULL

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

Kā parasti cilvēki, kuri izmanto SQL, mēs sagaidām, ka Ŕīs izteiksmes rezultāts bÅ«s NULL. Bet tā tur nebija. Tiek atgriezts masÄ«vs. Kāpēc? Tā kā Å”ajā gadÄ«jumā bāze izdala NULL uz veselu skaitļu masÄ«vu un netieÅ”i izsauc funkciju array_cat. Taču joprojām nav skaidrs, kāpēc Å”is ā€œmasÄ«va kaÄ·isā€ neatiestata masÄ«vu. ArÄ« Ŕī uzvedÄ«ba ir vienkārÅ”i jāatceras.

Apkopojiet. Ir daudz dÄ«vainu lietu. Lielākā daļa no viņiem, protams, nav tik kritiski, lai runātu par klaji nepiedienÄ«gu uzvedÄ«bu. Un citi ir izskaidrojami ar lietoÅ”anas vienkārŔību vai to pielietoÅ”anas biežumu noteiktās situācijās. Bet tajā paŔā laikā ir daudz pārsteigumu. Tāpēc jums par tiem jāzina. Ja kāda veida uzvedÄ«bā atrodat ko citu dÄ«vainu vai neparastu, rakstiet komentāros, es ar prieku papildināŔu par tiem pieejamo dokumentāciju.

Avots: www.habr.com

Pievieno komentāru