Կասկածելի տեսակներ

Նրանց արտաքինի մեջ կասկածելի ոչինչ չկա։ Ավելին, դրանք ձեզ նույնիսկ լավ և երկար ժամանակ ծանոթ են թվում։ Բայց դա միայն այնքան ժամանակ, քանի դեռ չեք ստուգել դրանք: Այստեղ նրանք ցույց են տալիս իրենց նենգ էությունը՝ աշխատելով բոլորովին այլ կերպ, քան դուք սպասում էիք։ Եվ երբեմն նրանք անում են մի բան, որից մազերը բիզ են տալիս, օրինակ՝ կորցնում են իրենց վստահված գաղտնի տվյալները: Նրանց հետ առերեսվելիս նրանք պնդում են, որ միմյանց չեն ճանաչում, թեև ստվերում նրանք քրտնաջան աշխատում են նույն գլխարկի տակ։ Ժամանակն է վերջապես բերել դրանք մաքուր ջրի: Եկեք զբաղվենք նաև այս կասկածելի տեսակներով։

PostgreSQL-ում տվյալների մուտքագրումը, չնայած իր ողջ տրամաբանությանը, երբեմն շատ տարօրինակ անակնկալներ է մատուցում: Այս հոդվածում մենք կփորձենք պարզաբանել նրանց որոշ տարօրինակություններ, հասկանալ նրանց տարօրինակ վարքի պատճառը և հասկանալ, թե ինչպես չբախվել առօրյա պրակտիկայում խնդիրների: Ճիշտն ասած, ես այս հոդվածը կազմել եմ նաև որպես տեղեկագիր ինձ համար, տեղեկագիր, որին կարելի է հեշտությամբ դիմել վիճելի դեպքերում: Ուստի այն կհամալրվի, քանի որ կասկածելի տեսակներից նոր անակնկալներ են հայտնաբերվում։ Այսպիսով, եկեք գնանք, օ՜, անխոնջ տվյալների բազայի հետքեր:

Թիվ մեկ գործ. իրական / կրկնակի ճշգրտություն / թվային / փող

Թվում է, թե թվային տեսակները ամենաքիչ խնդրահարույց են վարքագծի անակնկալների առումով։ Բայց անկախ նրանից, թե ինչպես է դա: Այսպիսով, եկեք սկսենք նրանցից: Այսպիսով…

Մոռացել եք հաշվել

SELECT 0.1::real = 0.1

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

Ինչ է պատահել? Խնդիրն այն է, որ PostgreSQL-ը փոխակերպում է չտիպված 0.1 հաստատունը կրկնակի ճշգրտության և փորձում է այն համեմատել իրական տիպի 0.1-ի հետ։ Եվ սրանք բոլորովին այլ իմաստներ են: Գաղափարը մեքենայի հիշողության մեջ իրական թվեր ներկայացնելն է: Քանի որ 0.1-ը չի կարող ներկայացվել որպես վերջավոր երկուական կոտորակ (այն կլիներ 0.0(0011) երկուականում), տարբեր բիթային խորություններ ունեցող թվերը տարբեր կլինեն, հետևաբար, նրանք հավասար չեն: Ընդհանրապես սա առանձին հոդվածի թեմա է, այստեղ ավելի մանրամասն չեմ գրի։

Որտեղի՞ց է առաջանում սխալը:

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-ի՝ փողը իրական թիվ չէ։ Ըստ որոշ անհատների՝ նույնպես. Մենք պետք է հիշենք, որ փողի տեսակը կարելի է գցել միայն թվային տեսակին, ինչպես որ միայն թվային տեսակը կարելի է գցել փողի տեսակին: Բայց հիմա դուք կարող եք խաղալ դրա հետ այնպես, ինչպես ձեր սիրտը ցանկանում է: Բայց դա նույն փողը չի լինի:

Փոքր և հաջորդականության սերունդ

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-ը չի սիրում ժամանակ վատնել մանրուքների վրա: Որո՞նք են այս հաջորդականությունները, որոնք հիմնված են մանրուքների վրա: int, ոչ պակաս! Հետևաբար, երբ փորձում է կատարել վերը նշված հարցումը, տվյալների բազան փորձում է փոքր թվեր փոխանցել որևէ այլ ամբողջ տիպի և տեսնում է, որ կարող են լինել մի քանի այդպիսի casts: Ո՞ր դերասանական կազմն ընտրել: Նա չի կարող որոշել դա, և, հետևաբար, վթարի է ենթարկվում սխալով:

Թիվ երկու ֆայլ. «char»/char/varchar/տեքստ

Մի շարք տարօրինակություններ կան նաև կերպարների տեսակներում։ Եկեք նրանց էլ ճանաչենք։

Ինչպիսի՞ հնարքներ են դրանք:

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

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

Սա ի՞նչ տեսակի «չար» է, սա ի՞նչ ծաղրածու է։ Մեզ դրանք պետք չեն... Որովհետև այն ձևացնում է, թե սովորական կերպար է, թեև չակերտների մեջ է: Եվ այն տարբերվում է սովորական նիշից, որն առանց չակերտների է, քանի որ այն թողարկում է տողի ներկայացման միայն առաջին բայթը, մինչդեռ սովորական նիշը թողարկում է առաջին նիշը: Մեր դեպքում առաջին նիշը P տառն է, որը յունիկոդի ներկայացման մեջ զբաղեցնում է 2 բայթ, ինչի մասին վկայում է արդյունքը բայթի տեսակի փոխակերպումը։ Իսկ «char» տեսակը վերցնում է այս յունիկոդի միայն առաջին բայթը: Այդ դեպքում ինչու է անհրաժեշտ այս տեսակը: 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

Նայեք բերված օրինակին: Ես բոլոր արդյունքները հատուկ փոխակերպեցի բայթի տեսակի, որպեսզի պարզ երևա, թե ինչ կա։ Որտե՞ղ են վազքուղին (6) ձուլելուց հետո հետագծերը: Փաստաթղթում հակիրճ ասվում է. «Նիշերի արժեքը մեկ այլ նիշի տեսակին փոխանցելիս, հետին բացատը հանվում է»: Այս հակակրանքը պետք է հիշել: Եվ նկատի ունեցեք, որ եթե մեջբերված լարային հաստատունը ուղղակիորեն գցվում է varchar(6) տիպին, ապա հետին բացատները պահպանվում են: Այսպիսին են հրաշքները.

Թիվ երեք ֆայլ. json/jsonb

JSON-ը առանձին կառույց է, որն ապրում է իր կյանքով: Հետևաբար, նրա և PostgreSQL-ի կազմավորումները մի փոքր տարբերվում են: Ահա օրինակներ.

Ջոնսոն և Ջոնսոն. զգացեք տարբերությունը

SELECT 'null'::jsonb IS NULL

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

Բանն այն է, որ JSON-ն ունի իր սեփական զրոյական էությունը, որը PostgreSQL-ում NULL-ի անալոգը չէ։ Միևնույն ժամանակ, JSON օբյեկտն ինքնին կարող է ունենալ NULL արժեքը, ուստի SELECT null::jsonb IS NULL արտահայտությունը (նկատի ունեցեք միայնակ չակերտների բացակայությունը) այս անգամ կվերադարձնի ճշմարիտ:

Մեկ տառը փոխում է ամեն ինչ

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]-ի, որը կառույցի մեջ մտավ հենց վերջում՝ նույն բանալիով։

Դեմքից ջուր մի խմեք

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 տեսակի համար: Մի քիչ տարօրինակ է, բայց նա իրավացի է:

Ֆայլ թիվ չորս. ամսաթիվ/ժամ/ժամացույց

Կան նաև տարօրինակություններ ամսաթվի/ժամանակի տեսակների հետ կապված: Եկեք նայենք նրանց: Անմիջապես վերապահում անեմ, որ վարքագծային որոշ առանձնահատկություններ պարզ կդառնան, եթե լավ եք հասկանում ժամային գոտիների հետ աշխատելու էությունը: Բայց սա նաև առանձին հոդվածի թեմա է։

Իմ քոնը չի հասկանում

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-ն է, ինչն իր գլխին փչում է։ Ընդհանուր առմամբ, ամսաթվերը տեքստային ձևաչափով փոխանցելիս պետք է շատ ուշադիր ստուգել, ​​թե որքանով է տվյալների բազան ճիշտ ճանաչել դրանք (մասնավորապես, վերլուծել datestyle պարամետրը SHOW 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 տիպը ամսաթվի տիպին փոխանցելու համար օգտագործվում է 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() ֆունկցիան:

Ֆայլ թիվ հինգ. քիչ

Մի քիչ տարօրինակ

SELECT '111'::bit(4)

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

Ո՞ր կողմը պետք է ավելացնել բիթերը տիպի ընդլայնման դեպքում: Այն կարծես ձախ կողմում է: Սակայն այս հարցում այլ կարծիք ունի միայն բազան։ Զգույշ եղեք. եթե թվանշանների թիվը չի համընկնում տեսակը ձուլելիս, դուք չեք ստանա այն, ինչ ցանկանում էիք: Սա վերաբերում է ինչպես աջին բիթերի ավելացմանը, այնպես էլ բիթերի կտրմանը: Նաև աջ...

Թիվ վեց ֆայլ. Զանգվածներ

Նույնիսկ NULL-ը չկրակեց

SELECT ARRAY[1, 2] || NULL

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

Քանի որ նորմալ մարդիկ մեծացել են SQL-ով, մենք ակնկալում ենք, որ այս արտահայտության արդյունքը կլինի NULL: Բայց դա չկար։ Զանգվածը վերադարձվում է: Ինչո՞ւ։ Քանի որ այս դեպքում բազան NULL-ը փոխանցում է ամբողջ թվային զանգվածին և անուղղակիորեն կանչում է array_cat ֆունկցիան։ Բայց դեռևս անհասկանալի է մնում, թե ինչու այս «զանգվածային կատուն» չի զրոյացնում զանգվածը: Այս պահվածքը նույնպես պարզապես պետք է հիշել:

Ամփոփել. Շատ տարօրինակ բաներ կան։ Նրանցից շատերը, իհարկե, այնքան էլ քննադատական ​​չեն, որ խոսեն բացահայտ ոչ պատշաճ պահվածքի մասին։ Իսկ մյուսները բացատրվում են օգտագործման հեշտությամբ կամ որոշակի իրավիճակներում դրանց կիրառելիության հաճախականությամբ: Բայց միևնույն ժամանակ անակնկալները շատ են։ Հետեւաբար, դուք պետք է իմանաք դրանց մասին: Եթե ​​որևէ այլ տարօրինակ կամ անսովոր բան եք գտնում ցանկացած տեսակի վարքագծի մեջ, գրեք մեկնաբանություններում, ես ուրախ կլինեմ ավելացնել դրանցում առկա դոսյեներին:

Source: www.habr.com

Добавить комментарий