จับคู่ a] (ปิดวงเล็บเหลี่ยม) กับ PATINDEX โดยใช้สัญลักษณ์“ []”


9

ผมเขียนเอง JSON parser ใน 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"  }

คุณสามารถเล่นกับแบบสอบถามนี้ในฐานข้อมูล <> ซอ


เรากำลังใช้ SQL Server 2014 และไม่น่าจะอัพเกรดเป็นเวอร์ชั่นที่รองรับการแยกวิเคราะห์ JSON โดยกำเนิด ฉันสามารถเขียนแอปพลิเคชันเพื่อทำงาน แต่ผลลัพธ์ของการแยกวิเคราะห์จำเป็นต้องดำเนินการเพิ่มเติมซึ่งหมายถึงการทำงานในแอปพลิเคชันมากกว่าการแยกวิเคราะห์ - ประเภทของงานที่จะง่ายขึ้นมากและอาจมีประสิทธิภาพมากขึ้นด้วย สคริปต์ T-SQL หากฉันสามารถนำไปใช้กับผลลัพธ์ได้โดยตรง

ไม่น่าเป็นไปได้มากที่ฉันจะใช้ SQLCLR เป็นวิธีแก้ปัญหาสำหรับปัญหานี้ได้ อย่างไรก็ตามฉันไม่รังเกียจหากมีคนตัดสินใจโพสต์โซลูชัน SQLCLR เนื่องจากอาจเป็นประโยชน์ต่อผู้อื่น


แล้ว json ที่มีหน้าตาเป็น["foo]bar”]อย่างไร?
Salman

@SalmanA: สถานการณ์ดังกล่าวสามารถละเว้นได้อย่างปลอดภัย
Andriy M

คำตอบ:


6

โซลูชันของฉันซึ่งมีวิธีแก้ไขเพิ่มเติมประกอบด้วยการระบุช่วงอักขระที่รวม]และใช้ช่วงนั้นพร้อมกับอักขระอื่น ๆ ใน[ ]wildcard ฉันใช้ช่วงตามตาราง ASCII ตามตารางนั้น]ตัวละครจะอยู่ในละแวกดังต่อไปนี้:

Char Hex Hex
--- --- ----
...
5A 90 Z
5B 91 [
5C 92
5D 93]
5E 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
สิ่งนี้ระบุวิธีการค้นหาวงเล็บเหลี่ยมเปิดหรือปิดหนึ่งวงเล็บ OP กำลังมองหาหนึ่งในหลาย ๆ ตัวอักษร (บันทึกไว้โดยการปิดล้อมตัวอักษรที่เป็นปัญหาในวงเล็บสี่เหลี่ยม) รวมถึงวงเล็บเหลี่ยมปิด
RDFozz
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.