Kahtlased tĂŒĂŒbid

Nende vĂ€limuses ei kahtle sa milleski. Veelgi enam, nad nĂ€ivad sulle hĂ€sti ja ammu tuttavad. Kuid see on alles seni, kuni sa neid ei kontrolli. Siin nad nĂ€itavadki oma petlikku olemust, kĂ€itudes sootuks teistmoodi, kui sa ootasid. MĂ”nikord juhtub midagi sellist, et juuksed tĂ”usevad pĂŒsti — nĂ€iteks kaotavad nad usaldatud saladusandmed. Kui sa teed neile otsetee, vĂ€idavad nad, et nad ei tunne ĂŒksteist, kuigi varjus teevad nad usinalt tööd ĂŒheskoos. On aeg nad lĂ”puks pĂ€evavalgele tuua. LĂ€heme siis koos nende kahtlaste tĂŒĂŒpide kallale.

Andmete tĂŒpiseerimine PostgreSQL-is, hoolimata oma loogilisusest, toob tĂ”epoolest aeg-ajalt vĂ€lja vĂ€ga kummalisi ĂŒllatusi. Selles artiklis pĂŒĂŒame selgitada mĂ”ningaid nende veidrustest, mĂ”ista nende kummalist kĂ€itumist ja leida viise, kuidas igapĂ€evaelus probleemide vĂ€ltimiseks. TĂ”si, koostasin selle artikli ka teatud mĂ”ttes enese jaoks, kuna see toimib juhendina, millele oleks kerge keerulistes olukordades viidata. SeetĂ”ttu tĂ€iendame seda artiklit igal ajal, kui avastame uusi ĂŒllatusi kahtlastelt tĂŒĂŒpide poolt. Nii et teele, vĂ€simatuid andmebaaside uurijaid!

Dossier number ĂŒks. real/double precision/numeric/money

NĂ€iliselt on arvutĂŒĂŒbid probleemide poolest vĂ€hem ĂŒllatavad. Kuid mitte sugugi. SeetĂ”ttu alustamegi neist. Nii et


Oleme unustanud arvutada

SELECT 0.1::real = 0.1

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

Mis toimub? Asi on selles, et PostgreSQL tĂ”lgib tĂŒĂŒbita konstanti 0.1 tĂŒĂŒpi double precision ja ĂŒritab seda vĂ”rrelda 0.1 tĂŒĂŒpi real-iga. Need on tĂ€iesti erinevad vÀÀrtused! Probleem on tegelikult reaalarvude esitamises masinamuistis. Kuna 0.1 ei saa esitada lĂ”pliku binaardĂŒraadina (see on 0.0(0011) binaarsete nĂ€idistega), siis erineva tĂ€psusega numbrid erinevad omavahel, seega ka tulemus, et nad pole vĂ”rdsed. Üldiselt on see teema eraldi artikli vÀÀriline, rohkem ei kirjuta siin.

Kust tuleb viga?

SELECT double precision(1)

ERROR: sĂŒntaksiviga lĂ€himast "("
LINE 1: SELECT double precision(1)
                               ^
********** Viga **********
ERROR: sĂŒntaksiviga lĂ€himast "("
SQL-i olek: 42601
SĂŒmbol: 24

Paljud teavad, et PostgreSQL lubab funktsionaalset tĂŒĂŒbi konverteerimist. See tĂ€hendab, et saab kirjutada mitte ainult 1::int, vaid ka int(1), mis on ekvivalentne. Kuid see ei kehti tĂŒĂŒpide puhul, mille nimed koosnevad mitmest sĂ”nast! SeetĂ”ttu, kui soovite pöörata numbrilise vÀÀrtuse tĂŒĂŒbiks double precision funktsionaalses vormis, kasutage selle tĂŒĂŒbi aliast float8, see tĂ€hendab SELECT float8(1).

Mis on suurem kui lÔpmatus?

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

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

Nii et see on nii! Selgub, et on midagi, mis on suurem kui lÔpmatus, ja see on NaN! Sellega nÔustub PostgreSQL dokumentatsioon ausate silmadega, vÀites, et NaN on pÔhimÔtteliselt suurem igast teisest numbrist ning seega ka lÔpmatusest. Vastupidine kehtib ka -NaN-i puhul. Tere, matemaatika sÔbrad! Kuid tuleb meeles pidada, et kÔik see kehtib reaalnumberite kontekstis.

Silmade ĂŒmardamine

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

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

Veel ĂŒks ootamatu tere andmebaasilt. Ja taas tuleb meeles pidada, et tĂŒĂŒpide double precision ja numeric jaoks kehtivad erinevad ĂŒmardamisreeglid. Numeric puhul kehtib tavaline, kus 0,5 ĂŒmardatakse ĂŒlespoole, samas kui double precision puhul toimub 0,5 ĂŒmardamine lĂ€hima paarisarvuni.

Raha on midagi erilist

SELECT '10'::money::float8

ERROR: cannot cast type money to double precision
LINE 1: SELECT '10'::money::float8
                          ^
********** Viga **********
ERROR: cannot cast type money to double precision
SQL-state: 42846
Symbol: 19

PostgreSQL-i arvates ei ole raha reaalne number. MĂ”ned inimesed arvavad samuti. Peame siiski meeles pidama, et raha tĂŒĂŒp on vĂ”imalik pöörata ainult tĂŒĂŒbiks numeric, samamoodi on raha tĂŒĂŒbiks vĂ”imalik pöörata ainult numeric tĂŒĂŒpi. Ja sellega saab juba mĂ€ngida, nagu hing ihaldab. Kuid need ei ole enam need rahad.

Smallint ja sekveneerimise genereerimine

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.
********** Viga **********
ERROR: function generate_series(smallint, smallint, smallint) is not unique
SQL-state: 42725
NÀpunÀide: Could not choose a best candidate function. You might need to add explicit type casts.
Symbol: 18

PostgreSQL ei armasta pisiasjadega tegeleda. Millised jĂ€rjestused pĂ”hinevad smallint'il? int, vĂ€hemalt! SeetĂ”ttu, kui proovite tĂ€ita ĂŒlaltoodud pĂ€ringut, pĂŒĂŒab andmebaas vĂ€ikse arvu tĂŒĂŒpida muuks tĂ€isarvutĂŒĂŒbi, ja nĂ€eb, et selliseid tĂŒpplahtteid vĂ”ib olla mitu. Millist tĂŒpplahtet valida? Andmebaas ei oska seda otsustada ja seetĂ”ttu kukub see veaga kokku.

Fail number kaks. «char»/char/varchar/text

Iseloomulikke kummalisusi leidub ka sĂŒmbolitĂŒĂŒpides. Alustame nende tutvustamist.

Mis asja see on?

SELECT 'PETYA'::"char"
     , 'PETYA'::"char"::bytea
     , 'PETYA'::char
     , 'PETYA'::char::bytea

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

Mis asi on «char» tĂŒĂŒp, kes see kloun on? Me ei vaja selliseid... Sest ta teeseldab tavalisest char'ist, kuigi on jutumĂ€rkides. Erinevus tavalisest char'ist, mis ei ole jutumĂ€rkides, on see, et see vĂ€ljastab ainult esimese baitide stringi esindusest, samas kui tavaline char vĂ€ljastab esimese sĂŒmboli. Meie juhul on esimene sĂŒmbol tĂ€ht П, mille unicode'i esindus vĂ”tab 2 baitide, nagu nĂ€itab tulemuse konverteerimine tĂŒĂŒbiks bytea. «char» tĂŒĂŒp haarab ainult selle unicode'i esinduse esimese byte'i. Miks see tĂŒĂŒp vajalik on? PostgreSQL'i dokumentatsioon ĂŒtleb, et see on eriline tĂŒĂŒp, mida kasutatakse erilistel vajadustel. Nii et me tĂ”enĂ€oliselt ei vaja seda. Kuid vaata talle silma ja Ă€rge eksige, kui kohtate teda tema erilise kĂ€itumisega.

Liigne tĂŒhik. Silmast vĂ€lja, sĂŒdamest kaugemale.

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

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

Vaadake antud nĂ€idet. Ma tĂ”in kĂ”ik tulemused teadlikult tĂŒĂŒbiks bytea, et oleks selgelt nĂ€ha, mis seal on. Kus on lĂ”pus tĂŒhikud pĂ€rast ĂŒleminekut tĂŒĂŒbile varchar(6)? Dokumentatsioon vĂ€idab napilt: «Kui vÀÀrtus muudetakse sĂŒmbolitĂŒĂŒbist teiseks, siis lisatud tĂŒhikud kukuvad Ă€ra». Seda mitte-armastust tuleb meelde jĂ€tta. Ja mĂ€rkige, et kui stringi konstant jutumĂ€rkides muudetakse kohe tĂŒĂŒbiks varchar(6), siis lĂ”pp-tĂŒhikud sĂ€ilitavad.

Fail number kolm. json/jsonb

JSON on eraldi struktuur, mis elab oma elu. SeetĂ”ttu erinevad tema ĂŒksused ja PostgreSQL'i ĂŒksused veidi. Siin on mĂ”ned nĂ€ited.

Johnson ja Johnson. Tunne erinevus

SELECT 'null'::jsonb IS NULL

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

Asi on selles, et JSON-il on oma null-kĂ€sk, mis ei ole PostgreSQL-i NULL-i analoog. Samas vĂ”ib JSON-objekt ise omada vÀÀrtust NULL, seega vĂ€ljund SELECT null::jsonb IS NULL (pange tĂ€hele, et ĂŒhtegi ĂŒhtegi tĂŒhikute mĂ€rki ei ole) tagastab seekord true.

Üks tĂ€ht muudab kogu

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

Asi on selles, et json ja jsonb on tÀiesti erinevad struktuurid. json-is sÀilitatakse objekt nii nagu ta on, samas kui jsonb-s salvestatakse see juba indekseeritud struktuurina. Just seetÔttu, teises variandis, muudeti objekti vÀÀrtus vÔtmega 1 vÀÀrtuseks [1, 2, 3] [7, 8, 9], mis tuli strukturi lÔpus sama vÔtmega.

Vee nÀolt ei joo

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

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

PostgreSQL JSONB rakenduses muudab realiseeritud reaalsete arvude vorming, viies need klassikalise kujuga. JSON-tĂŒĂŒbi puhul seda ei juhtu. Veidi kummaline, kuid see on nii.

Toimiku number neli. kuupÀev/aeg/aeg

KuupĂ€eva/aegade tĂŒĂŒpidega on samuti teatud kummalisi asju. Vaatame neid. Juba ette öeldes, et mĂ”ned kĂ€itumise eripĂ€rad saavad selgemaks, kui mĂ”istetakse hĂ€sti, kuidas ajavöönditega töötamine toimib. Kuid see on ka teema eraldi artikli jaoks.

Minu su sinupooli ei mÔista

SELECT '08-Jan-99'::date

ERROR:  kuupÀev/aeg vÀli vÀÀrtus vÀlja vahemik: "08-Jan-99"
LINE 1: SELECT '08-Jan-99'::date
               ^
HINT:  VÔibolla on teil vaja erinevat "datestyle" seadistust.
********** Viga **********
ERROR: kuupÀev/aeg vÀli vÀÀrtus vÀlja vahemik: "08-Jan-99"
SQL-seisund: 22008
Viide: VÔibolla on teil vaja erinevat "datestyle" seadistust.
SĂŒmbol: 8

Tundub, et mis siin segadust tekitab? Kuid siiski ei mĂ”ista andmebaas, mida me siia esiteks seadsime — aasta vĂ”i pĂ€ev? Ja otsustab, et see on 99. jaanuar 2008, mis paneb selle aju plahvatama. Üldiselt tuleb kuupĂ€evi tekstiformaadis edastamisel vĂ€ga ettevaatlik olla, kui Ă”igesti andmebaas neid tuvastab (eriti analĂŒĂŒsida parameetrit datestyle kĂ€suga SHOW datestyle), kuna selles kĂŒsimuses vĂ”ivad ebaselgused olla vĂ€ga kallid.

Kust sa selline tulid?

VALI '04:05 Euroopa/Moskva'::aeg

VIGA:  vale sisendi sĂŒntaks tĂŒĂŒbi jaoks aeg: "04:05 Euroopa/Moskva"
REALI 1: VALI '04:05 Euroopa/Moskva'::aeg
               ^
********** Viga **********
VIGA: vale sisendi sĂŒntaks tĂŒĂŒbi jaoks aeg: "04:05 Euroopa/Moskva"
SQL-olek: 22007
SĂŒmbol: 8

Miks andmebaas ei saa arusaadavalt mÀÀratud aega mĂ”ista? Sest ajavööndile on antud mitte lĂŒhend, vaid tĂ€ielik nimetus, millel on tĂ€hendus ainult kuupĂ€eva kontekstis, kuna see arvestab ajavööndite ajaloo muutusi, ja see ei toimi ilma kuupĂ€evata. Ja ka aegade vormul tekitab kĂŒsimusi - mida programmeerija tegelikult silmas pidas? Seega on siin kĂ”ik loogiline, kui sĂŒveneda.

Mis tal viga on?

Kujutage ette olukorda. Teie tabelis on vĂ€li tĂŒĂŒbiga timestamptz. Soovite seda indekseerida. Kuid mĂ”istate, et selle vĂ€lja jĂ€rgi indeksi loomine ei ole alati Ă”igustatud, arvestades selle kĂ”rget valikulisust (peaaegu kĂ”ik selle tĂŒĂŒpi vÀÀrtused on unikaalsed). Seega otsustate vĂ€hendada indeksi valikulisust, muutes selle tĂŒĂŒbi kuupĂ€evaks. Ja saate ĂŒllatuse:

LOO INDEKS "iIdent-DateLastUpdate"
  PUBLIC."Ident" PEAL kasutades btree
  (("DTLastUpdate"::kuupÀev));

VIGA:  funktsioonid indeksivalemis peavad olema mÀrgistatud IMMUTABLE
********** Viga **********
VIGA: funktsioonid indeksivalemis peavad olema mÀrgistatud IMMUTABLE
SQL-olek: 42P17

Mis toimub? Asi on selles, et tĂŒĂŒbi timestamptz muutmiseks tĂŒĂŒbiks kuupĂ€ev kasutatakse sĂŒsteemi parameetri TimeZone vÀÀrtust, mis muudab tĂŒĂŒbimuundamise funktsiooni sĂ”ltuvaks konfigureeritavast parameetrist, st muutlikust (volatile). Sellised funktsioonid indeksis ei ole lubatud. Sellisel juhul tuleb selgelt mÀÀrata, millises ajavööndis tĂŒĂŒbimuundamine toimub.

Kui now ei ole sugugi now

Oleme harjunud, et now() tagastab praeguse kuupÀeva/aega, arvestades ajavööndit. Kuid vaadake jÀrgmisi pÀringuid:

ALUSTA TRANSAKTSIOONI;
VALI now();

            now
  timestamp koos ajavööndiga
-----------------------------
2019-11-26 13:13:04.271419+03

...

VALI now();

            now
  timestamp koos ajavööndiga
-----------------------------
2019-11-26 13:13:04.271419+03

...

VALI now();

            now
  timestamp koos ajavööndiga
-----------------------------
2019-11-26 13:13:04.271419+03

KOMMITTEERI;

KuupĂ€ev/aeg tagastatakse alati sama, olenemata sellest, kui kaua on möödunud eelmisest pĂ€ringust! Miks? Sest now() ei ole praegune aeg, vaid jooksva tehingu algusaeg. SeetĂ”ttu ei muutu see tehingu raames. Iga pĂ€ring, mis kĂ€ivitatakse tehingu raames, ĂŒmbritsetakse vaikimisi tehinguga, mistĂ”ttu me ei pane tĂ€hele, et lihtne pĂ€ring SELECT now(); tagastab tegelikult mitte praeguse aja... Kui soovite saada tĂ”elist praegust aega, peate kasutama funktsiooni clock_timestamp().

Dossier number viis. bit

Veidi kummaline

SELECT '111'::bit(4)

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

Mis kĂŒljest tuleks bitti lisada tĂŒĂŒbi suurendamisel? Tundub, et vasakult. Kuid ainult andmebaasil on selle kohta teine arvamus. Olge ettevaatlik: kui tĂŒĂŒbi konverteerimisel on bittide arvud vale, saate midagi muud, kui soovisite. See kehtib nii bittide lisamise kui ka bittide kĂ€rpimise kohta. Samuti paremal...

Dossier number kuus. Massiivid

I isegi NULL ei tulnud vÀlja

SELECT ARRAY[1, 2] || NULL

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

Nagu normaalsed inimesed, kellel on SQL-alused, ootame, et selle avaldise tulemuseks on NULL. Kuid ei. Tagastatakse massiiv. Miks? Sest antud juhul teisendab andmebaas NULL'i tĂ€isarvude massiiviks ja kutsub vaikimisi ĂŒles foonktsiooni array_cat. Kuid jÀÀb ebaselgeks, miks see "massiivne kass" massiivi ei nulli. Sellise kĂ€itumise mĂ€letamine on oluline.

KokkuvĂ”ttes. Kummalisi asju on palju. Enamiku nende seas ei ole need piisavalt kriitilised, et rÀÀkida ĆĄokeerivast mitteadekvaatsest kĂ€itumisest. Teised on selgitatavad mugavuse kasutamise vĂ”i nende rakendamise sagedusega erinevates olukordades. Kuid samas on palju ĂŒllatusi. SeetĂ”ttu tuleb neist teada. Kui leiate veel mingeid kummalisi vĂ”i ebatavalisi kĂ€itumisi mingite tĂŒĂŒpide seas, kirjutage kommentaaridesse, tĂ€iendan hea meelega olemasolevaid dossiersid.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster