如何正确替换连续使用多次的预定义定界符

时间:2019-04-29 15:15:58

标签: sql-server tsql

我有一栏保存纯文本,并使用定界符将第一个表中的图像(位于base64中)替换为由一些唯一字符和base64表ID组成的定界符。

例如:

TableA

notesColumn

图片在这里<## 1 ##>,然后是一些文本和另一张图片<## 2 ##>,在这里结束

每个实例可以返回多个结果。具有1个定界符的行可以正常工作。这里的问题是,当我尝试根据整个页面选择要显示的带有多个定界符的分隔符时,它将显示类似的内容,并显示为多行。

图片就在这里data:image / png; base64 ...然后是一些文字和另一张图片<## 2 ##>这是结尾

图片在这里<## 1 ##>,然后是一些文本和另一个图片数据:image / png; base64 ...,这是结尾

我的一般查询是

 SELECT REPLACE(A.notesColumn,'<##'+CAST(B.base64ID AS VARCHAR(25))+'##>', B.docImage) [noteText]
 FROM tableA A
    LEFT JOIN base64Table B ON A.ID = B.tableANote
 WHERE pageID = @pageID

如何解决将其显示为多个结果而不是仅显示1行的问题,而不考虑分隔符有多少?

3 个答案:

答案 0 :(得分:2)

您可以执行以下操作:使用字符串拆分功能将原始文本分解为组成词,然后join根据需要放入base64Table中以获取相关的替换值,然后通过任一方法重新组合FOR XMLSTRING_AGG,具体取决于您的SQL Server是2017之前还是之后。

如果您使用的是SQL Server 2016或更高版本,则还可以使用STRING_SPLIT,如果没有,则可以在此答案的末尾使用该功能,这是我对Jeff Moden's的修改版本。

declare @b table(id int,p varchar(100));
insert into @b values(1,'THIS IS PICTURE 1'),(2,'THIS IS PICTURE 2'),(3,'THIS IS PICTURE 3');

declare @t table(v varchar(500));
insert into @t values('A picture is right here <##1##> and then here is some text and another picture <##2##> and here is the end'),('Another picture is here <##1##> and yet more text and another picture <##2##> and here is the end');

select t.v as Original
      ,stuff((select ' ' + isnull(b.p,s.Item)
              from dbo.fn_StringSplit4k(t.v,' ',null) as s
                  left join @b as b
                    on left(s.Item,3) = '<##'
                        and cast(substring(s.Item,4,1) as int) = b.id
              order by s.rn
              for xml path('')
              ),1,1,'') as Replaced
from @t as t;

输出

+------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
|                                                  Original                                                  |                                                            Replaced                                                            |
+------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+
| A picture is right here <##1##> and then here is some text and another picture <##2##> and here is the end | A picture is right here THIS IS PICTURE 1 and then here is some text and another picture THIS IS PICTURE 2 and here is the end |
| Another picture is here <##1##> and yet more text and another picture <##2##> and here is the end          | Another picture is here THIS IS PICTURE 1 and yet more text and another picture THIS IS PICTURE 2 and here is the end          |
+------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+

滚动您自己的字符串拆分

create function dbo.fn_StringSplit4k
(
     @str nvarchar(4000) = ' '              -- String to split.
    ,@delimiter as nvarchar(1) = ','        -- Delimiting value to split on.
    ,@num as int = null                     -- Which value to return.
)
returns table
as
return
                    -- Start tally table with 10 rows.
    with n(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)

                    -- Select the same number of rows as characters in @str as incremental row numbers.
                    -- Cross joins increase exponentially to a max possible 10,000 rows to cover largest @str length.
        ,t(t)   as (select top (select len(isnull(@str,'')) a) row_number() over (order by (select null)) from n n1,n n2,n n3,n n4)

                    -- Return the position of every value that follows the specified delimiter.
        ,s(s)   as (select 1 union all select t+1 from t where substring(isnull(@str,''),t,1) = @delimiter)

                    -- Return the start and length of every value, to use in the SUBSTRING function.
                    -- ISNULL/NULLIF combo handles the last value where there is no delimiter at the end of the string.
        ,l(s,l) as (select s,isnull(nullif(charindex(@delimiter,isnull(@str,''),s),0)-s,4000) from s)

    select rn
          ,item
    from(select row_number() over(order by s) as rn
                ,substring(@str,s,l) as item
        from l
        ) a
    where rn = @num
        or @num is null;

再次查看您的问题,您可能需要一个可处理4000个以上字符的字符串分隔符。如果是这种情况,您可以改用它,尽管它在较小的字符串上的性能可能会比4k版本差:

create function dbo.fn_StringSplitMax
(
    @str nvarchar(max) = ' '                -- String to split.
    ,@delimiter as nvarchar(max) = ','      -- Delimiting value to split on.
    ,@num as int = null                     -- Which value to return.
)
returns table
as
return
    with s as
    (       -- Convert the string to an XML value, replacing the delimiter with XML tags
        select convert(xml,'<x>' + replace((select @str for xml path('')),@delimiter,'</x><x>') + '</x>').query('.') as s
    )
    select rn
          ,item     -- Select the values from the generated XML value by CROSS APPLYing to the XML nodes
    from(select row_number() over (order by (select null)) as rn
              ,n.x.value('.','nvarchar(max)') as item
        from s
              cross apply s.nodes('x') as n(x)
        ) a
    where rn = @num
        or @num is null;

答案 1 :(得分:0)

这是罕见的时刻之一,其中古怪的更新可以提供很多帮助:

(感谢@iamdave,他的mcve部分已使用)

CREATE TABLE B(id int,p varchar(100));
insert into B values(1,'THIS IS PICTURE 1'),(2,'THIS IS PICTURE 2'),(3,'THIS IS PICTURE 3');
GO

CREATE TABLE A(v varchar(500));
insert into A values('A picture is right here <##1##> and then here is some text and another picture <##2##> and here is the end')
                   ,('Another picture is here <##1##> and yet more text and another picture <##2##> and here is the end')
                   ,('Another example with a picutre <##2##> here and one more here <##3##>');
GO

-魔术发生在这里:

CREATE FUNCTION dbo.MultipleImageReplace(@input VARCHAR(MAX))
RETURNS VARCHAR(MAX) AS
BEGIN
    SELECT @input = REPLACE(@input,CONCAT('<##',B.id,'##>'),B.p)    FROM B;
    RETURN @input;
END
GO

-这就是您的称呼方式:

SELECT *
      ,dbo.MultipleImageReplace(A.v) AS ReplacedText
FROM A;
GO

-清理:小心真实数据!

DROP FUNCTION dbo.MultipleImageReplace;
GO
DROP TABLE B;
GO
DROP TABLE A;

结果

A picture is right here THIS IS PICTURE 1 and then here is some text and another picture THIS IS PICTURE 2 and here is the end
Another picture is here THIS IS PICTURE 1 and yet more text and another picture THIS IS PICTURE 2 and here is the end
Another example with a picutre THIS IS PICTURE 2 here and one more here THIS IS PICTURE 3

神奇古怪的更新的某些背景:

该函数使用@input=Modify(@input)。这将导致逐行操作,其中表B的每一行将修改@input的内容并将其重新分配给变量。因此,下一行将使用修改后的内容,依此类推。

此方法存在一些严重的缺点,一般不建议这样做。但是在这种情况下,排序无关紧要,则可以尝试一下。

答案 2 :(得分:0)

我将其作为第二个答案,因为方法与另一方法完全不同,因此我不想将其混淆。

您可以使用XQuery及其强大的功能来处理通用数据。

测试场景:

CREATE TABLE B(id int,p varchar(100));
insert into B values(1,'THIS IS PICTURE 1'),(2,'THIS IS PICTURE 2'),(3,'THIS IS PICTURE 3');
GO

CREATE TABLE A(v varchar(500));
insert into A values('A picture is right here <##1##> and then here is some text and another picture <##2##> and here is the end')
                   ,('Another picture is here <##1##> and yet more text and another picture <##2##> and here is the end')
                   ,('Another example with a picutre <##2##> here and one more here <##3##>');
GO

-CTE将使用一些替代方法将您的字符串转换为XML

WITH Casted AS
(
    SELECT *
          ,CAST('<root>
                 <row>
                   <text>' + REPLACE(REPLACE(A.v,'<##','</text><img index="'),'##>','"/><text>') + '</text>
                 </row>
                 <images>' +

                 --and we will include the pictures to be part of the XML
                 --this will make it easier to use them in the XQuery
                 --You might use two CTEs to include just the pictures needed
                 (
                    SELECT *
                    FROM B
                    FOR XML PATH('img')
                 ) + 
                 '</images>
                 </root>
                 ' AS XML) AS CastedToXml
    FROM A
)
--We use a simple "for .. in .." loop to travers down the nodes and return their content "as-is" or replaced by the corresponding image
SELECT CastedToXml.query('for $nd in /root/row/*
                          return
                          if(local-name($nd)="img") then 
                              /root/images/img[id=$nd/@index]/p/text()
                          else 
                              $nd/text()
                         ').value('.','nvarchar(max)')
FROM Casted
GO

-清理:认真处理真实数据!

DROP TABLE B;
GO
DROP TABLE A;

一个转换后的XML看起来像这样:

<root>
  <row>
    <text>Another example with a picutre </text>
    <img index="2" />
    <text> here and one more here </text>
    <img index="3" />
    <text />
  </row>
  <images>
    <img>
      <id>1</id>
      <p>THIS IS PICTURE 1</p>
    </img>
    <img>
      <id>2</id>
      <p>THIS IS PICTURE 2</p>
    </img>
    <img>
      <id>3</id>
      <p>THIS IS PICTURE 3</p>
    </img>
  </images>
</root>

结果是

A picture is right here THIS IS PICTURE 1 and then here is some text and another picture THIS IS PICTURE 2 and here is the end
Another picture is here THIS IS PICTURE 1 and yet more text and another picture THIS IS PICTURE 2 and here is the end
Another example with a picutre THIS IS PICTURE 2 here and one more here THIS IS PICTURE 3