替换CSV字符串中的值

时间:2012-03-06 01:34:30

标签: sql sql-server tsql

我有逗号分隔方式的产品列表,由于项目列表已替换为新产品项目,我尝试使用新产品项目列表修改此CSV列表。

create table #tmp (
  id int identity(1,1) not null,
  plist varchar(max) null
)

create table #tmpprod (
  oldid int null,
  newid int null
)

insert into #tmp
select '10,11,15,17,19'
union
select '22,34,44,25'
union
select '5,6,8,9'

insert into #tmpprod
select 5, 109
union
select 9, 110
union
select 10, 111
union
select 15, 112
union
select 19, 113
union
select 30, 114
union
select 34, 222
union
select 44, 333

drop table #tmp
drop table #tmpprod

我正在尝试使用split fn转换为行,然后替换这些值,然后再将列转换为行。有可能以其他方式吗?

输出将为:


1   111,11,112,17,113
2   22,222,333,25
3   109,6,8,110

4 个答案:

答案 0 :(得分:4)

将逗号分隔列表转换为XML。使用数字表,XQuery和position()来获取单独的ID以及它们在字符串中的位置。使用for xml path('')技巧构建逗号分隔字符串,left outer join#tempprod,然后按position()排序。

;with C as
(
  select T.id,
         N.number as Pos,
         X.PList.value('(/i[position()=sql:column("N.Number")])[1]', 'int') as PID
  from @tmp as T
    cross apply (select cast('<i>'+replace(plist, ',', '</i><i>')+'</i>' as xml)) as X(PList)
    inner join master..spt_values as N
      on N.number between 1 and X.PList.value('count(/i)', 'int')
  where N.type = 'P'  
)
select C1.id,
       stuff((select ','+cast(coalesce(T.newid, C2.PID) as varchar(10))
              from C as C2
                left outer join @tmpprod as T
                  on C2.PID = T.oldid
              where C1.id = C2.id
              order by C2.Pos
              for xml path(''), type).value('.', 'varchar(max)'), 1, 1, '')

from C as C1
group by C1.id

试试SE-Data

答案 1 :(得分:3)

假设SQL Server 2005或更高版本,并假设顺序不重要,那么给出这个分割函数:

CREATE FUNCTION [dbo].[SplitInts]
(
   @List       VARCHAR(MAX),
   @Delimiter  CHAR(1)
)
RETURNS TABLE
AS
   RETURN ( SELECT Item FROM ( SELECT Item = x.i.value('(./text())[1]', 'int') FROM 
            ( SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(@List, @Delimiter, '</i><i>') 
              + '</i>').query('.') ) AS a CROSS APPLY [XML].nodes('i') AS x(i)
          ) AS y WHERE Item IS NOT NULL
   );
GO

您可以通过以下方式获得此结果:

;WITH x AS
(
    SELECT id, item, oldid, [newid], rn = ROW_NUMBER() OVER
    (PARTITION BY id ORDER BY PATINDEX('%,' + RTRIM(s.Item) + ',%', ',' + t.plist + ','))
    FROM #tmp AS t CROSS APPLY dbo.SplitInts(t.plist, ',') AS s
    LEFT OUTER JOIN #tmpprod AS p ON p.oldid = s.Item
)
SELECT DISTINCT id, STUFF((SELECT ',' +RTRIM(COALESCE([newid], Item)) 
    FROM x AS x2 WHERE x2.id = x.id
    FOR XML PATH(''), TYPE).value('.[1]', 'varchar(max)'), 1, 1, '') 
FROM x;

请注意,ROW_NUMBER() / OVER / PARTITION BY / ORDER BY仅在尝试时强制优化器按该顺序返回行。您今天可能会观察到此行为,并且明天可能会根据统计信息或数据更改,优化程序更改(Service Pack,CU,升级等)或其他变量进行更改。

长话短说:如果您依赖于该顺序,只需将该集发送回客户端,并让客户端构建逗号分隔列表。这可能是这个功能所属的地方。

答案 2 :(得分:1)

感谢您提出这个问题 - 我刚学到了一些新知识。以下代码是article written by Rob Volk对此主题的改编。这是一个非常聪明的查询!我不会在这里复制所有内容。我已经调整它以创建您在示例中寻找的结果。

CREATE TABLE #nums (n INT)
DECLARE @i INT 
SET @i = 1
WHILE @i < 8000 
BEGIN
    INSERT #nums VALUES(@i)
    SET @i = @i + 1
END


CREATE TABLE #tmp (
  id INT IDENTITY(1,1) not null,
  plist VARCHAR(MAX) null
)

INSERT INTO #tmp
VALUES('10,11,15,17,19'),('22,34,44,25'),('5,6,8,9')

CREATE TABLE #tmpprod (
  oldid INT NULL,
  newid INT NULL
)

INSERT INTO #tmpprod VALUES(5, 109),(9, 110),(10, 111),(15, 112),(19, 113),(30, 114),(34, 222),(44, 333)

;WITH cte AS (SELECT ID, NULLIF(SUBSTRING(',' + plist + ',' , n , CHARINDEX(',' , ',' + plist + ',' , n) - n) , '') AS prod
    FROM #nums, #tmp
    WHERE ID <= LEN(',' + plist + ',') AND SUBSTRING(',' + plist + ',' , n - 1, 1) = ',' 
    AND CHARINDEX(',' , ',' + plist + ',' , n) - n > 0)
UPDATE t SET plist = (SELECT CAST(CASE WHEN tp.oldid IS NULL THEN cte.prod ELSE tp.newid END AS VARCHAR) + ',' 
            FROM cte LEFT JOIN #tmpprod tp ON cte.prod = tp.oldid
            WHERE cte.id = t.id FOR XML PATH(''))
FROM #tmp t WHERE id = t.id

UPDATE #tmp SET plist = SUBSTRING(plist, 1, LEN(plist) -1)
WHERE LEN(plist) > 0 AND SUBSTRING(plist, LEN(plist), 1) = ','

SELECT * FROM #tmp
DROP TABLE #tmp
DROP TABLE #tmpprod
DROP TABLE #nums

#nums表是一个连续整数的表,其长度必须大于表中最长的CSV。脚本的前8行创建此表并填充它。然后我复制了你的代码,然后是这个查询的内容 - 非常聪明的单查询解析器,在上面指出的文章中有更详细的描述。公共表表达式(WITH cte ...)执行解析,更新脚本将结果重新编译为CSV并更新#tmp。

答案 3 :(得分:0)

Adam Machanic的博客包含了这个仅发布T-SQL的UDF,它可以接受T-SQL的通配符以供替换。

http://dataeducation.com/splitting-a-string-of-unlimited-length/

对于我自己的用途,我将varchar大小调整为max。另请注意,此UDF执行速度相当慢,但如果您无法使用CLR,则可能是一个选项。我对作者代码所做的微小更改可能会限制对SQL Server 2008r2及更高版本的使用。

CREATE FUNCTION dbo.PatternReplace
(
   @InputString VARCHAR(max),
   @Pattern VARCHAR(max),
   @ReplaceText VARCHAR(max)
)
RETURNS VARCHAR(max)
AS
BEGIN
   DECLARE @Result VARCHAR(max) = ''
   -- First character in a match
   DECLARE @First INT
   -- Next character to start search on
   DECLARE @Next INT = 1
   -- Length of the total string -- 0 if @InputString is NULL
   DECLARE @Len INT = COALESCE(LEN(@InputString), 0)
   -- End of a pattern
   DECLARE @EndPattern INT

   WHILE (@Next <= @Len) 
   BEGIN
      SET @First = PATINDEX('%' + @Pattern + '%', SUBSTRING(@InputString, @Next, @Len))
      IF COALESCE(@First, 0) = 0 --no match - return
      BEGIN
         SET @Result = @Result + 
            CASE --return NULL, just like REPLACE, if inputs are NULL
               WHEN  @InputString IS NULL
                     OR @Pattern IS NULL
                     OR @ReplaceText IS NULL THEN NULL
               ELSE SUBSTRING(@InputString, @Next, @Len)
            END
         BREAK
      END
      ELSE
      BEGIN
         -- Concatenate characters before the match to the result
         SET @Result = @Result + SUBSTRING(@InputString, @Next, @First - 1)
         SET @Next = @Next + @First - 1

         SET @EndPattern = 1
         -- Find start of end pattern range
         WHILE PATINDEX(@Pattern, SUBSTRING(@InputString, @Next, @EndPattern)) = 0
            SET @EndPattern = @EndPattern + 1
         -- Find end of pattern range
         WHILE PATINDEX(@Pattern, SUBSTRING(@InputString, @Next, @EndPattern)) > 0
               AND @Len >= (@Next + @EndPattern - 1)
            SET @EndPattern = @EndPattern + 1

         --Either at the end of the pattern or @Next + @EndPattern = @Len
         SET @Result = @Result + @ReplaceText
         SET @Next = @Next + @EndPattern - 1
      END
   END
   RETURN(@Result)
END