Grunsamlegar tegundir

Það er ekkert grunsamlegt við útlit þeirra. Þar að auki virðast þeir jafnvel kunnugir þér vel og í langan tíma. En það er bara þangað til þú athugar þá. Þetta er þar sem þeir sýna lúmsk eðli sitt, vinna allt öðruvísi en þú bjóst við. Og stundum gera þeir eitthvað sem fær hárið til að rísa - til dæmis missa þeir leynileg gögn sem þeim er trúað fyrir. Þegar þú mætir þeim halda þau því fram að þau þekkist ekki, þó að í skugganum vinni þau hörðum höndum undir sama hettunni. Það er kominn tími til að koma þeim loksins í hreint vatn. Við skulum líka takast á við þessar grunsamlegu tegundir.

Gagnasláttur í PostgreSQL, þrátt fyrir alla rökfræði þess, kemur stundum mjög undarlega á óvart. Í þessari grein munum við reyna að skýra nokkur einkenni þeirra, skilja ástæðuna fyrir undarlegri hegðun þeirra og skilja hvernig á ekki að lenda í vandræðum í daglegu starfi. Satt að segja tók ég þessa grein saman sem eins konar uppflettirit fyrir sjálfan mig, uppflettirit sem auðvelt var að vísa í í umdeildum málum. Þess vegna verður það endurnýjað þegar nýjar óvæntar gerðir koma í ljós. Svo, við skulum fara, ó óþreytandi gagnagrunnsrekningar!

Málsskjöl númer eitt. raunveruleg / tvöföld nákvæmni / tölur / peningar

Svo virðist sem tölulegar tegundir séu minnst vandamál hvað varðar óvæntar hegðun. En hvernig sem það er. Svo skulum við byrja á þeim. Svo…

Gleymdi hvernig á að telja

SELECT 0.1::real = 0.1

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

Hvað er að? Vandamálið er að PostgreSQL breytir óritaða fastanum 0.1 í tvöfalda nákvæmni og reynir að bera hann saman við 0.1 af raunverulegri gerð. Og þetta eru allt aðrar merkingar! Hugmyndin er að tákna rauntölur í vélaminni. Þar sem ekki er hægt að tákna 0.1 sem endanlegt tvíundarbrot (það væri 0.0(0011) í tvíundi), verða tölur með mismunandi bitadýpt ólíkar, þess vegna afleiðingin að þær eru ekki jafnar. Almennt séð er þetta efni fyrir sérstaka grein; ég mun ekki skrifa nánar hér.

Hvaðan kemur villa?

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

Margir vita að PostgreSQL leyfir virka nótnaskrift fyrir gerð steypu. Það er, þú getur skrifað ekki aðeins 1::int, heldur einnig int(1), sem mun vera jafngilt. En ekki fyrir tegundir þar sem nöfnin samanstanda af nokkrum orðum! Þess vegna, ef þú vilt varpa tölugildi til að tvöfalda nákvæmni gerð á virku formi, notaðu samnefni þessarar tegundar float8, það er, SELECT float8(1).

Hvað er stærra en óendanleiki?

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

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

Sjáðu hvernig það er! Það kemur í ljós að það er eitthvað stærra en óendanleiki, og það er NaN! Á sama tíma lítur PostgreSQL skjölin á okkur heiðarlegum augum og heldur því fram að NaN sé augljóslega hærri en nokkur önnur tala, og þar af leiðandi óendanlegt. Hið gagnstæða er líka satt fyrir -NaN. Halló, stærðfræðiunnendur! En við verðum að muna að allt þetta starfar í samhengi við rauntölur.

Ávalt auga

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

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

Enn ein óvænt kveðja frá herstöðinni. Aftur, mundu að tvöföld nákvæmni og tölulegar gerðir hafa mismunandi námundunaráhrif. Fyrir tölustafi - þá venjulega, þegar 0,5 er námundað upp, og fyrir tvöfalda nákvæmni - er 0,5 námundað í átt að næstu sléttu heiltölu.

Peningar eru eitthvað sérstakt

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

Samkvæmt PostgreSQL eru peningar ekki raunveruleg tala. Að sögn sumra einstaklinga líka. Við þurfum að muna að það er aðeins hægt að steypa peningategundinni í tölutegundina, eins og aðeins tölutegundinni er hægt að steypa í peningategundina. En nú geturðu leikið þér með það eins og hjartað þráir. En það verður ekki sami peningurinn.

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 líkar ekki við að eyða tíma í smáatriði. Hverjar eru þessar raðir byggðar á smallint? int, hvorki meira né minna! Þess vegna, þegar reynt er að framkvæma ofangreinda fyrirspurn, reynir gagnagrunnurinn að varpa smallint í einhverja aðra heiltölugerð og sér að það geta verið nokkrar slíkar steypur. Hvaða leikara á að velja? Hún getur ekki ákveðið þetta og hrynur því með villu.

Skrá númer tvö. "char"/char/varchar/texti

Ýmis einkenni eru einnig til staðar í persónugerðum. Við skulum kynnast þeim líka.

Hvers konar brellur eru þetta?

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

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

Hvaða tegund af "bleikju" er þetta, hvers konar trúður er þetta? Við þurfum þær ekki... Vegna þess að hún þykist vera venjuleg bleikja, þó hún sé innan gæsalappa. Og það er frábrugðið venjulegri bleikju, sem er án gæsalappa, að því leyti að hún gefur aðeins út fyrsta bæti strengjaframsetningarinnar, en venjuleg bleikja gefur út fyrsta stafinn. Í okkar tilviki er fyrsti stafurinn stafurinn P, sem í unicode framsetningunni tekur upp 2 bæti, eins og sést af því að breyta niðurstöðunni í bætitegundina. Og „char“ tegundin tekur aðeins fyrsta bætið af þessari unicode framsetningu. Af hverju þarf þá þessa tegund? PostgreSQL skjölin segja að þetta sé sérstök tegund sem notuð er fyrir sérþarfir. Það er því ólíklegt að við þurfum þess. En líttu í augun á honum og þér mun ekki skjátlast þegar þú hittir hann með sinni sérstöku hegðun.

Auka rými. Út úr augsýn, úr huga

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

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

Skoðaðu dæmið sem gefið er. Ég breytti öllum niðurstöðunum sérstaklega í bætitegundina, þannig að það væri greinilega sýnilegt hvað var þar. Hvar eru slóðbilin eftir kast í varchar(6)? Í skjölunum segir í stuttu máli: "Þegar gildi persónunnar er varpað yfir á aðra persónutegund er aftan bili hent." Þessa óþokka verður að muna. Og athugaðu að ef tilvitnuð strengjafasti er varpað beint á gerð varchar(6), þá varðveitast aftari bilin. Slík eru kraftaverkin.

Skrá númer þrjú. json/jsonb

JSON er sérstakt mannvirki sem lifir sínu eigin lífi. Þess vegna eru einingar þess og PostgreSQL aðeins öðruvísi. Hér eru dæmi.

Jónsson og Jónsson. finna muninn

SELECT 'null'::jsonb IS NULL

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

Málið er að JSON hefur sína eigin núlleiningu, sem er ekki hliðstæða NULL í PostgreSQL. Á sama tíma gæti JSON hluturinn sjálfur haft gildið NULL, þannig að tjáningin SELECT null::jsonb ER NULL (athugið að gæsalappir eru ekki til) mun skila satt að þessu sinni.

Einn stafur breytir öllu

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

Málið er að json og jsonb eru gjörólík mannvirki. Í json er hluturinn geymdur eins og hann er og í jsonb er hann þegar geymdur í formi þáttaðrar, verðtryggðrar uppbyggingar. Þess vegna var gildi hlutarins í öðru tilvikinu skipt út fyrir lykil 1 úr [1, 2, 3] í [7, 8, 9], sem kom inn í bygginguna alveg í lokin með sama lykli.

Ekki drekka vatn úr andliti þínu

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

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

PostgreSQL í JSONB útfærslu sinni breytir sniði rauntalna og færir þær í klassískt form. Þetta gerist ekki fyrir JSON gerð. Svolítið skrítið, en hann hefur rétt fyrir sér.

Skrá númer fjögur. dagsetning/tími/tímastimpill

Það eru líka nokkrar undarlegar með dagsetningar-/tímategundum. Við skulum skoða þær. Leyfðu mér að taka strax fyrirvara á því að sumir hegðunareiginleikar verða skýrir ef þú skilur vel kjarna þess að vinna með tímabelti. En þetta er líka efni fyrir sérstaka grein.

Mitt þitt skilur ekki

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

Það virðist sem hvað er óskiljanlegt hér? En gagnagrunnurinn skilur samt ekki hvað við setjum í fyrsta sæti hér - árið eða daginn? Og hún ákveður að það sé 99. janúar 2008, sem kemur henni í opna skjöldu. Almennt séð, þegar þú sendir dagsetningar á textasniði, þarftu að athuga mjög vel hversu rétt gagnagrunnurinn þekkti þær (sérstaklega greindu datestyle færibreytuna með SHOW datestyle skipuninni), þar sem tvíræðni í þessu efni getur verið mjög dýr.

Hvaðan fékkstu þetta?

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

Af hverju getur gagnagrunnurinn ekki skilið þann tíma sem er sérstaklega tilgreindur? Vegna þess að tímabeltið hefur ekki skammstöfun, heldur fullt nafn, sem er skynsamlegt aðeins í samhengi við dagsetningu, þar sem það tekur mið af sögu tímabeltisbreytinga og það virkar ekki án dagsetningar. Og sjálft orðalag tímalínunnar vekur spurningar - hvað átti forritarinn eiginlega við? Þess vegna er allt rökrétt hér, ef þú skoðar það.

Hvað er að honum?

Ímyndaðu þér ástandið. Þú ert með reit í töflunni þinni með tegundinni timestamptz. Þú vilt verðtryggja það. En þú skilur að það er ekki alltaf réttlætanlegt að byggja upp vísitölu á þessu sviði vegna mikillar sértækni (næstum öll gildi af þessari gerð verða einstök). Þannig að þú ákveður að draga úr sértækni vísitölunnar með því að steypa gerðinni til dagsetningar. Og þú kemur á óvart:

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

Hvað er að? Staðreyndin er sú að til að varpa timestamptz tegund í dagsetningartegund er gildi TimeZone kerfisfæribreytunnar notað, sem gerir tegundaumreikningsfallið háð sérsniðinni færibreytu, þ.e. óstöðugur. Slíkar aðgerðir eru ekki leyfðar í vísitölunni. Í þessu tilviki verður þú að tilgreina sérstaklega á hvaða tímabelti tegundarkastið er framkvæmt.

Þegar nú er alls ekki einu sinni núna

Við erum vön því að now() skilar núverandi dagsetningu/tíma, að teknu tilliti til tímabeltisins. En skoðaðu eftirfarandi fyrirspurnir:

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;

Dagsetningin/tíminn er sá hinn sami, sama hversu langur tími er liðinn frá fyrri beiðni! Hvað er að? Staðreyndin er sú að now() er ekki núverandi tími, heldur upphafstími núverandi viðskipta. Þess vegna breytist það ekki innan viðskiptanna. Allar fyrirspurnir sem eru settar af stað utan umfangs viðskipta er vafin inn í færslu óbeint, sem er ástæðan fyrir því að við tökum ekki eftir því að tíminn skilar sér með einföldum SELECT now(); reyndar ekki núverandi... Ef þú vilt fá heiðarlegan núverandi tíma þarftu að nota clock_timestamp() aðgerðina.

Skrá númer fimm. smá

Skrítið svolítið

SELECT '111'::bit(4)

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

Hvaða hlið ætti að bæta við bitunum ef um er að ræða tegundarframlengingu? Það virðist vera til vinstri. En aðeins grunnurinn hefur aðra skoðun á þessu máli. Vertu varkár: ef fjöldi tölustafa passar ekki þegar þú steyptir tegund færðu ekki það sem þú vildir. Þetta á bæði við um að bæta bitum við hægri og klippa bita. Einnig til hægri...

Skrá númer sex. Fylki

Jafnvel NULL kveikti ekki

SELECT ARRAY[1, 2] || NULL

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

Sem venjulegt fólk alið upp við SQL, gerum við ráð fyrir að niðurstaða þessarar tjáningar sé NULL. En það var ekki þar. Fylki er skilað. Hvers vegna? Vegna þess að í þessu tilviki varpar grunnurinn NULL í heiltalna fylki og kallar óbeint á array_cat fallið. En það er enn óljóst hvers vegna þessi „fylkisköttur“ endurstillir ekki fylkið. Þessa hegðun þarf líka bara að muna.

Tekið saman. Það er fullt af undarlegum hlutum. Flestir þeirra eru auðvitað ekki svo gagnrýnir að tala um hróplega óviðeigandi hegðun. Og aðrir eru útskýrðir af auðveldri notkun eða tíðni notkunar þeirra við ákveðnar aðstæður. En á sama tíma kemur margt á óvart. Þess vegna þarftu að vita um þá. Ef þér finnst eitthvað annað undarlegt eða óvenjulegt í hegðun hvers kyns, skrifaðu í athugasemdirnar, ég mun vera fús til að bæta við skjölin sem eru tiltæk um þau.

Heimild: www.habr.com

Bæta við athugasemd