Как правильно заменить предопределенный разделитель, используемый несколько раз подряд - PullRequest
0 голосов
/ 29 апреля 2019

У меня есть столбец, который сохраняет простой текст и использует разделитель для замены изображений (которые находятся в base64) в первой таблице разделителем, состоящим из нескольких уникальных символов и идентификатора таблицы base64.

Например:

Таблица A

notesColumn

Картинка прямо здесь <## 1 ##>, а затем вот текст и еще одна картинка <## 2 ##> и вот конец

Может быть несколько результатов, возвращаемых на один экземпляр. Строки с 1 разделителем работают корректно. Проблема здесь заключается в том, что, когда я пытаюсь выбрать те из них с несколькими разделителями для отображения на основе всей страницы, он показывает что-то вроде этого, где он отображается в виде нескольких строк.

Вот изображение, данные: 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 ]

2 голосов
/ 29 апреля 2019

Вы можете сделать это, разбив исходный текст на составные слова, используя функцию разделения строк, а затем join вставив, где требуется, в ваш base64Table, чтобы получить соответствующие значения замены, а затем рекомбинируя с помощью либо FOR XML, либоSTRING_AGG в зависимости от того, используется ли ваш SQL Server до или после 2017 года.

Если вы используете SQL Server 2016 или выше, вы также можете использовать STRING_SPLIT, а если нет, то можете использовать функцию в конце этогоответ, который является моей модифицированной версией Джеффа Модена .

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          |
+------------------------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------+

Сверните свой собственный String Split

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;
0 голосов
/ 30 апреля 2019

Я помещаю это как второй ответ, так как подход полностью отличается от другого, и я не хочу смешивать это.

Вы можете использовать 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
0 голосов
/ 30 апреля 2019

Это один из редких моментов, когда причудливое обновление может сильно помочь:

(Кредиты @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 и переназначит его переменной. Поэтому следующая строка будет работать с измененным содержимым и т. Д.

У этого подхода есть серьезные недостатки, но в целом ничего не рекомендуется. Но в этом случае, когда сортировка не имеет значения, вы можете попробовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...