3.6 JSON tipi ve SQL/JSON işlevleri

<< İçindekileri Görüntülemek İçin Tıklayın >>

Yer:  Teknik Kılavuzlar > IVEDI SQL Kullanıcı Kılavuzu > 3. Veri tipleri >

3.6 JSON tipi ve SQL/JSON işlevleri

JSON metin değil, kendi başına bir tiptir. Bir JSON yerine yazılan değer yazılırken ayrıştırılır; geçersizse saklanmaz ve Invalid JSON text hatası verir (SQLSTATE 22032). Saklanan biçim kanoniktir ve üç kuralı vardır:

  • Boşluk korunmaz.
  • Nesne anahtarları sıralanır.
  • Aynı anahtar birden çok kez yazıldıysa sonuncusu kalır.

Sayılar yeniden biçimlenmez: 1.50 öyle kalır. Bu üç kural PostgreSQL'in jsonb tipiyle aynıdır; PostgreSQL arka ucunda sütun JSONB olur, öteki motorlarda metin olarak saklanır ve doğrulamayı IVEDI yapar.

create table belge(id integer not null primary key, doc json);

insert into belge values (1, '{ "b" : 2 , "a" : 1 , "a" : 9 }');

select cast(doc as varchar(100)) from belge;   --> {"a":9,"b":2}

3.6.1 Belgeyi okumak: JSON_VALUE, JSON_QUERY, JSON_EXISTS

Belgeyi okumak için üç işlev vardır. Üçü de belgeyi JSON ya da düz metin olarak alabilir:

YazımNotlar
JSON_VALUE(<belge>, <yol>)Yolun ulaştığı skaler değeri metin olarak verir. Yol bir nesneye ya da diziye ulaşırsa sonuç NULL'dur; onun için JSON_QUERY vardır. JSON null değeri SQL NULL olur. JSON mantıksal değeri metin sonuçta JSON yazımıyla döner ('true' / 'false'); RETURNING BOOLEAN verilirse mantıksal kalır.
JSON_QUERY(<belge>, <yol>)Yolun ulaştığı düğümü kanonik JSON metni olarak verir; nesne, dizi ya da skaler olabilir.
JSON_EXISTS(<belge>, <yol>)Yol bir düğüme ulaşıyor mu: TRUE / FALSE. Belge ya da yol NULL ise sonuç NULL'dur.

3.6.2 Yol dili

Yol dili şunlardan oluşur: $ (belgenin kökü), .ad ve ["ad"] (üye erişimi), .* (bütün üyeler), [n] (dizi indeksi, sıfırdan başlar), [n to m] (aralık), [*] (bütün öğeler), [last] ve [last - n] (sondan sayma), köşeli parantez içinde virgüllü liste ([0, 2 to 4]), ?( … ) süzgeci ve başa yazılan lax/strict kipi. Tanınmayan bir yazım sessizce boş dönmez, Invalid SQL/JSON path hatası verir. Bu bir yazım hatasıdır, veri hatası değil: SQLSTATE 42601 (syntax error); standardın 22… sınıfı SQL/JSON veri durumlarına ayrılmıştır. Öğe metotları (.type(), .size(), .keyvalue()) bilerek yoktur: karşılıkları bu motorda SQL tarafındadır.

Süzgeç

?( <yüklem> ) geldiği noktadaki düğümleri eler. Yüklemde @ o anki düğüm, $ belgenin kökü demektir:

YazımNotlar
== != <> < <= > >=Karşılaştırma varoluşsaldır: iki taraf da birer dizi düğüm olabilir ve bir çift bile doğruysa yüklem doğrudur. Tipler uyuşmuyorsa sonuç bilinmeyendir ve süzgeç geçmez; sessiz bir tip zorlaması yapılmaz.
&& || !Mantıksal bağlaçlar; parantezle gruplanabilir.
exists( <yol> )Yol bir düğüme ulaşıyor mu. Yapı hatasını yutar: strict kipinde bile hata değil false verir.
starts withDizge öneki.
like_regex "<desen>" [flag "<bayraklar>"]Düzenli ifade (XQuery söz dizimi, SUBSTRING_REGEX ailesiyle aynı).
select * from json_table(:doc, '$.k[*]?(@.aktif == true && @.yas > 26)'
    columns (ad varchar(20) path '$.ad')) jt;

--  süzgeç içinde $ belgenin köküdür
'$.k[*]?(@.yas > $.esik)'
'$.k[*]?(@.ad starts with "A")'
'$.k[*]?(exists(@.telefon))'

lax ve strict

Yolun başına lax ya da strict yazılabilir; öndeğer lax'tır. İki kipin tek farkı, yapı uymadığında ne olduğudur:

Durumlax (öndeğer)strict
Dizi üzerinde .adDizi bir düzey açılır, ad her öğede aranırHata
Dizi olmayanda [0] / [*]Değer tek öğeli dizi sayılırHata
Sınır dışı indeks, bulunamayan üyeSessizce boşHata
select json_query(:doc, 'lax $.k.ad'    with array wrapper);  --> ["Ali","Veli"]
select json_query(:doc, 'strict $.k[*].ad' with array wrapper);  --> ["Ali","Veli"]
select json_value(:doc, 'strict $.k.ad');                        --> NULL (NULL ON ERROR)
Not

strict kipinin yapı hatası bir veri hatasıdır: ON ERROR onu yakalar (öndeğer NULL ON ERROR olduğu için sessizce NULL olur) ve ERROR ON ERROR yazılırsa hata olarak çıkar. Standardın üç ayrı durumu üç ayrı SQLSTATE taşır: dizi beklenirken dizi olmayan SQL/JSON array not found (22039), bulunamayan ya da nesne olmayanda aranan üye SQL/JSON member not found (2203A), sınır dışı altsimge invalid SQL/JSON subscript (22033). Bir yol yazım hatası ise bunun dışındadır ve hiçbir zaman yutulmaz.

Birden çok düğüm

Joker, aralık ya da süzgeç içeren bir yol birden çok düğüme ulaşabilir. JSON_EXISTS bunu olduğu gibi okur (bir düğüm bile varsa TRUE), ama JSON_VALUE ile JSON_QUERY tek bir değer üretmek zorundadır: birden çok eşleşme standardın kuralı gereği hata verir. Birden çok düğümü satırlara açmak JSON_TABLE'ın işidir.

select json_value(doc, '$.adres.il')       from belge;   --> Ankara
select json_value(doc, '$.etiketler[1]')   from belge;   --> b
select json_query(doc, '$.adres')          from belge;   --> {"il":"Ankara","posta":"06000"}
select json_exists(doc, '$.adres.posta')   from belge;   --> TRUE

3.6.3 Yan tümceler: RETURNING, ON EMPTY, ON ERROR, ARRAY WRAPPER

Üç işlev de standardın yan tümcelerini kabul eder:

JSON_EXISTS(<belge>, <yol> [ TRUE | FALSE | UNKNOWN | ERROR ON ERROR ])

JSON_VALUE (<belge>, <yol> [ RETURNING <tip> ]
             [ (NULL | ERROR | DEFAULT <ifade>) ON EMPTY ]
             [ (NULL | ERROR | DEFAULT <ifade>) ON ERROR ])

JSON_QUERY (<belge>, <yol> [ RETURNING <tip> ]
             [ WITHOUT [ARRAY] WRAPPER
             | WITH [CONDITIONAL|UNCONDITIONAL] [ARRAY] WRAPPER ]
             [ (NULL | ERROR | EMPTY ARRAY | EMPTY OBJECT) ON EMPTY ]
             [ (NULL | ERROR | EMPTY ARRAY | EMPTY OBJECT) ON ERROR ])

Öndeğerler standardınkidir: NULL ON EMPTY ve NULL ON ERROR; JSON_EXISTS'te FALSE ON ERROR. Yani bozuk bir belge metni bu işlevlerde hata değil NULL verir; hata isteniyorsa ERROR ON ERROR yazılır. (Bozuk metni bir JSON sütununa yazmak ayrı şeydir ve orada hata verir.)

select json_value('{bozuk}', '$.a');                              --> NULL
select json_value('{bozuk}', '$.a' error on error);               --> hata
select json_value('{"a":42}', '$.a' returning integer);           --> 42
select json_value('{"a":"abc"}', '$.a' returning integer
                                 default -1 on error);            --> -1
select json_value('{"a":1}', '$.yok' default 'yok' on empty);     --> yok

select json_query('{"a":[1,2,3]}', '$.a[*]' with array wrapper);  --> [1,2,3]
select json_query('{"a":1}', '$.yok' empty array on empty);       --> []
select json_exists('{bozuk}', '$.a' unknown on error);            --> NULL

RETURNING sonucu istenen tipe çevirir ve bu dönüşümün hatası da ON ERROR'un kapsamındadır; dışarıdan bir CAST ile sarmak aynı şey değildir, o hata fırlatır. WITH CONDITIONAL ARRAY WRAPPER sonucu yalnızca zaten tek bir dizi ya da nesne değilse diziye sarar; sarmalayıcı istenmediğinde yolun birden çok düğüme ulaşması bir veri hatasıdır ve ON ERROR'a düşer.

Not

Bir yol yazım hatası bu kapsamda değildir: ON ERROR onu yutmaz, her zaman dışarı çıkar. Yutulan şey veri hatalarıdır: bozuk belge, boş sonuç, birden çok eşleşme, dönüşüm hatası.

3.6.4 IS JSON yüklemi

Bir metnin geçerli JSON olup olmadığı hata almadan sorulabilir; yüklemin varlık sebebi budur:

<ifade> IS [NOT] JSON [ VALUE | SCALAR | ARRAY | OBJECT ] [ WITH | WITHOUT UNIQUE KEYS ]

Geçersiz metin hata değil FALSE verir. Girdi NULL ise sonuç bilinmeyendir (NULL); bu IS NOT JSON için de böyledir. Ekler yazılmazsa öndeğer VALUE (yani "geçerli bir belge mi") ve WITHOUT UNIQUE KEYS'tir.

select '{"a":1}'     is json;                     --> TRUE
select '{bozuk}'     is json;                     --> FALSE
select '42'          is json scalar;              --> TRUE
select '[1,2]'       is json object;              --> FALSE
select '{"a":1,"a":2}' is json with unique keys;  --> FALSE
Not

WITH UNIQUE KEYS yalnızca metin üzerinde ayırt edicidir. JSON tipli bir değerde kanonik biçim yinelenen anahtarın sonuncusunu tutup ötekileri attığı için yanıt her zaman TRUE'dur; aynı belge bir varchar sütununda tutulursa FALSE döner.

3.6.5 Belge üretmek

Dört işlev belge üretir. Çağrı konum sıralıdır: standardın KEY … VALUE … yazımı yerine MySQL, SQLite ve PostgreSQL'in json_build_object/json_build_array işlevleriyle aynı şekil:

YazımNotlar
JSON_ARRAY(<değer>, …)Argümanlardan bir dizi. NULL argüman diziye girmez (standardın ABSENT ON NULL öndeğeri).
JSON_OBJECT(<anahtar>, <değer>, …)Anahtar/değer çiftlerinden bir nesne. NULL değer null olarak yazılır (NULL ON NULL); anahtar NULL olamaz ve tek kalan argüman hata verir.
JSON_ARRAYAGG(<değer>)Grubun değerlerinden bir dizi (ABSENT ON NULL). Hiç satır yoksa sonuç []'dir, NULL değil.
JSON_OBJECTAGG(<anahtar>, <değer>)Grubun anahtar/değerlerinden bir nesne (NULL ON NULL). Hiç satır yoksa sonuç {}'dir.

Sonuç JSON tipindedir ve kanoniktir; yukarıdaki üç kural burada da geçerlidir: aynı anahtar iki kez yazılırsa sonuncusu kalır. Bildirilen tipi JSON olan bir argüman tırnaklanmaz, belge olarak gömülür; iç içe belge böyle kurulur. Ayırt eden şey argümanın tipidir, içeriği değil: JSON gibi görünen bir metin yine de dizgedir.

select json_array(1, null, 'x')                 from dual;   --> [1,"x"]
select json_object('b', 2, 'a', 1)              from dual;   --> {"a":1,"b":2}
select json_array(json_object('k', 1), '[1,2]') from dual;   --> [{"k":1},"[1,2]"]

select g, json_objectagg(k, v) from t group by g;            --> {"a":10,"b":null}

3.6.6 Belgeyi satırlara açmak: JSON_TABLE

JSON_TABLE bir belgeyi ilişkisel satırlara açar ve FROM'da bir kaynak gibi durur:

JSON_TABLE(<belge>, '<kök yol>' [AS <ad>]
           COLUMNS ( <kolon> [, <kolon>]... )
           [PLAN (<plan>) | PLAN DEFAULT (<seçenekler>)])

Kök yol bir dizi düğüm verir, tipik olarak '$.satirlar[*]', ve her düğüm bir satırdır. Kolon dört şekilde yazılır:

KolonNe yapar
<ad> <tip> [PATH '<yol>']Yolun ulaştığı skaler değer (JSON_VALUE anlambilimi). PATH yazılmazsa öndeğer yol kolonun yazıldığı adıdır; JSON anahtarları büyük/küçük harfe duyarlıdır.
<ad> <tip> FORMAT JSON [PATH …]Düğümün kendisi, kanonik JSON metni olarak (JSON_QUERY anlambilimi); nesne ya da dizi böyle alınır.
<ad> FOR ORDINALITYDüğümün kendi dizisindeki 1 tabanlı sırası.
NESTED [PATH] '<yol>' [AS <ad>] COLUMNS (…)İç bir diziyi aynı satırın altına açar; içinde yine her üç kolon türü ve yeni NESTED'lar olabilir.

Skaler kolonlara NULL | ERROR | DEFAULT <ifade> ardından ON EMPTY ya da ON ERROR yazılabilir. Öndeğer ikisinde de NULL'dur; ERROR seçilirse yol boş döndüğünde No SQL/JSON item hatası gelir (SQLSTATE 22035).

select * from json_table('{"k":[{"ad":"Ali","tel":["1","2"]},{"ad":"Veli","tel":[]}]}',
    '$.k[*]' columns (
      ad  varchar(20) path '$.ad',
      nested path '$.tel[*]' columns (n for ordinality, tel varchar(10) path '$')
    )) jt;

--> Ali  1  1
--> Ali  2  2
--> Veli NULL NULL

PLAN, bir düzeyin çocuklarıyla nasıl birleştiğini söyler: OUTER çocuk satırı olmasa da üst satırı tutar, INNER tutmaz; kardeş NESTED'lar arasında UNION satırları art arda ekler, CROSS kartezyen çarpar. Öndeğer PLAN DEFAULT (OUTER, UNION)'dır; yukarıdaki örnekte Veli satırının kalmasının sebebi budur. PLAN (kök OUTER (a CROSS b)) yazımı tek tek düzeyleri adlandırır ve bunun için AS ile ad verilmiş olması gerekir.

Not

JSON_TABLE FROM listesinde örtülü LATERAL'dır: belgesi kendinden önce gelen bir kaynağın kolonu olabilir ve ayrıca LATERAL yazmak gerekmez.

select d.id, jt.k
from dokuman d, json_table(d.doc, '$.e[*]' columns (k varchar(10) path '$.k')) jt;