Piszę niestandardowy parser JSON w języku T-SQL † .
Na potrzeby mojego parsera używam PATINDEX
funkcji, która oblicza pozycję tokena na podstawie listy tokenów. Wszystkie tokeny w moim przypadku są pojedynczymi postaciami i obejmują one:
{} []:,
Zwykle, gdy muszę znaleźć (pierwszą) pozycję dowolnego z kilku podanych znaków, używam PATINDEX
funkcji w następujący sposób:
PATINDEX('%[abc]%', SourceString)
Funkcja będzie następnie dać mi pierwszą pozycję a
lub b
lub c
- w zależności od tego co dzieje się znaleźć pierwszy - w SourceString
.
Teraz problem w moim przypadku wydaje się być związany z ]
postacią. Jak tylko podam to na liście postaci, np. Tak:
PATINDEX('%[[]{}:,]%', SourceString)
mój zamierzony wzorzec najwyraźniej zostaje zepsuty, ponieważ funkcja nigdy nie znajdzie dopasowania. Wygląda na to, że potrzebuję sposobu na ucieczkę od pierwszego, ]
więc PATINDEX
traktuje to jako jedną z postaci, a nie specjalny symbol.
Znalazłem to pytanie z pytaniem o podobny problem:
Jednak w takim przypadku po ]
prostu nie trzeba podawać w nawiasach, ponieważ jest to tylko jeden znak i można go podać bez nawiasów wokół nich. Alternatywne rozwiązanie, które korzysta ze znaku ucieczki, działa tylko dla, LIKE
a nie dla PATINDEX
, ponieważ wykorzystuje ESCAPE
podklucz, obsługiwany przez to pierwsze, a nie drugie.
Więc moje pytanie brzmi, czy jest jakiś sposób, aby szukać ]
z PATINDEX
użyciem [ ]
symboli wieloznacznych? Czy istnieje sposób na emulowanie tej funkcjonalności przy użyciu innych narzędzi Transact-SQL?
Dodatkowe informacje
Oto przykład z zapytaniem gdzie muszę korzystać PATINDEX
ze […]
wzoru jak powyżej. Wzór tutaj działa (choć nieco ), ponieważ nie zawiera ]
znaku. Potrzebuję go również do pracy z ]
:
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)
;
Otrzymuję wynik:
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" }
Możesz zobaczyć, że ]
jest uwzględniony jako część S
w jednym z wierszy. Level
Kolumna wskazuje poziom zagnieżdżenia, co oznacza, wspornik i szelki lęgowych. Jak widać, gdy poziom osiągnie 2, nigdy nie powróci do 1. Byłoby tak, gdybym mógł PATINDEX
rozpoznać ]
jako token.
Oczekiwany wynik dla powyższego przykładu to:
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" }
Możesz grać z tym zapytaniem w db <> skrzypce .
† Korzystamy z programu SQL Server 2014 i jest mało prawdopodobne, aby wkrótce zaktualizowano go do wersji obsługującej natywnie parsowanie JSON. Mógłbym napisać aplikację do wykonania zadania, ale wyniki analizy muszą być dalej przetwarzane, co oznacza więcej pracy w aplikacji niż tylko analizowanie - taki rodzaj pracy, który byłby o wiele łatwiejszy i prawdopodobnie bardziej wydajny, skrypt T-SQL, gdybym tylko mógł zastosować go bezpośrednio do wyników.
Jest bardzo mało prawdopodobne, że mogę użyć SQLCLR jako rozwiązania tego problemu. Jednak nie mam nic przeciwko, jeśli ktoś zdecyduje się opublikować rozwiązanie SQLCLR, ponieważ może to być przydatne dla innych.
["foo]bar”]
?Odpowiedzi:
Moje własne rozwiązanie, które jest raczej obejściem, polegało na określeniu zakresu znaków, który obejmował
]
i używanie tego zakresu wraz z innymi znakami ze znaku[ ]
wieloznacznego. Użyłem zakresu opartego na tabeli ASCII. Zgodnie z tą tabelą]
postać znajduje się w następującym sąsiedztwie:Mój zakres zatem miały formę
[-^
, to znaczy zawiera cztery znaki:[
,\
,]
,^
. Podałem również, że wzorzec używa sortowania binarnego, aby dokładnie pasować do zakresu ASCII. WynikowePATINDEX
wyrażenie wyglądało tak:Oczywistym problemem związanym z tym podejściem jest to, że zakres na początku wzorca obejmuje dwie niepożądane postacie
\
oraz^
. Rozwiązanie działało dla mnie po prostu dlatego, że dodatkowe znaki nigdy nie mogły wystąpić w określonych ciągach JSON, które musiałem przeanalizować. Oczywiście nie może to być prawda, więc nadal interesują mnie inne metody, mam nadzieję, że są bardziej uniwersalne niż moje.źródło
Mam prawdopodobnie okropne podejście do tego z tyłu, kiedy musiałem dużo rozłupywać struny.
Jeśli masz znany zestaw znaków, zrób ich tabelę.
Następnie użyj tej magii
CROSS APPLY
wraz zCHARINDEX
:Jeśli brakuje mi czegoś oczywistego na temat tego, co musisz zrobić, daj mi znać.
źródło
W przeszłości widziałem podejścia, które zastępują obraźliwą postać przed wyszukiwaniem, a potem umieszczają ją z powrotem.
W tym przypadku możemy zrobić coś takiego:
Ten kod poprawnie zwraca 5. Używam znaku ¬, ponieważ jest to mało prawdopodobne - jeśli nie ma znaków ASCII, których nie będziesz używać, to rozwiązanie nie będzie działać.
Co dziwne, bezpośrednia odpowiedź na twoje pytanie brzmiałaby: nie - nie mogę zmusić PATINDEX do wyszukania „]”, ale jeśli ją zastąpisz, nie musisz.
Ten sam przykład, ale bez użycia zmiennej:
Zastosowanie powyższego rozwiązania w kodzie daje wymagane wyniki:
źródło
Ponieważ
]
jest on wyjątkowy[...]
, możesz go użyćPATINDEX
dwa razy, wychodząc]
poza[...]
. Oceń obaPATINDEX('%[[{}:,]%', SourceString)
iPATINDEX('%]%', SourceString)
. Jeśli jeden wynik wynosi zero, weź drugi. W przeciwnym razie weź mniejszą z dwóch wartości.W twoim przykładzie:
https://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=66fba2218d8d7d310d5a682be143f6eb
źródło
Dla lewego „[”:
Dla prawa „]”:
źródło