2012-03-06 87 views
1

我的產品以逗號分隔的方式,自項目列表的列表,包括新產品項目被換下替換值,我想修改與新產品項目列表這個CSV列表。在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 

我想使用拆分fn轉換成行,然後替換這些值,然後再次將列轉換爲行。有沒有可能以其他方式?

輸出將是如下:

 

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

+2

查看此鏈接中的解決方案:http://blogs.msdn.com/b/amitjet/archive/2009/12/11/sql-server-comma-separated-string-to-table。 aspx – diaho 2012-03-06 01:39:10

+0

你能否指定你使用的是哪個版本的SQL Server? – 2012-03-06 03:30:24

+0

此外,如果訂單很重要(例如,替換後111必須首先在列表中),我認爲唯一支持和保證的方式是使用遊標。 – 2012-03-06 03:39:56

回答

4

轉換你的逗號分隔的列表,以XML。使用數字表XQuery和position()獲取單獨的ID與它們在字符串中的位置。通過position()使用for xml path('')招用left outer join#tempprod和秩序構建逗號分隔字符串。

;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

+0

幾個問題:(1)你確定子查詢中的'order by'保證了'xml path'將重建值的順序嗎? (2)「spt_values」中有18個1,15個2,10個3等是否有影響?計劃中還有一個類型轉換警告,但我認爲所有參考'spt_values'的計劃都可能發生。無論如何,榮譽,這與我的方法非常相似,但效率更高。有一天我會學習SQL Server中XML的細節。 – 2012-03-06 13:27:21

+0

@AaronBertrand - 我沒有官方文檔來支持任何事情。 (1)我知道當像'@S = @S + Col'這樣的變量使用字符串連接時並不能保證。在這些情況下,我已經看到建議使用'for xml'來代替'order by'。 (2)我不認爲這很重要,因爲我使用'N.type ='P''來擺脫重複項。然而,我會推薦使用專用的數字表格而不是'master..spt_values'。 (3)我看不到類型轉換錯誤。不知道那是什麼。 – 2012-03-06 13:43:32

+0

@AaronBertrand(4)如果你通過id來代替'distinct',我認爲你的效率會更高。 – 2012-03-06 13:44:12

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只有那裏嘗試強制優化器按照該順序返回行。您今天可能會觀察到這種行爲,並且可能會根據統計信息或數據更改,優化程序更改(服務包,CU,升級等)或其他變量在明天進行更改。

長話短說:如果你根據這個順序,只需發送集返回給客戶端,並且在客戶端構建逗號分隔的列表。無論如何,這可能就是這個功能所在。

+0

@Aaron ...對於CSV列表中的訂單並不太在意,而不是創建單獨的函數..使用Mikaels CTE方法...但是會記住SQL-2012中的spt..values的問題,感謝解決方案! – Ram 2012-03-07 16:18:14

+0

這個問題也發生在以前的版本中,只是它通過SQL Server 2012中的執行計劃暴露出來。這並不意味着需要擔心,只需使用專用數字表或函數而不是現有表具有潛在的選擇性或其他問題。 – 2012-03-07 16:26:21

+0

@Aaron ......再次感謝,會用Tally數字表格的方式,並避免spt_values所有在一起! – Ram 2012-03-07 16:32:16

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。

+0

@ peter ..解決方案是相當有趣的...更新CTE後羣體...也好文章.. 。謝謝! – Ram 2012-03-07 16:22:03

0

亞當Machanic的博客包含T-SQL只UDF的這個帖子能接受T-SQL的通配符替換使用。

http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/pattern-based-replacement-udf.aspx

我自己用的,我調整了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