【问题标题】:Extract string between double quotes in SQL在SQL中提取双引号之间的字符串
【发布时间】:2025-12-31 21:30:01
【问题描述】:

我有一个字符串,我需要返回双引号之间的字符

'(("名称 1" 和 "名称 2") 或 "名称 3")

我要回去

Name 1
Name 2
Name 3

我已经使用下面的函数来拆分字符串,但是我得到了 (( 以及 Or 和 AND 等 .. 我不想要,不幸的是我不能确定所有其他可能的字符包括在内,因此删除或替换它们实际上并不可行。

ALTER FUNCTION [dbo].[fn_SplitString] 
( 
   @string NVARCHAR(MAX), 
   @delimiter CHAR(1) 
) 
RETURNS @output TABLE(ID int, splitdata NVARCHAR(MAX) 
) 
BEGIN 
   DECLARE @start INT, @end INT, @Count INT
   set @Count = 1
   SELECT @start = 1, @end = CHARINDEX(@delimiter, @string) 
   WHILE @start < LEN(@string) + 1 BEGIN 
      IF @end = 0  
         SET @end = LEN(@string) + 1

       INSERT INTO @output (ID, splitdata)  
       VALUES(@Count, SUBSTRING(@string, @start, @end - @start)) 
       SET @start = @end + 1 
       SET @end = CHARINDEX(@delimiter, @string, @start)
       Set @Count = @Count+1
   END 
   RETURN 
END

我知道这段代码会返回两个分隔符之间的字符串

substring( LEFT(@String, charindex(']', @String)-1), CHARINDEX('[', @String) + len('['), LEN(@String)) 

有没有什么方法可以将 2 组合起来并返回所需的输出?

谢谢

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    这是一种递归方法

    DECLARE @s VARCHAR(100)='(("Name 1" and "Name 2") or "Name 3")';
    
    WITH recCTE AS
    (
        SELECT 1 AS Position
              ,SUBSTRING(@s,1,1) AS CharAtPos
              ,CASE WHEN SUBSTRING(@s,1,1)='"' THEN 0 ELSE -1 END AS QuoteGroup
              ,CASE WHEN SUBSTRING(@s,1,1)='"' THEN 1 ELSE 0 END AS QuoteIsOpen
        UNION ALL
        SELECT r.Position+1
              ,SUBSTRING(@s,r.Position+1,1)
              ,CASE WHEN SUBSTRING(@s,r.Position+1,1)='"' THEN CASE WHEN r.QuoteIsOpen=0 THEN r.QuoteGroup+1 ELSE r.QuoteGroup END ELSE r.QuoteGroup END AS QuoteGroup
              ,CASE WHEN SUBSTRING(@s,r.Position+1,1)='"' THEN CASE WHEN r.QuoteIsOpen=0 THEN 1 ELSE 0 END ELSE r.QuoteIsOpen END AS QuoteIsOpen
        FROM recCTE r
        WHERE r.Position+1<=LEN(REPLACE(@s,' ','*')) 
    )
    SELECT r.QuoteGroup
          ,(
            SELECT CharAtPos AS [*]
            FROM recCTE r2 
            WHERE r2.QuoteGroup=r.QuoteGroup AND r2.QuoteIsOpen=1 AND r2.CharAtPos<>'"'
            ORDER BY r2.Position
            FOR XML PATH(''),TYPE).value('.','varchar(100)')
    FROM recCTE r
    WHERE r.QuoteGroup>=0
    GROUP BY QuoteGroup;
    

    一些解释:

    递归 CTE 将逐个字符地遍历您的字符串。它将检查报价并跟踪打开关闭。根据这一点,所有带有 open 标志的值都会被分组并通过 XML 重新连接。

    【讨论】:

      【解决方案2】:

      试试这个;我将调试变量保留在输出表中

      ALTER FUNCTION [dbo].[fn_SplitString] 
      ( 
          @string     NVARCHAR(MAX), 
          @delimiter  CHAR(1) 
      ) 
      RETURNS @output TABLE
      (
          [Id]        INT,
          [Start]     INT,
          [End]       INT,
          [Length]    INT,
          [Data]      NVARCHAR(MAX)
      ) 
      BEGIN 
          DECLARE @count INT, @start INT, @end INT
          SELECT  @count = 1, @end = 0,
                  @start = CHARINDEX(@delimiter, @string)
          WHILE   @start > 0 BEGIN 
      
              SELECT  @end = CHARINDEX(@delimiter, @string, @start + 1)
      
              INSERT INTO @output ([Id], [Start], [End], [Length], [Data])
              VALUES (@count, @start, @end, @end - @start - 1, 
                      SUBSTRING(@string, @start + 1, @end - @start - 1)) 
      
              SELECT  @start = CHARINDEX(@delimiter, @string, @end + 1), 
                      @count = @count + 1
          END 
          RETURN 
      END
      

      或者这个,基于接收两个不同分隔符的想法:

      ALTER FUNCTION [dbo].[fn_SplitString] 
      ( 
          @string     NVARCHAR(MAX), 
          @delimiter1 NVARCHAR(MAX),
          @delimiter2 NVARCHAR(MAX)
      ) 
      RETURNS @output TABLE
      (
          [Id]        INT,
          [Start]     INT,
          [End]       INT,
          [Length]    INT,
          [Data]      NVARCHAR(MAX)
      ) 
      BEGIN 
          DECLARE @count INT, @start INT, @end INT
          SELECT  @count = 1, @end = 0
          SELECT  @start = CHARINDEX(@delimiter1, @string)
          SELECT  @end   = CHARINDEX(@delimiter2, @string, @start + 1)
      
          WHILE   @start > 0 AND @end > 0 BEGIN 
      
              INSERT INTO @output ([Id], [Start], [End], [Length], [Data])
              VALUES (@count, @start, @end, @end - @start - 1, 
                      SUBSTRING(@string, @start + 1, @end - @start - 1)) 
      
              SELECT  @start = CHARINDEX(@delimiter1, @string, @end + 1)
              SELECT  @end   = CHARINDEX(@delimiter2, @string, @start + 1),
                      @count = @count + 1
          END 
          RETURN 
      END
      

      【讨论】:

      • 我试图保留OP代码;顺便说一句,我喜欢你的实现;)
      【解决方案3】:

      Jeff Moden 的拆分器功能的这个修改版本应该可以为您提供所需的内容。它避免了循环以及查找和替换某些字符串或字符的需要。

      CREATE FUNCTION dbo._SplitStrings
      (
          @List NVARCHAR(MAX),
          @Delimiter NVARCHAR(255)
      )
      RETURNS TABLE
      WITH SCHEMABINDING AS
      RETURN
          WITH E1(N)
          AS 
          ( 
              SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                       UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 
                      UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
          ),
          E2(N) AS    ( SELECT 1 FROM E1 a, E1 b ),
          E4(N) AS    ( SELECT 1 FROM E2 a, E2 b ),
          E42(N) AS   ( SELECT 1 FROM E4 a, E2 b ),
          cteTally(N)  
          AS 
          (
              SELECT 0 UNION ALL SELECT TOP (DATALENGTH(ISNULL(@List,1))) 
                               ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E42),
             cteStart(N1) AS (SELECT t.N+1 FROM cteTally t
                               WHERE (SUBSTRING(@List,t.N,1) = @Delimiter OR t.N = 0)),
        cteSplitByDelimiter AS
        (
            SELECT    s.N1, 
                      SUBSTRING(@List, s.N1 - 1, 1) AS [StartDelimiter],
                      SUBSTRING(@List , s.N1 + ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000), + 1) AS [EndDelimiter],
                      SUBSTRING(@List, s.N1, ISNULL(NULLIF(CHARINDEX(@Delimiter,@List,s.N1),0)-s.N1,8000)) AS [Item]
              FROM cteStart s
        ),
        cteList AS
        (
            SELECT N1
                  ,Item
                  ,ROW_NUMBER() OVER (ORDER BY N1) % 2 AS ExpectedItem
            FROM  cteSplitByDelimiter sd
            WHERE sd.StartDelimiter = @Delimiter and sd.EndDelimiter = @Delimiter
        )
        SELECT    Item
        FROM      cteList 
        WHERE     ExpectedItem = 1;
      

      【讨论】:

        【解决方案4】:

        您需要使用replace() 函数,然后将其传递给函数参数:

        declare @string varchar(255) = '(("Name 1" and "Name 2") or "Name 3")'
        
        set @string = replace(replace(replace(replace(replace(@string, '(', ''), ')', ''), '"', ''), 'and', ','), 'or', ',')
        
        select *
        from [dbo].[fn_SplitString](@string, ',') spt;
        

        如果您使用的是最新版本的 SQL Server,则可以使用 TRANSLATE() 代替 replace()

        【讨论】:

        • 我无法确定所有其他可能包含的字符,因此删除或替换它们实际上并不可行