Saya menulis parser JSON khusus di T-SQL † .
Untuk keperluan parser saya, saya menggunakan PATINDEX
fungsi yang menghitung posisi token dari daftar token. Token dalam kasus saya semuanya adalah karakter tunggal dan termasuk:
{} []:,
Biasanya, ketika saya perlu menemukan posisi (pertama) dari beberapa karakter yang diberikan, saya menggunakan PATINDEX
fungsi seperti ini:
PATINDEX('%[abc]%', SourceString)
Fungsi kemudian akan memberikan posisi pertama a
atau b
atau c
- mana kebetulan ditemukan pertama - dalam SourceString
.
Sekarang masalah dalam kasus saya tampaknya terhubung dengan ]
karakter. Segera setelah saya tentukan dalam daftar karakter, misal seperti ini:
PATINDEX('%[[]{}:,]%', SourceString)
Pola yang saya maksudkan ternyata menjadi rusak, karena fungsi tidak pernah menemukan kecocokan. Sepertinya saya perlu cara untuk melarikan diri yang pertama ]
sehingga PATINDEX
memperlakukannya sebagai salah satu karakter pencarian daripada simbol khusus.
Saya menemukan pertanyaan ini menanyakan tentang masalah yang serupa:
Namun, dalam hal ]
itu tidak perlu ditentukan dalam tanda kurung, karena itu hanya satu karakter dan dapat ditentukan tanpa tanda kurung di sekitarnya. Solusi alternatif, yang menggunakan melarikan diri, hanya berfungsi untuk LIKE
dan bukan untuk PATINDEX
, karena menggunakan ESCAPE
sub - klausa, didukung oleh yang pertama dan bukan oleh yang terakhir.
Jadi, pertanyaan saya adalah, apakah ada cara untuk mencari ]
dengan PATINDEX
menggunakan [ ]
wildcard? Atau adakah cara untuk meniru fungsi itu menggunakan alat Transact-SQL lainnya?
informasi tambahan
Berikut adalah contoh query di mana saya harus menggunakan PATINDEX
dengan […]
pola seperti di atas. Pola di sini berfungsi (meskipun agak ) karena tidak termasuk ]
karakter. Saya membutuhkannya untuk bekerja dengan ]
:
WITH
data AS (SELECT CAST('{"f1":["v1","v2"],"f2":"v3"}' AS varchar(max)) AS ResponseJSON),
parser AS
(
SELECT
Level = 1,
OpenClose = 1,
P = p.P,
S = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
C = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1),
ResponseJSON = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
FROM
data AS d
CROSS APPLY (SELECT PATINDEX('%[[{]%', d.ResponseJSON)) AS p (P)
UNION ALL
SELECT
Level = ISNULL(d.OpenClose - 1, 0) + d.Level + ISNULL(oc.OpenClose, 0),
OpenClose = oc.OpenClose,
P = d.P + p.P,
S = SUBSTRING(d.ResponseJSON, 1, NULLIF(p.P, 0) - 1),
C = c.C,
ResponseJSON = SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0) + 1, 999999)
FROM
parser AS d
CROSS APPLY (SELECT PATINDEX('%[[{}:,]%' COLLATE Latin1_General_BIN2, d.ResponseJSON)) AS p (P)
CROSS APPLY (SELECT SUBSTRING(d.ResponseJSON, NULLIF(p.P, 0), 1)) AS c (C)
CROSS APPLY (SELECT CASE WHEN c.C IN ('[', '{') THEN 1 WHEN c.C IN (']', '}') THEN 0 END) AS oc (OpenClose)
WHERE 1=1
AND p.P <> 0
)
SELECT
*
FROM
parser
OPTION
(MAXRECURSION 0)
;
Output yang saya dapatkan adalah:
Level OpenClose P S C ResponseJSON
----- --------- -- ----- -- ---------------------------
1 1 1 { "f1":["v1","v2"],"f2":"v3"}
1 null 6 "f1" : ["v1","v2"],"f2":"v3"}
2 1 7 [ "v1","v2"],"f2":"v3"}
2 null 12 "v1" , "v2"],"f2":"v3"}
2 null 18 "v2"] , "f2":"v3"}
2 null 23 "f2" : "v3"}
2 0 28 "v3" }
Anda dapat melihat bahwa ]
ini dimasukkan sebagai bagian dari S
dalam salah satu baris. The Level
kolom menunjukkan tingkat bersarang, yang berarti bracket dan kawat gigi bersarang. Seperti yang Anda lihat, begitu levelnya menjadi 2, ia tidak pernah kembali ke 1. Ini akan terjadi jika saya bisa PATINDEX
mengenali ]
sebagai token.
Output yang diharapkan untuk contoh di atas adalah:
Level OpenClose P S C ResponseJSON
----- --------- -- ---- -- ---------------------------
1 1 1 { "f1":["v1","v2"],"f2":"v3"}
1 NULL 6 "f1" : ["v1","v2"],"f2":"v3"}
2 1 7 [ "v1","v2"],"f2":"v3"}
2 NULL 12 "v1" , "v2"],"f2":"v3"}
2 0 17 "v2" ] ,"f2":"v3"}
1 NULL 18 , "f2":"v3"}
1 NULL 23 "f2" : "v3"}
1 0 28 "v3" }
Anda dapat bermain dengan kueri ini di db <> fiddle .
† Kami menggunakan SQL Server 2014 dan tidak mungkin untuk segera memutakhirkan ke versi yang mendukung penguraian JSON secara asli. Saya bisa menulis aplikasi untuk melakukan pekerjaan itu tetapi hasil parsing perlu diproses lebih lanjut, yang menyiratkan lebih banyak pekerjaan dalam aplikasi daripada hanya parsing - jenis pekerjaan yang akan jauh lebih mudah, dan mungkin lebih efisien, dilakukan dengan skrip T-SQL, kalau saja saya bisa menerapkannya langsung ke hasil.
Sangat tidak mungkin saya bisa menggunakan SQLCLR sebagai solusi untuk masalah ini. Namun, saya tidak keberatan jika seseorang memutuskan untuk mengirim solusi SQLCLR, karena itu bisa berguna bagi orang lain.
["foo]bar”]
?Jawaban:
Solusi saya sendiri, yang lebih merupakan solusi, terdiri dalam menentukan rentang karakter yang termasuk
]
dan menggunakan rentang itu bersama dengan karakter lain di[ ]
wildcard. Saya menggunakan rentang berdasarkan tabel ASCII. Menurut tabel itu,]
karakter terletak di lingkungan berikut:Jangkauan saya, oleh karena itu, mengambil bentuk
[-^
, yaitu itu termasuk empat karakter:[
,\
,]
,^
. Saya juga menentukan bahwa polanya menggunakan Binary collation, untuk mencocokkan kisaran ASCII dengan tepat.PATINDEX
Ekspresi yang dihasilkan akhirnya tampak seperti ini:Masalah yang jelas dengan pendekatan ini adalah bahwa rentang di awal pola mencakup dua karakter yang tidak diinginkan,
\
dan^
. Solusinya bekerja untuk saya hanya karena karakter tambahan tidak pernah dapat terjadi di string JSON tertentu yang saya butuhkan untuk menguraikan. Secara alami, ini tidak mungkin benar secara umum, jadi saya masih tertarik pada metode lain, semoga lebih universal daripada metode saya.sumber
Saya mungkin memiliki pandangan buruk tentang ini dari belakang ketika saya harus melakukan banyak pemisahan tali.
Jika Anda memiliki serangkaian karakter yang diketahui, buatlah tabelnya.
Kemudian gunakan sihir itu
CROSS APPLY
bersama denganCHARINDEX
:Jika saya melewatkan sesuatu yang jelas tentang apa yang perlu Anda lakukan, saya tahu.
sumber
Saya telah melihat pendekatan di masa lalu untuk menggantikan karakter yang menyinggung sebelum mencari, dan memasukkannya kembali setelahnya.
Dalam hal ini kita dapat melakukan sesuatu seperti:
Kode ini kembali dengan benar 5. Saya menggunakan karakter ¬ karena tidak mungkin muncul - jika tidak ada karakter ASCII yang tidak akan Anda gunakan, solusi ini tidak akan berfungsi.
Namun anehnya, jawaban langsung untuk pertanyaan Anda adalah tidak - Saya juga tidak bisa mendapatkan PATINDEX untuk mencari ']', tetapi jika Anda menggantinya, Anda tidak perlu melakukannya.
Contoh yang sama tetapi tanpa penggunaan variabel:
Menggunakan solusi di atas dalam kode Anda menghasilkan hasil yang Anda perlukan:
sumber
Karena
]
hanya khusus di[...]
, Anda dapat menggunakanPATINDEX
dua kali, bergerak di]
luar[...]
. Mengevaluasi keduanyaPATINDEX('%[[{}:,]%', SourceString)
danPATINDEX('%]%', SourceString)
. Jika satu hasilnya nol, ambil yang lainnya. Jika tidak, ambil yang lebih rendah dari dua nilai.Dalam contoh Anda:
https://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=66fba2218d8d7d310d5a682be143f6eb
sumber
Untuk sebelah kiri '[':
Untuk hak ']':
sumber