A] (kapanış köşeli ayraç) ile “[]” joker karakterini kullanarak PATINDEX ile eşleştirme


9

T-SQL özel bir JSON ayrıştırıcı yazıyorum .

Ayrıştırıcımın amacı için, PATINDEXbir belirteç listesinden bir belirtecin konumunu hesaplayan işlevi kullanıyorum . Benim durumumdaki jetonların hepsi tek karakterdir ve şunları içerir:

{} []:,

Genellikle, verilen birkaç karakterden herhangi birinin (ilk) konumunu bulmam gerektiğinde, PATINDEXişlevi şu şekilde kullanırım:

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

Fonksiyon sonra bana ilk konumunu verecektir aya bya ciçinde - ilk bulunabilir olur hangisi - SourceString.

Şimdi benim durumumdaki problem ]karakterle bağlantılı görünüyor . En kısa sürede karakter listesinde belirtmek gibi, örneğin:

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

fonksiyonum hiçbir zaman bir eşleşme bulamadığı için görünüşte kırıldım. İlkinden kaçmanın bir yoluna ihtiyacım var gibi görünüyor, ]böylece PATINDEXözel bir sembol yerine arama karakterlerinden biri olarak davranıyor.

Benzer bir sorun hakkında bu soruyu buldum:

Ancak, bu durumda, ]parantez içinde basitçe belirtilmesi gerekmez, çünkü sadece bir karakterdir ve etraflarında parantez olmadan belirtilebilir. Kaçmayı kullanan alternatif çözüm, sadece için LIKEdeğil , sadece için geçerlidir PATINDEX, çünkü ESCAPEbirincisi tarafından desteklenir, ikincisi tarafından desteklenmez.

Yani, benim sorum, bir aramaya herhangi bir yolu yoktur ]ile PATINDEXkullanarak [ ]joker? Veya diğer Transact-SQL araçlarını kullanarak bu işlevselliği taklit etmenin bir yolu var mı?

ek bilgi

Burada yukarıdaki gibi desen PATINDEXile kullanmanız gereken bir sorgu örneği […]. Buradaki desen ( biraz da olsa ) çalışır, çünkü ]karakteri içermez . Ben de çalışmak gerekiyor ]:

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

Aldığım çıktı:

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

Satırlardan birinde ]parçası olarak dahil edildiğini görebilirsiniz S. LevelSütun, iç içe braket anlam ve parantez iç içe seviyesini gösterir. Gördüğünüz gibi, seviye 2 olduğunda, asla 1'e dönmez . Bir token olarak PATINDEXtanımayı yapabilseydim olurdu ].

Yukarıdaki örnek için beklenen çıktı:

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

Bu sorgu ile db <> fiddle'da oynayabilirsiniz .


SQL Server 2014 kullanıyoruz ve yakında yerel olarak JSON ayrıştırmayı destekleyen bir sürüme yükseltme olasılığı düşük. İşi yapmak için bir başvuru yazabilirim ancak ayrıştırma sonuçlarının daha fazla işlenmesi gerekir, bu da uygulamada yalnızca ayrıştırmadan daha fazla iş anlamına gelir - çok daha kolay ve muhtemelen daha verimli bir şekilde yapılacak bir iş bir T-SQL betiği, yalnızca doğrudan sonuçlara uygularsam.

SQLCLR'yi bu soruna bir çözüm olarak kullanabilmem pek olası değildir. Ancak, birisinin bir SQLCLR çözümü yayınlamaya karar verip vermediğini umursamıyorum, çünkü bu diğerleri için yararlı olabilir.


Peki ya json'a benziyor ["foo]bar”]?
Salman A

@SalmanA: Bu tür senaryolar güvenle göz ardı edilebilir.
Andriy M

Yanıtlar:


6

Daha çok bir geçici çözüm olan kendi çözümüm, joker ]karakterdeki diğer karakterlerle birlikte bu aralığı içeren bir karakter aralığı belirtmek ve kullanmaktan ibaretti [ ]. ASCII tablosuna dayalı bir aralık kullandım. Bu tabloya göre, ]karakter aşağıdaki mahallede bulunmaktadır:

Hex Ara Karakter
--- --- ----
...
5A 90 Z
5B 91 [
5C 92 \
5D 93]
5E 94 ^
5F 95 _
...

Benim aralığı, bu nedenle, şeklini aldı [-^, dört karakter dahil yani: [, \, ], ^. Ayrıca, modelin ASCII aralığını tam olarak eşleştirmek için bir İkili harmanlama kullandığını belirttim. Sonuçta ortaya çıkan PATINDEXifade şöyle görünüyordu:

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

Bu yaklaşımdaki bariz problem, desenin başındaki aralığın iki istenmeyen karakter içermesidir \ve ^. Çözüm benim için işe yaradı çünkü fazladan karakterler hiçbir zaman ayrıştırmam gereken belirli JSON dizelerinde oluşmadı. Doğal olarak, bu genel olarak doğru olamaz, bu yüzden hala diğer yöntemlerle ilgileniyorum, umarım benimkinden daha evrenseldir.


4

Ben dize bölme bir sürü yapmak zorundayken ben muhtemelen bu arkadan korkunç bir almak var.

Bilinen bir karakter kümeniz varsa, bunların bir tablosunu oluşturun.

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)

Sonra o büyülü kullanmak CROSS APPLYile birlikte 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

Yapmanız gerekenler hakkında bariz bir şey kaçırırsam, biliyorum.


4

Geçmişte, aramadan önce rahatsız edici karakterin yerini alacak ve daha sonra tekrar yerleştirecek yaklaşımlar gördüm.

Bu durumda şöyle bir şey yapabiliriz:

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

SET @test = 'Test[]@String'

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

Bu kod doğru olarak 5 değerini döndürür. Görünmesi olası olmayan ¬ karakterini kullanıyorum - kullanmayacağınız ASCII karakteri yoksa bu çözüm çalışmaz.

Garip bir şekilde, sorunuza doğrudan cevap hayır olacaktır - PATINDEX'i ']' araması da alamıyorum, ancak değiştirirseniz buna gerek yoktur.

Aynı örnek, ancak değişken kullanımı olmadan:

DECLARE @test NVARCHAR(MAX);

SET @test = 'Test[]@String'

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

Yukarıdaki çözümü kodunuzda kullanmak, gerekli sonuçları verir:

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

Yana ]sadece özel olduğunu [...], kullanabileceğiniz PATINDEXhareketli iki kez ]dışında [...]. Her ikisini de değerlendirin PATINDEX('%[[{}:,]%', SourceString)ve PATINDEX('%]%', SourceString). Bir sonuç sıfırsa diğerini alın. Aksi takdirde, iki değerden daha azını alın.

Örneğinizde:

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

Sol bir '[' için:

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

Doğru bir ']' için:

PATINDEX('%]%',expression)

1
Bu, bir açılı köşeli parantez veya bir kapanış parantezinin nasıl aranacağını belirtir; OP, kapanış köşeli parantez dahil olmak üzere birkaç karakterden birini (söz konusu karakterlerin köşeli parantez içine alınmasıyla not edilir) arıyor.
RDFozz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.