2014-12-02 255 views
1

我有兩個SQL表EmployeeMst和EmployeeCity在SQL Server替換逗號分隔值

EmployeeMst 
SrNo Name 
1  abc 
2  xyz 
3  pqr 
4  def 

EmployeeCity 
srno City  EmplSrNo 
1  Delhi 1,2,3,4 
2  Mumbai 2,3,1 
3  New York 3,2 

我想用select查詢從EmployeeMst得到員工姓名

輸出象下面這樣:

srno City   EmployeeName 
1 Delhi   abc,xyz,pqr,def 
2 Mumbai  xyz,pqr,abc 
3 New York  pqr,xyz 

這裏有幾個數據。請給我查詢我如何做到這一點

我用charindex但它需要更多的時間。

+4

這是非常不好的數據庫設計,你能夠改變它嗎? – DavidG 2014-12-02 11:56:25

+4

通過使用聯結表來修復數據庫設計,而不是嘗試進行此項工作。 – 2014-12-02 11:56:49

+0

是的,我可以改變,但這是舊錶,所以我只能使用這張表 – 2014-12-02 11:57:25

回答

5

我仍然認爲,無論表是否是舊的還是新的,你應該花時間來解決設計不佳宜早不宜遲。你只是在拖延不可避免的事情。因此這裏的東西,讓你開始歸一化的設計:

-- CREATE CITY TABLE 
CREATE TABLE dbo.City 
(
    SrNo INT, 
    City VARCHAR(50) 
); 

-- POPULATE FROM EXISTING TABLE 
INSERT dbo.City (SrNo, City) 
SELECT SrNo, City 
FROM dbo.EmployeeCity; 

-- CREATE CITY-EMPLOYEE JUNCTION TABLE USING EXISTING 
-- DATA, AND XML METHOD TO SPLIT COMMA SEPARATED VALUES 
-- INTO ROWS 
CREATE TABLE dbo.EmployeeCity2 (CitySrNo, EmployeeSrNo) 
SELECT SrNo, i.value('.', 'INT') 
FROM ( SELECT SrNo, 
        x = CONVERT(XML, '<i>' + 
           REPLACE(EmplSrNo, ',', '</i><i>') + 
           '</i>') 
      FROM dbo.EmployeeCity 
     ) AS t 
     CROSS APPLY t.x.nodes('i') rx (i); 

-- DROP EXISTING TABLE SO THAT WE CAN CREATE A VIEW OF THE SAME NAME 
DROP TABLE dbo.EmployeeCity; 
GO 

-- CREATE A VIEW THAT REPLICATES THE FORMAT OF THE CURRENT TABLE SO 
-- THAT EXISTING SELECT QUERIES ARE NOT AFFECTED 
CREATE VIEW dbo.EmployeeCity 
AS 
    SELECT c.SrNo, 
      c.City, 
      EmplSrNo = STUFF(( SELECT ',' + CAST(EmployeeSrNo AS VARCHAR(10)) 
           FROM dbo.EmployeeCity2 AS ec 
           WHERE ec.CitySrNo = c.SrNo 
           FOR XML PATH(''), TYPE 
          ).value('.', 'VARCHAR(MAX)'), 1, 1, '') 
    FROM dbo.City AS c; 
GO 

-- FINALLY, THE QUERY YOU NEED TO GET THE OUTPUT IN THE QUESTION 
SELECT c.SrNo, 
     c.City, 
     EmployeeName = STUFF(( SELECT ',' + m.Name 
           FROM dbo.EmployeeCity2 AS ec 
             INNER JOIN EmployeeMst AS m 
              ON m.SrNo = ec.EmployeeSrNo 
           WHERE ec.CitySrNo = c.SrNo 
           FOR XML PATH(''), TYPE 
          ).value('.', 'VARCHAR(MAX)'), 1, 1, '') 
FROM dbo.City AS c; 

你仍然需要修改數據是如何插入/更新/刪除,但是應該有少得多的地方,這種情況比它在哪裏選定,並且所有現有的選擇查詢都被視圖覆蓋。

對於一對夫婦的上述用於分割逗號分隔字符串的原則進一步閱讀,然後把它重新結合在一起,請參閱:

+0

很好的答案,但請注意,任何插入或更新EmployeeCity的現有SQL代碼都將停止工作。無論如何,值得努力重構任何這樣的代碼! – 2014-12-02 12:45:25

+0

謝謝,我會建議我的上司做這個,並製作新表。 – 2014-12-02 13:00:01

+0

我用同樣的方法,現在插入什麼?我必須更改EmployeeCity2而不是EmployeeCity? – 2014-12-03 08:45:47

2

使用此。 Fiddler Demo

CREATE FUNCTION EmployeeName(@Expr1 AS VARCHAR(MAX)) 
    RETURNS VARCHAR(MAX) 
    BEGIN 
     DECLARE @res AS VARCHAR(MAX) 

     SET @res = (SELECT ',' + B.name AS A FROM 
      (SELECT 
       Split.a.value('.', 'VARCHAR(100)') AS CVS 
      FROM 
      (
       SELECT CAST ('<M>' + REPLACE(@Expr1, ',', '</M><M>') + '</M>' AS XML) AS CVS 
      ) AS A CROSS APPLY CVS.nodes ('/M') AS Split(a)) AS A 

     INNER JOIN EmployeeMst AS B ON A.CVS = B.id 

     FOR XML PATH(''), TYPE).value('.', 'varchar(max)') 

     RETURN STUFF(@res, 1,1,'') 
    END 

SELECT srno, city, dbo.EmployeeName(EmplSrNo) AS EmployeeName FROM EmployeeCity 
1
[SQL Fiddle][1] 
-------------------------------------------------------- 
--create temp table for using in split 
IF OBJECT_ID('tempdb..#Tally') IS NOT NULL 
    DROP TABLE #Tally 
CREATE TABLE #Tally (N INT) 
DECLARE @i AS INT = 1 
WHILE @i != 1000 
    BEGIN 
     INSERT INTO #Tally 
       (N) 
     VALUES (@i) 
     SET @i = @i + 1 
    END 
-------------------------------------------------------- 
--Create temp table EmployeeMst 
IF OBJECT_ID('tempdb..#EmployeeMst') IS NOT NULL 
    DROP TABLE #EmployeeMst 
CREATE TABLE #EmployeeMst 
    (
     SrNo INT , 
     EmpNo VARCHAR(10), 
    ) 
INSERT INTO #EmployeeMst 
VALUES (1, 'abc'), 
     (2, 'xyz'), 
     (3, 'pqr'), 
     (4, 'def') 
-------------------------------------------------------- 
--Create temp table EmployeeCity 
IF OBJECT_ID('tempdb..#EmployeeCity') IS NOT NULL 
    DROP TABLE #EmployeeCity 
CREATE TABLE #EmployeeCity 
    (
     srno INT , 
     City VARCHAR(20) , 
     EmplSrNo VARCHAR(MAX) 
    ) 
INSERT INTO #EmployeeCity 
VALUES (1, 'Delhi', '1,2,3,4'), 
     (2, 'Mumbai', '2,3,1'), 
     (3, 'New York', '3,2') 
-------------------------------------------------------- 
--Get output as in example 
; 
WITH EmployeeCityUpdated 
      AS (SELECT E.srno , 
         E.City , 
         CONVERT(INT, REPLACE(f4.EmpNoUpd, ',', '')) AS EmployeeName 
       FROM  #EmployeeCity AS E 
         JOIN #Tally AS T ON SUBSTRING(',' + E.EmplSrNo, T.N, 1) = ',' 
              AND T.N < LEN(EmplSrNo) + 1 
         CROSS APPLY (SELECT string = SUBSTRING(' ' 
                   + EmplSrNo + ',', 
                   T.N + 1, 
                   LEN(EmplSrNo) 
                   + 1) 
            ) f1 
         CROSS APPLY (SELECT p1 = CHARINDEX(',', string) 
            ) f2 
         CROSS APPLY (SELECT EmpNoUpd = SUBSTRING(EmplSrNo, 
                   T.N, p1) 
            ) f4 
      ) 
    SELECT EC.srno , 
      EC.City , 
      SUBSTRING((SELECT ',' + CONVERT(VARCHAR, EM.EmpNo) 
         FROM EmployeeCityUpdated AS ECU 
           JOIN #EmployeeMst AS EM ON ECU.EmployeeName = EM.SrNo 
         WHERE ECU.srno = EC.srno 
         FOR 
         XML PATH('') 
        ), 2, 1000) AS EmployeeName 
    FROM #EmployeeCity AS EC 
-------------------------------------------------------- 
--srno City   EmployeeName 
--1 Delhi   abc,xyz,pqr,def 
--2 Mumbai  xyz,pqr,abc 
--3 New York  pqr,xyz 
+0

http://sqlfiddle.com/#!3/954e4/1 – Vasily 2014-12-03 00:24:43