2017-06-16 9 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 

Iは、そのIDS領域がユーザに割り当てられている時には、上記表のカンマ区切りに格納されている別のテーブルtblareaを有します。私はこれらの2つのテーブルに参加し、まだ割り当てられていない最後のテーブルのようなエントリを残したいと思います。今では、コンマ区切りの値でデータを格納することが悪い習慣であることについて何度か言われています(私はそれが私が直面している問題のためだと思います)。しかし、この構造は私の会社の他の開発者によって作成されています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,',')); 

これは、面積を取得しているが、それは私の領域が割り当てられていることをユーザーのログインを与えることができる方法はありません。サンプル入力と出力。

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

tbllogin tblarea

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

私はあなたがLIKE、例えばを使用して参加することができ

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

質問を編集して、サンプル入力と予想される出力を明確に表示できますか?私はいくつかの回避策を念頭に置いていますが、あなたのデータは私には分かりません。 –

+1

これはデータが大きくなると本当に苦しいことになります。あなたはテーブルデザインの責任を負いませんが。このようにしてデータを保存する際の問題点を説明し、適切な正規化テーブル –

答えて

2

スプリットとCROSSを使用することにより、我々は所望の出力を達成することができます適用

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 

OutPut

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

に変更するだけです。このクエリの仕組みを説明してください。 – rexroxm

2

この結果を必要とする参加した後、 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; 

OUTPUT

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 
関連する問題