Проблема декартового объединения SQL - PullRequest
1 голос
/ 08 мая 2009

У меня три таблицы

  • A: первичный ключ A.pID, A.Name nvarchar (250)
  • B: первичный ключ B.pID, B.Name nvarchar (250)
  • C: первичный ключ C.pID, C.Name nvarchar (250)

Существует отношение m к n между A и B (таблица lA_B с первичным ключом lA_B.pID и .pInstanceA Внешний ключ к таблице A и .pInstanceB Внешний ключ к таблице B)

Существует отношение m к n между A и C (table lA_C с первичным ключом lA_C.pID и .pInstanceA Внешний ключ к таблице A и .pInstanceB Внешний ключ к таблице C)

  • A1 относится к B1, B2 и C1
  • A2 относится к B3 и C2, C3
  • A3 связан с B4
  • А4 связан с С4
  • A5 не имеет отношения

Вот мой SQL:

CREATE TABLE [dbo].[A]( [pID] [bigint] NOT NULL, [Name] [nvarchar](250) NULL )
CREATE TABLE [dbo].[B]( [pID] [bigint] NOT NULL, [Name] [nvarchar](250) NULL )
CREATE TABLE [dbo].[C]( [pID] [bigint] NOT NULL, [Name] [nvarchar](250) NULL)
CREATE TABLE [dbo].[lA_B]( [pID] [bigint] NOT NULL, [pInstanceA] [bigint] NULL, [pInstanceB] [bigint] NULL )
CREATE TABLE [dbo].[lA_C]( [pID] [bigint] NOT NULL, [pInstanceA] [bigint] NULL, [pInstanceB] [bigint] NULL )

INSERT INTO [dbo].[A] ([pID] ,[Name]) VALUES (1,'A1')
INSERT INTO [dbo].[A] ([pID] ,[Name]) VALUES (2,'A2')
INSERT INTO [dbo].[A] ([pID] ,[Name]) VALUES (3,'A3')
INSERT INTO [dbo].[A] ([pID] ,[Name]) VALUES (4,'A4')
INSERT INTO [dbo].[A] ([pID] ,[Name]) VALUES (5,'A5')

INSERT INTO [dbo].[B] ([pID] ,[Name]) VALUES (1,'B1')
INSERT INTO [dbo].[B] ([pID] ,[Name]) VALUES (2,'B2')
INSERT INTO [dbo].[B] ([pID] ,[Name]) VALUES (3,'B3')
INSERT INTO [dbo].[B] ([pID] ,[Name]) VALUES (4,'B4')

INSERT INTO [dbo].[C] ([pID] ,[Name]) VALUES (1,'C1')
INSERT INTO [dbo].[C] ([pID] ,[Name]) VALUES (2,'C2')
INSERT INTO [dbo].[C] ([pID] ,[Name]) VALUES (3,'C3')
INSERT INTO [dbo].[C] ([pID] ,[Name]) VALUES (4,'C4')

INSERT INTO [dbo].[lA_B] ([pID],[pInstanceA],[pInstanceB])   VALUES   (1,1,1)
INSERT INTO [dbo].[lA_B] ([pID],[pInstanceA],[pInstanceB])   VALUES   (2,1,2)
INSERT INTO [dbo].[lA_B] ([pID],[pInstanceA],[pInstanceB])   VALUES   (3,2,3)
INSERT INTO [dbo].[lA_B] ([pID],[pInstanceA],[pInstanceB])   VALUES   (4,3,4)

INSERT INTO [dbo].[lA_C] ([pID],[pInstanceA],[pInstanceB])   VALUES   (1,1,1)
INSERT INTO [dbo].[lA_C] ([pID],[pInstanceA],[pInstanceB])   VALUES   (2,2,2)
INSERT INTO [dbo].[lA_C] ([pID],[pInstanceA],[pInstanceB])   VALUES   (3,2,3)
INSERT INTO [dbo].[lA_C] ([pID],[pInstanceA],[pInstanceB])   VALUES   (4,4,4)

этот запрос:

SELECT
  A.Name AS A, 
  B.Name AS B, 
  C.Name AS C
FROM
  A  
  left JOIN lA_B  ON (A.pID = lA_B.pInstanceA)
  left JOIN B     ON (B.pID = lA_B.pInstanceB)
  left JOIN lA_C  ON (A.pID = lA_C.pInstanceA)
  left JOIN C     ON (C.pID = lA_C.pInstanceB) 

возвращает

A1      B1      C1
A1      B2      C1
A2      B3      C2
A2      B3      C3
A3      B4      NULL
A4      NULL    C4
A5      NULL    NULL 

А теперь вопрос :-) как сделать запрос на получение

A1      B1      NULL
A1      B2      NULL
A1      NULL    C1
A2      B3      NULL
A2      NULL    C2
A2      NULL    C3
A3      B4      NULL
A4      NULL    C4
A5      NULL    NULL 

Проблема в том, что когда я делаю соединение как с B, так и с C, в результате получаются все комбинации B C. Как я могу устранить это?

Ответы [ 2 ]

4 голосов
/ 08 мая 2009

Вы можете сделать это с помощью UNION:

SELECT A.Name AS A, B.Name AS B, NULL AS C 
FROM A
left JOIN lA_B ON (A.pID=lA_B.pInstanceA) 
left JOIN B ON (lA_B.pInstanceB=B.pID) 
UNION
SELECT A.Name AS A, NULL AS B, C.Name AS C 
FROM A
left JOIN lA_C ON (A.pID=lA_C.pInstanceA) 
left JOIN C ON (lA_C.pInstanceB=C.pID)

Первая часть выбирает все комбинации A и B, вторая часть - все комбинации A и C.

Если вы хотите отфильтровать строки наподобие (A4, NULL, NULL), поскольку строка уже есть (A4, NULL, C4), попробуйте выполнить следующий запрос:

SELECT A.Name AS A, B.Name AS B, NULL AS C 
FROM A
LEFT JOIN lA_B ON (A.pID=lA_B.pInstanceA) 
LEFT JOIN B ON (lA_B.pInstanceB=B.pID) 
WHERE b.name is not null 
    or not exists(select * from lA_C where A.pID=lA_C.pInstanceA) 
UNION
SELECT A.Name AS A, NULL AS B, C.Name AS C 
FROM A
LEFT JOIN lA_C ON (A.pID=lA_C.pInstanceA) 
LEFT JOIN C ON (lA_C.pInstanceB=C.pID)
WHERE c.name is not null 
ORDER BY A,B,C

Для объединения на B это означает включение строк, которые имеют совпадение в B или для которых нет совпадений в C. Объединение на C включает только строки, которые совпадают в C. Строки, которые не совпадают, будут включайся от объединения на Б.

Обратите внимание, что UNION отфильтровывает дублирующиеся строки, как DISTINCT. Чтобы включить каждую строку, вы можете использовать UNION ALL.

0 голосов
/ 08 мая 2009

Я думаю, что это делает:

select a,
       case when b='zzz' then null else b end as b,
       case when c='zzz' then null else c end as c
from (SELECT  A.Name AS A
             ,b.Name as b
             ,'zzz' as c
      FROM  A    
      JOIN lA_B  ON (A.pID = lA_B.pInstanceA)  
      JOIN B     ON (B.pID = lA_B.pInstanceB)  
      union
      select  a.Name
             ,'zzz'
            ,c.NAme
      from A
      left JOIN lA_C  ON (A.pID = lA_C.pInstanceA)  
      left JOIN C     ON (C.pID = lA_C.pInstanceB)) as a
...