2017-06-16 121 views
4

我有一個如下結構的表格。由於逗號分隔值,無法弄清楚如何連接表格

select loginid,alloted_area from tbllogin 

其中返回此結果。

loginid  alloted_area 
------------- --------------------------- 
01900017  22,153,169,174,179,301 
01900117  254,91,92,285,286,287 
01900217  2,690,326,327,336 
17900501  null 
17900601  28,513,409,410 
17901101  254,91,92,285 
17901701  59,1302,1303 
17902101  2,690,326,327 
17902301  20,159,371,161 
17902401  null 

我有另一個表tblarea它的ID存儲在逗號分隔值以上表中時的區域被分配給用戶。我想加入這兩個表格,並留下最後一個尚未分配區域的條目。現在我已經多次被告知,以逗號分隔值存儲數據是一種不好的做法(我想這是因爲我面臨的問題),但我知道,但是這種結構是由我公司的另一位開發人員創建的,而不是我請幫忙,而不是downvoting。這是我曾嘗試:

declare @csv varchar(max)=''; 
SELECT @CSV = COALESCE(@CSV + ', ', '') + case when alloted_area is null or alloted_area='' then '0' else alloted_area end from tbllogin; 
select * from tblarea where id in (select 0 union select sID from splitstring(@CSV,',')); 

這確實得到了區,但沒有辦法它可以給我說,地區已被分配給用戶的登錄。示例輸入和輸出。

tbllogin

loginid  alloted_area 
------------- --------------------------- 
a1   1,3,5 
a2   2,4 
a3   1,4 
a4   null 

tblarea

id   area_name 
------------- --------------------------- 
1    v 
2    w 
3    x 
4    y 
5    z 

加入後,我需要這個結果

login_id   area_name 
------------- --------------------------- 
a1    v 
a1    x 
a1    z 
a2    w 
a2    y 
a3    v 
a3    y 
+2

你能編輯你的問題,並清楚地向我們展示示例輸入和預期輸出嗎?我有一些解決方法,但你的數據不清楚。 –

+1

當數據增長時,這將是一個真正的痛苦。儘管您對錶格設計不負責任。您可以用這種方式解釋存儲數據時遇到的問題,並將其更改爲合適的標準化表 –

回答

2

通過拆分和CROSS APPLY就可以達到所需的輸出

DECLARE @tbllogin TABLE (LoginID CHAR(2) NOT NULL PRIMARY KEY, alloted_area VARCHAR(MAX)); 
INSERT @tblLogin (LoginID, alloted_area) 
VALUES ('a1', '1,3,5'), ('a2', '2,4'),('a3', '1,4'), ('a4', NULL); 

DECLARE @tblArea TABLE (ID INT NOT NULL PRIMARY KEY, Area_Name CHAR(1)); 
INSERT @tblArea (ID, Area_Name) 
VALUES (1, 'v'), (2, 'w'), (3, 'x'), (4, 'y'), (5, 'z'); 


SELECT Dt.LoginID,A.Area_Name FROm 
(
SELECT LoginID,Split.a.value('.', 'VARCHAR(1000)') AS alloted_area 
      FROM (
       SELECT LoginID,CAST('<S>' + REPLACE(alloted_area, ',', '</S><S>') + '</S>' AS XML) AS alloted_area 
       FROM @tbllogin 
       ) AS A 
      CROSS APPLY alloted_area.nodes('/S') AS Split(a) 

)DT 
Inner join 
@tblArea A 
on A.ID=DT.alloted_area 

輸出

LoginID  Area_Name 
-------------------- 
a1   v 
a1   x 
a1   z 
a2   w 
a2   y 
a3   v 
a3   y 
+0

,這非常出色。你能解釋一下這個查詢的工作原理嗎? – rexroxm

2

你可以加入使用LIKE,例如CONCAT(',', alloted_area, ',') LIKE CONCAT('%,', ID, ',%')

因此,對於一個完整的例子

-- SAMPLE DATA 
DECLARE @tbllogin TABLE (LoginID CHAR(2) NOT NULL PRIMARY KEY, alloted_area VARCHAR(MAX)); 
INSERT @tblLogin (LoginID, alloted_area) 
VALUES ('a1', '1,3,5'), ('a2', '2,4'),('a3', '1,4'), ('a4', NULL); 
DECLARE @tblArea TABLE (ID INT NOT NULL PRIMARY KEY, Area_Name CHAR(1)); 
INSERT @tblArea (ID, Area_Name) 
VALUES (1, 'v'), (2, 'w'), (3, 'x'), (4, 'y'), (5, 'z'); 

-- QUERY 
SELECT l.LoginID, 
     a.Area_Name 
FROM @tblLogin AS l 
     INNER JOIN @tblArea AS a 
      ON CONCAT(',', l.alloted_area, ',') LIKE CONCAT('%,', a.ID, ',%') 
ORDER BY l.LoginID; 

輸出

LoginID  Area_Name 
-------------------- 
a1   v 
a1   x 
a1   z 
a2   w 
a2   y 
a3   v 
a3   y 

你可以說是分裂allocated_area成單獨的行,但阿龍貝特朗文章Split strings the right way – or the next best way表明,在這種情況下LIKE會跑贏任何分裂功能。

雖然你說過你知道這是一個糟糕的設計,但我不能很好地在我的回答中沒有提及它,所以無論選擇哪種方法都不能替代它的存儲方式。如果不是由你,由誰設計的。

正確的方法將是一個結合表,tblLoginArea

LoginID  AreaID 
------------------ 
a1   1 
a1   3 
a1   5 
a2   2 
a2   4 
....etc 

這時如果開發商仍然需要csv格式,那麼他們可以創建一個視圖,並更新其引用到:

CREATE VIEW dbo.LoginAreaCSV 
AS 
SELECT l.LoginID, 
     Allocated_Area = STUFF(la.AllocatedAreas.value('.', 'NVARCHAR(MAX)'), 1, 1, '') 
FROM tblLogin AS l 
     OUTER APPLY 
     ( SELECT CONCAT(',', la.AreaID) 
      FROM tblLoginArea AS la 
      WHERE la.LoginID = l.LoginID 
      ORDER BY la.AreaID 
      FOR XML PATH(''), TYPE 
     ) AS la (AllocatedAreas); 

而且你的查詢可以使用可與索引優化等式謂詞來完成:

SELECT l.LoginID, a.Area_Name 
FROM tblLogin AS l 
     INNER JOIN tblLoginArea AS la 
      ON la.LoginID = l.LoginID 
     INNER JOIN tblArea AS a 
      ON a.ID = la.AreaID; 

Example on DB Fiddle

2

考慮這個分割功能:

CREATE FUNCTION [dbo].[SplitString] 
(
    @List NVARCHAR(MAX), 
    @Delim VARCHAR(255) 
) 
RETURNS TABLE 
AS 
    RETURN (SELECT [Value] FROM 
     ( 
     SELECT 
      [Value] = LTRIM(RTRIM(SUBSTRING(@List, [Number], 
      CHARINDEX(@Delim, @List + @Delim, [Number]) - [Number]))) 
     FROM (SELECT Number = ROW_NUMBER() OVER (ORDER BY name) 
      FROM sys.all_objects) AS x 
      WHERE Number <= LEN(@List) 
      AND SUBSTRING(@Delim + @List, [Number], LEN(@Delim)) = @Delim 
    ) AS y 
    ); 

然後,你可以做這樣的查詢:

SELECT 
    t.loginid,tblarea.area_name 
FROM 
    tbllogin AS t 
CROSS APPLY(SELECT value FROM SplitString(t.alloted_area,',')) as split 
JOIN tblarea ON tblarea.id=split.Value