Удалить строку, разделенную запятыми, из другого значения столбца, разделенного запятой, в SQL [duplicate]

Насколько я знаю, вы перезаписали, поэтому я думаю, что двигатель сохраняет его в другом пространстве памяти, тогда как b все еще указывает на адрес памяти старого a (который каким-то образом не разрушается).

167
задан DineshDB 21 March 2018 в 06:41
поделиться

12 ответов

Вы можете использовать замечательные рекурсивные функции из SQL Server:


Пример таблицы:

CREATE TABLE Testdata
(
    SomeID INT,
    OtherID INT,
    String VARCHAR(MAX)
)

INSERT Testdata SELECT 1,  9, '18,20,22'
INSERT Testdata SELECT 2,  8, '17,19'
INSERT Testdata SELECT 3,  7, '13,19,20'
INSERT Testdata SELECT 4,  6, ''
INSERT Testdata SELECT 9, 11, '1,2,3,4'

Запрос

;WITH tmp(SomeID, OtherID, DataItem, String) AS
(
    SELECT
        SomeID,
        OtherID,
        LEFT(String, CHARINDEX(',', String + ',') - 1),
        STUFF(String, 1, CHARINDEX(',', String + ','), '')
    FROM Testdata
    UNION all

    SELECT
        SomeID,
        OtherID,
        LEFT(String, CHARINDEX(',', String + ',') - 1),
        STUFF(String, 1, CHARINDEX(',', String + ','), '')
    FROM tmp
    WHERE
        String > ''
)

SELECT
    SomeID,
    OtherID,
    DataItem
FROM tmp
ORDER BY SomeID
-- OPTION (maxrecursion 0)
-- normally recursion is limited to 100. If you know you have very long
-- strings, uncomment the option
< hr>

Выход

 SomeID | OtherID | DataItem 
--------+---------+----------
 1      | 9       | 18       
 1      | 9       | 20       
 1      | 9       | 22       
 2      | 8       | 17       
 2      | 8       | 19       
 3      | 7       | 13       
 3      | 7       | 19       
 3      | 7       | 20       
 4      | 6       |          
 9      | 11      | 1        
 9      | 11      | 2        
 9      | 11      | 3        
 9      | 11      | 4        
203
ответ дан LuizLoyola 16 August 2018 в 00:04
поделиться
  • 1
    +1 Это мой ответ, но с самим запросом! – Aliostad 31 March 2011 в 00:20
  • 2
    Код не работает, если изменить тип данных столбца Data с varchar(max) на varchar(4000), например. create table Testdata(SomeID int, OtherID int, Data varchar(4000))? – ca9163d9 22 February 2012 в 01:58
  • 3
    @NickW это может быть потому, что части до и после UNION ALL возвращают разные типы из функции LEFT. Лично я не понимаю, почему вы не переходите к MAX, как только вы доберетесь до 4000 ... – RichardTheKiwi 22 February 2012 в 10:35
  • 4
    @dsz Вот когда вы используете OPTION (maxrecursion 0) – RichardTheKiwi 14 January 2014 в 22:50
  • 5
    Для функций LEFT может потребоваться CAST для работы ... например, LEFT (CAST (Data AS VARCHAR (MAX)) .... – smoore4 15 July 2016 в 15:27

Ниже работает на sql server 2008

select *, ROW_NUMBER() OVER(order by items) as row# 
from 
( select 134 myColumn1, 34 myColumn2, 'd,c,k,e,f,g,h,a' comaSeperatedColumn) myTable
    cross apply 
SPLIT (rtrim(comaSeperatedColumn), ',') splitedTable -- gives 'items'  column 

Получит все декартово произведение с столбцами таблицы происхождения плюс «элементы» таблицы split.

-1
ответ дан Arun Pratap Singh 16 August 2018 в 00:04
поделиться

Проверьте

 SELECT A.OtherID,  
     Split.a.value('.', 'VARCHAR(100)') AS Data  
 FROM  
 (
     SELECT OtherID,  
         CAST ('<M>' + REPLACE(Data, ',', '</M><M>') + '</M>' AS XML) AS Data  
     FROM  Table1
 ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a); 
74
ответ дан bvr 16 August 2018 в 00:04
поделиться
  • 1
    При использовании этого подхода вы должны убедиться, что ни одно из ваших значений не содержит что-то, что было бы незаконным XML – user1151923 4 March 2015 в 19:22
  • 2
    Отлично. Могу ли я спросить вас, как бы переписать это, если бы я хотел, чтобы новый столбец отображал только первый символ из моей разделенной строки? – Control 8 July 2015 в 16:46
  • 3
    Это отлично работает, спасибо! Мне пришлось обновить предел VARCHAR, но после этого он работал. – chazbot7 6 January 2017 в 01:46
  • 4
    это отлично работало для sql 2014. спасибо! – Chris Brickhouse 8 April 2018 в 19:20

По состоянию на февраль 2016 года - см. таблицу TALLY Table Example - очень вероятно, чтобы превзойти мой TVF ниже, с февраля 2014 года. Сохраняя исходное сообщение ниже для потомков:


Слишком много повторяющегося кода для моей симпатии в приведенных выше примерах. И мне не нравится производительность CTE и XML. Кроме того, явный Id, чтобы потребители, имеющие специфику заказа, могли указать предложение ORDER BY.

CREATE FUNCTION dbo.Split
(
    @Line nvarchar(MAX),
    @SplitOn nvarchar(5) = ','
)
RETURNS @RtnValue table
(
    Id INT NOT NULL IDENTITY(1,1) PRIMARY KEY CLUSTERED,
    Data nvarchar(100) NOT NULL
)
AS
BEGIN
    IF @Line IS NULL RETURN

    DECLARE @split_on_len INT = LEN(@SplitOn)
    DECLARE @start_at INT = 1
    DECLARE @end_at INT
    DECLARE @data_len INT

    WHILE 1=1
    BEGIN
        SET @end_at = CHARINDEX(@SplitOn,@Line,@start_at)
        SET @data_len = CASE @end_at WHEN 0 THEN LEN(@Line) ELSE @end_at-@start_at END
        INSERT INTO @RtnValue (data) VALUES( SUBSTRING(@Line,@start_at,@data_len) );
        IF @end_at = 0 BREAK;
        SET @start_at = @end_at + @split_on_len
    END

    RETURN
END
12
ответ дан dsz 16 August 2018 в 00:04
поделиться

Приятно видеть, что он был решен в версии 2016 года, но для всех тех, что на этом нет, вот две обобщенные и упрощенные версии вышеприведенных методов.

XML-метод

XML-метод:

create function dbo.splitString(@input Varchar(max), @Splitter VarChar(99)) returns table as
Return
    SELECT Split.a.value('.', 'VARCHAR(max)') AS Data FROM
    ( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data 
    ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a); 

Рекурсивный метод:

create function dbo.splitString(@input Varchar(max), @Splitter VarChar(99)) returns table as
Return
    SELECT Split.a.value('.', 'VARCHAR(max)') AS Data FROM
    ( SELECT CAST ('<M>' + REPLACE(@input, @Splitter, '</M><M>') + '</M>' AS XML) AS Data 
    ) AS A CROSS APPLY Data.nodes ('/M') AS Split(a); 

Рекурсивный метод:

create function dbo.splitString(@input Varchar(max), @Splitter Varchar(99)) returns table as
Return
  with tmp (DataItem, ix) as
   ( select @input  , CHARINDEX('',@Input)  --Recu. start, ignored val to get the types right
     union all
     select Substring(@input, ix+1,ix2-ix-1), ix2
     from (Select *, CHARINDEX(@Splitter,@Input+@Splitter,ix+1) ix2 from tmp) x where ix2<>0
   ) select DataItem from tmp where ix<>0

Функция в действии

Create table TEST_X (A int, CSV Varchar(100));
Insert into test_x select 1, 'A,B';
Insert into test_x select 2, 'C,D';

Select A,data from TEST_X x cross apply dbo.splitString(x.CSV,',') Y;

Drop table TEST_X

XML-МЕТОД 2: Юникод Дружественный

5
ответ дан Eske Rahn 16 August 2018 в 00:04
поделиться
  • 1
    Это может показаться очевидным, но как вы используете эти две функции? В частности, можете ли вы показать, как использовать его в случае использования OP? – jpaugh 31 August 2016 в 21:26
  • 2
    Вот краткий пример: Создать таблицу TEST_X (A int, CSV Varchar (100)); Вставить в test_x выбрать 1, 'A, B'; Вставить в test_x select 2, 'C, D'; Выберите A, данные из TEST_X x cross apply dbo.splitString (x.CSV, ',') Y; Стол для распаковки TEST_X – Eske Rahn 9 September 2016 в 12:50
select t.OtherID,x.Kod
    from testData t
    cross apply (select Code from dbo.Split(t.Data,',') ) x
14
ответ дан iCodez 16 August 2018 в 00:04
поделиться
  • 1
    То, что мне нужно, и легче читать, чем многие другие примеры (при условии, что в базе данных уже есть функция для разграничения строк с разделителями). Как кто-то, ранее не знакомый с CROSS APPLY, это очень полезно! – tobriand 3 November 2015 в 14:45
  • 2
    Я не мог понять эту часть (выберите «Код» из dbo.Split (t.Data, ','))? dbo.Split - это таблица, где это существует, а также Code является столбцом в таблице Split? я не смог найти список этих таблиц или значений в любой точке этой страницы? – Jayendran 7 June 2017 в 15:29
  • 3
    Мой рабочий код: select t.OtherID, x.* from testData t cross apply (select item as Data from dbo.Split(t.Data,',') ) x – Akbar Kautsar 11 August 2017 в 04:07
DECLARE @id_list VARCHAR(MAX) = '1234,23,56,576,1231,567,122,87876,57553,1216'
DECLARE @table TABLE ( id VARCHAR(50) )
DECLARE @x INT = 0
DECLARE @firstcomma INT = 0
DECLARE @nextcomma INT = 0

SET @x = LEN(@id_list) - LEN(REPLACE(@id_list, ',', '')) + 1 -- number of ids in id_list

WHILE @x > 0
    BEGIN
        SET @nextcomma = CASE WHEN CHARINDEX(',', @id_list, @firstcomma + 1) = 0
                              THEN LEN(@id_list) + 1
                              ELSE CHARINDEX(',', @id_list, @firstcomma + 1)
                         END
        INSERT  INTO @table
        VALUES  ( SUBSTRING(@id_list, @firstcomma + 1, (@nextcomma - @firstcomma) - 1) )
        SET @firstcomma = CHARINDEX(',', @id_list, @firstcomma + 1)
        SET @x = @x - 1
    END

SELECT  *
FROM    @table
3
ответ дан Jayvee 16 August 2018 в 00:04
поделиться
  • 1
    Это один из немногих методов, которые работают с ограниченной поддержкой SQL в Azure SQL Data Warehouse. – Aaron Schultz 16 October 2017 в 20:06

Функция

CREATE FUNCTION dbo.SplitToRows (@column varchar(100), @separator varchar(10))
RETURNS @rtnTable TABLE
  (
  ID int identity(1,1),
  ColumnA varchar(max)
  )
 AS
BEGIN
    DECLARE @position int = 0
    DECLARE @endAt int = 0
    DECLARE @tempString varchar(100)

    set @column = ltrim(rtrim(@column))

    WHILE @position<=len(@column)
    BEGIN       
        set @endAt = CHARINDEX(@separator,@column,@position)
            if(@endAt=0)
            begin
            Insert into @rtnTable(ColumnA) Select substring(@column,@position,len(@column)-@position)
            break;
            end
        set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position)

        Insert into @rtnTable(ColumnA) select @tempString
        set @position=@endAt+1;
    END
    return
END

Использовать регистр

select * from dbo.SplitToRows('T14; p226.0001; eee; 3554;', ';')

Или просто выбрать с несколькими результирующими наборами

DECLARE @column varchar(max)= '1234; 4748;abcde; 324432'
DECLARE @separator varchar(10) = ';'
DECLARE @position int = 0
DECLARE @endAt int = 0
DECLARE @tempString varchar(100)

set @column = ltrim(rtrim(@column))

WHILE @position<=len(@column)
BEGIN       
    set @endAt = CHARINDEX(@separator,@column,@position)
        if(@endAt=0)
        begin
        Select substring(@column,@position,len(@column)-@position)
        break;
        end
    set @tempString = substring(ltrim(rtrim(@column)),@position,@endAt-@position)

    select @tempString
    set @position=@endAt+1;
END
1
ответ дан mr R 16 August 2018 в 00:04
поделиться

Наконец, ожидание закончилось с SQL Server 2016. Они ввели функцию строки Split, STRING_SPLIT :

select OtherID, cs.Value --SplitData
from yourtable
cross apply STRING_SPLIT (Data, ',') cs

Все остальные методы разделения строки например, XML, таблица Tally, while loop и т. д. были сдуты этой функцией STRING_SPLIT.

Вот отличная статья с сопоставлением производительности: Сюрпризы производительности и предположения: STRING_SPLIT .

Для более старых версий использование таблицы tally table здесь - это одна функция разделения строк (наилучший возможный подход)

CREATE FUNCTION [dbo].[DelimitedSplit8K]
        (@pString VARCHAR(8000), @pDelimiter CHAR(1))
RETURNS TABLE WITH SCHEMABINDING AS
 RETURN
--===== "Inline" CTE Driven "Tally Table" produces values from 0 up to 10,000...
     -- enough to cover NVARCHAR(4000)
  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
                ),                          --10E+1 or 10 rows
       E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
       E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
 cteTally(N) AS (--==== This provides the "base" CTE and limits the number of rows right up front
                     -- for both a performance gain and prevention of accidental "overruns"
                 SELECT TOP (ISNULL(DATALENGTH(@pString),0)) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
                ),
cteStart(N1) AS (--==== This returns N+1 (starting position of each "element" just once for each delimiter)
                 SELECT 1 UNION ALL
                 SELECT t.N+1 FROM cteTally t WHERE SUBSTRING(@pString,t.N,1) = @pDelimiter
                ),
cteLen(N1,L1) AS(--==== Return start and length (for use in substring)
                 SELECT s.N1,
                        ISNULL(NULLIF(CHARINDEX(@pDelimiter,@pString,s.N1),0)-s.N1,8000)
                   FROM cteStart s
                )
--===== Do the actual split. The ISNULL/NULLIF combo handles the length for the final element when no delimiter is found.
 SELECT ItemNumber = ROW_NUMBER() OVER(ORDER BY l.N1),
        Item       = SUBSTRING(@pString, l.N1, l.L1)
   FROM cteLen l
;

Ссылка на Tally OH! Улучшенная функция SQL 8K «CSV Splitter»

84
ответ дан Pரதீப் 16 August 2018 в 00:04
поделиться
  • 1
    очень важный ответ – Syed Md. Kamruzzaman 6 March 2017 в 07:21
  • 2
    Я бы использовал STRING_SPLIT, если только сервер находился на SQL Server 2016! BTW в соответствии со страницей, с которой вы связаны, имя поля, которое он выдает, - value, а не SplitData. – Stewart 28 April 2017 в 16:12
;WITH tmp(SomeID, OtherID, DataItem, Data) as (
    SELECT SomeID, OtherID, LEFT(Data, CHARINDEX(',',Data+',')-1),
        STUFF(Data, 1, CHARINDEX(',',Data+','), '')
FROM Testdata
WHERE Data > ''
)
SELECT SomeID, OtherID, Data
FROM tmp
ORDER BY SomeID

с незначительной модификацией вышеприведенного запроса ...

0
ответ дан Tisho 16 August 2018 в 00:04
поделиться
  • 1
    Можете ли вы вкратце объяснить, как это улучшение по сравнению с версией в принятом ответе? – Leigh 28 July 2012 в 21:50
  • 2
    No union все ... меньше код. Поскольку он использует union all вместо union, не должен быть разницей в производительности? – TamusJRoyce 5 January 2015 в 17:24
  • 3
    Это не возвращало все строки, которые должны были иметь. Я не уверен, что для данных требуется объединение всех, но ваше решение вернуло столько же строк, сколько и исходная таблица. – Oedhel Setren 27 February 2015 в 16:20
  • 4
    (проблема здесь в том, что рекурсивная часть - это опускаемая ...) – Eske Rahn 7 April 2016 в 22:38

При использовании этого подхода вы должны убедиться, что ни одно из ваших значений не содержит что-то, что было бы незаконным. XML-user1151923

Я всегда использую метод XML. Убедитесь, что вы используете VALID XML. У меня есть две функции для преобразования между действительным XML и текстом. (Я, как правило, вырезаю каретки, как мне обычно не нужны.

CREATE FUNCTION dbo.udf_ConvertTextToXML (@Text varchar(MAX)) 
    RETURNS varchar(MAX)
AS
    BEGIN
        SET @Text = REPLACE(@Text,CHAR(10),'')
        SET @Text = REPLACE(@Text,CHAR(13),'')
        SET @Text = REPLACE(@Text,'<','&lt;')
        SET @Text = REPLACE(@Text,'&','&amp;')
        SET @Text = REPLACE(@Text,'>','&gt;')
        SET @Text = REPLACE(@Text,'''','&apos;')
        SET @Text = REPLACE(@Text,'"','&quot;')
    RETURN @Text
END


CREATE FUNCTION dbo.udf_ConvertTextFromXML (@Text VARCHAR(MAX)) 
    RETURNS VARCHAR(max)
AS
    BEGIN
        SET @Text = REPLACE(@Text,'&lt;','<')
        SET @Text = REPLACE(@Text,'&amp;','&')
        SET @Text = REPLACE(@Text,'&gt;','>')
        SET @Text = REPLACE(@Text,'&apos;','''')
        SET @Text = REPLACE(@Text,'&quot;','"')
    RETURN @Text
END
1
ответ дан tommylux 16 August 2018 в 00:04
поделиться
  • 1
    Есть небольшая проблема с кодом, который у вас есть. Он изменит '& lt;' к '& amp; lt;' вместо '& amp; lt;' как и следовало бы. Поэтому вам нужно кодировать '& amp;' первый. – Stewart 1 May 2017 в 09:30
  • 2
    Нет необходимости в такой функции ... Просто используйте неявные способности. Попробуйте это: SELECT (SELECT '<&> blah' + CHAR(13)+CHAR(10) + 'next line' FOR XML PATH('')) – Shnugo 19 July 2018 в 09:02
0
ответ дан Jag Kandasamy 5 September 2018 в 23:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: