Збіг а] (квадратний дужок, що закривається) з PATINDEX, використовуючи підстановку “[]”


9

Я пишу користувальницький JSON-аналізатор у T-SQL .

Для мого аналізатора я використовую PATINDEXфункцію, яка обчислює позицію токена зі списку лексем. У моєму випадку лексеми - це єдині символи, і вони включають такі:

{} []:,

Зазвичай, коли мені потрібно знайти (перше) положення будь-якого з декількох заданих символів, я використовую цю PATINDEXфункцію:

PATINDEX('%[abc]%', SourceString)

Функція буде потім дати мені першу позицію aабо bабо c- в залежності від того , трапляється знайти перший - в SourceString.

Зараз проблема в моєму випадку, здається, пов'язана з ]характером. Щойно я вказую його у списку символів, наприклад, як:

PATINDEX('%[[]{}:,]%', SourceString)

мій задуманий зразок, мабуть, стає порушеним, оскільки функція ніколи не знаходить відповідності. Схоже, мені потрібен спосіб уникнути першого, ]щоб PATINDEXтрактувати його як один із символів пошуку, а не спеціальний символ.

Я знайшов це запитання про подібну проблему:

Однак у такому випадку ]просто не потрібно вказувати в дужках, оскільки це лише один символ, і він може бути вказаний без дужок навколо них. Альтернативне рішення, яке використовує втечу, працює лише для, LIKEа не для PATINDEX, оскільки воно використовує ESCAPEпідпункт, підтримуваний першим, а не другим.

Отже, моє запитання, чи є спосіб , щоб шукати ]з PATINDEXдопомогою [ ]підстановки? Або є спосіб емуляції цієї функції за допомогою інших інструментів Transact-SQL?

Додаткова інформація

Нижче наведено приклад запиту , коли мені потрібно використовувати PATINDEXз […]малюнком , як вказані вище. Шаблон тут працює (хоча і дещо ), оскільки він не містить ]символу. Мені це потрібно і для роботи ]:

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)
;

Вихід, який я отримую:

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

Ви можете бачити, що ]символи включені як частина Sв одному з рядків. У Levelстовпці вказується рівень гніздування, тобто дужка та дужки вкладення. Як бачите, як тільки рівень стає 2, він ніколи не повертається до 1. Це було б, якби я міг зробити PATINDEXвизнання ]як маркер.

Очікуваний вихід для наведеного вище прикладу:

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

Ви можете грати з цим запитом у db <> fiddle .


Ми використовуємо SQL Server 2014 і навряд чи незабаром оновиться до версії, яка підтримує JSON для синтаксичного розбору. Я міг би написати заявку на виконання роботи, але результати розбору потребують додаткової обробки, що передбачає більше роботи в додатку, ніж просто розбір - таку роботу, яку було б набагато простіше і, можливо, ефективніше, виконувати з сценарій T-SQL, якби тільки я міг застосувати його безпосередньо до результатів.

Дуже малоймовірно, що я можу використовувати SQLCLR як рішення цієї проблеми. Однак я не заперечую, якщо хтось вирішить опублікувати рішення SQLCLR, оскільки це може бути корисним для інших.


Що з json, що виглядає ["foo]bar”]?
Салман А

@SalmanA: Такі сценарії можна сміливо ігнорувати.
Андрій М

Відповіді:


6

Моє власне рішення, яке є більш корисним способом, полягало у визначенні діапазону символів, який включав ]та використовував цей діапазон разом з іншими символами у [ ]малій підстановці. Я використовував діапазон на основі таблиці ASCII. Відповідно до цієї таблиці, ]символ розташований у наступному мікрорайоні:

Hex Dec Char
--- --- ----
…
5A 90 Z
5В 91 [
5С 92 \
5D 93]
5Е 94 ^
5F 95 _
…

Мій діапазон, тому прийняв форму [-^, тобто включені чотири символи: [, \, ], ^. Я також вказав, що в шаблоні використовується двійкове порівняння, щоб точно відповідати діапазону ASCII. Отриманий PATINDEXвираз закінчився таким чином:

PATINDEX('%[[-^{}:,]%' COLLATE Latin1_General_BIN2, MyJSONString)

Очевидна проблема цього підходу полягає в тому, що діапазон на початку шаблону включає два небажані символи \та ^. Рішення для мене спрацювало просто тому, що зайві символи ніколи не можуть зустрічатися в конкретних рядках JSON, які мені потрібні для розбору. Звичайно, це взагалі не може бути правдою, тому мене все ще цікавлять інші методи, сподіваюсь, універсальніші за мої.


4

У мене, мабуть, страшно сприймати це ззаду, коли мені довелося зробити багато розщеплення струн.

Якщо у вас є відомий набір символів, складіть їх таблицю.

CREATE TABLE dbo.characters ( character CHAR(1) NOT NULL PRIMARY KEY CLUSTERED );

INSERT dbo.characters ( character )
SELECT *
FROM (
        SELECT '[' UNION ALL
        SELECT ']' UNION ALL
        SELECT '{' UNION ALL
        SELECT '}' UNION ALL
        SELECT ',' 
) AS x (v)

Потім використовуйте це магічне CROSS APPLYразом із CHARINDEX:

SELECT TOP 1000 p.Id, p.Body, ca.*
FROM dbo.Posts AS p
CROSS APPLY (
    SELECT TOP 1 CHARINDEX(c.character, p.Body) AS first_things_first
    FROM dbo.characters AS c
    ORDER BY CHARINDEX(c.character, p.Body) ASC
) AS ca
WHERE ca.first_things_first > 0

Якщо я пропускаю щось очевидне в тому, що вам потрібно зробити, знайте, лемме.


4

У минулому я бачив підходи до заміни ображаючого персонажа перед пошуком і введення його назад після цього.

У цьому випадку ми могли б зробити щось на кшталт:

DECLARE @test NVARCHAR(MAX);
DECLARE @replacementcharacter CHAR(1) = CHAR(174);

SET @test = 'Test[]@String'

SELECT PATINDEX('%[[' + @replacementcharacter + '@]%', REPLACE(@test,']',@Replacementcharacter))

Цей код правильно повертається 5. Я використовую символ ¬ так, що навряд чи з’явиться - якщо немає символів ASCII, які ви не будете використовувати, це рішення не працюватиме.

Як не дивно, прямий відповідь на ваше запитання буде "ні" - я не можу змусити PATINDEX шукати "]", але якщо ви заміните його, вам не потрібно.

Той же приклад, але без використання змінної:

DECLARE @test NVARCHAR(MAX);

SET @test = 'Test[]@String'

SELECT PATINDEX('%[[' + CHAR(174) + '@]%', REPLACE(@test,']',CHAR(174)))

Використання вищезазначеного рішення у коді дає необхідні результати:

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('%[[{'+ CHAR(174) + ']%', REPLACE(d.ResponseJSON,']',CHAR(174)))) 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('%[[{}:,'+ CHAR(174) + ']%' COLLATE Latin1_General_BIN2, REPLACE(d.ResponseJSON,']',CHAR(174)))) 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)
;

4

Оскільки ]це лише спеціальне місце [...], ви можете використовувати PATINDEXдвічі, виїжджаючи ]за межі поля [...]. Оцініть і те, PATINDEX('%[[{}:,]%', SourceString)і PATINDEX('%]%', SourceString). Якщо один результат дорівнює нулю, візьміть інший. В іншому випадку візьміть менше двох значень.

У вашому прикладі:

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 + ISNULL(p.P, 0),
      S             = SUBSTRING(d.ResponseJSON, 1, p.P - 1),
      C             = c.C,
      ResponseJSON  = SUBSTRING(d.ResponseJSON, p.P + 1, 999999)
    FROM
      parser AS d
      CROSS APPLY (VALUES (NULLIF(PATINDEX('%[[{}:,]%', d.ResponseJSON), 0), NULLIF(PATINDEX('%]%', d.ResponseJSON), 0))) AS p_ (a, b)
      CROSS APPLY (VALUES (CASE WHEN p_.a < p_.b OR p_.b IS NULL THEN p_.a ELSE p_.b END)) AS p (P)
      CROSS APPLY (SELECT SUBSTRING(d.ResponseJSON, p.P, 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)
;

https://dbfiddle.uk/?rdbms=sqlserver_2014&fiddle=66fba2218d8d7d310d5a682be143f6eb


-4

Зліва '[':

PATINDEX('%[[]%',expression)

Праворуч ']':

PATINDEX('%]%',expression)

1
Це визначає, як шукати або відкриваючу квадратну дужку, або закриваючу; ОП шукає одного з декількох символів (відзначається додаванням відповідних символів у квадратні дужки), включаючи квадратну дужку, що закривається.
RDFozz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.