如何在多个表上避免SQL上的笛卡尔积

时间:2014-04-28 13:30:47

标签: sql sql-server join outer-join cartesian-product

这是我的sqlfiddle http://sqlfiddle.com/#!3/671c8/1

以下是我的表格:

PID LNAME FNAME 1 Bob Joe 2 Smith John 3 Johnson Jake 4 Doe Jane

表1

PID VALUE 1 3 1 5 1 35 2 10 2 15 3 8

表2

PID VALUE 1 X1 1 X2 1 X3 2 Z1 3 X3

我正在尝试在一个人的ID上加入几个表格。这些表包含具有日期的事件,但日期可能会在表中匹配,也可能不匹配。因此,无论日期如何,我真正希望它以某种方式连接表格,这样当我得到结果时,具有最大行的表将是我的结果中的行数,而所有其他表将“适合”。例如

而不是笛卡尔产品:

PID     LNAME   FNAME   THINGONE    THINGTWO
1       Bob     Joe     3           X1
1       Bob     Joe     3           X2
1       Bob     Joe     3           X3
1       Bob     Joe     5           X1
1       Bob     Joe     5           X2
1       Bob     Joe     5           X3
1       Bob     Joe     35          X1
1       Bob     Joe     35          X2
1       Bob     Joe     35          X3

我想要这样的事情:

PID     LNAME   FNAME   THINGONE    THINGTWO
1       Bob     Joe     3           X1
1       Bob     Joe     5           X2
1       Bob     Joe     35          X3

我的sql声明:

SELECT
    p.*,
    t1.value as thingone,
    t2.value as thingtwo
FROM
    person p 
    left outer join table1 t1 on p.pid=t1.pid
    left outer join table2 t2 on p.pid=t2.pid
;

2 个答案:

答案 0 :(得分:2)

我无法理解为什么你想要这样做,但是......

您需要在table1和table2之间创建一个人工连接,然后将其链接到主表。一种方法是按顺序排列行。例如:

SELECT 
    p.pid, p.lname,p.fname, thingone, thingtwo
FROM
    person p 
    left outer join 
    (
        select ISNULL(t1.pid, t2.pid) as pid, t1.value as thingone, t2.value as thingtwo
        from 
            (select *, ROW_NUMBER() over (partition by pid order by value) rn 
                     from table1) t1 
            full outer join 
                    (select *, ROW_NUMBER() over (partition by pid order by value) rn 
                     from table2) t2 
                    on t1.pid=t2.pid and t1.rn=t2.rn
    ) v
        on p.pid = v.pid

答案 1 :(得分:0)

这比我想象的要棘手。无论两个列表的长度如何,挑战在于确保所有记录都出现。以下工作通过枚举每个列表并将其用于连接条件:

SELECT p.*,
       t1.value as thingone,
       t2.value as thingtwo
FROM person p left outer join
     (select t1.*,
             row_number() over (partition by pid order by pid) as seqnum,
             count(*) over (partition by pid) as cnt
      from table1 t1
     ) t1
     on p.pid = t1.pid left outer join
     (select t2.*, row_number() over (partition by pid order by pid) as seqnum,
             count(*) over (partition by pid) as cnt
      from table2 t2
     ) t2
     on p.pid = t2.pid
WHERE t1.seqnum = t2.seqnum or
      (t2.seqnum > t1.cnt) or
      (t1.seqnum > t2.cnt) or
      t1.seqnum is null or
      t2.seqnum is null;

Here是对您的SQL Fiddle的一个小修改,它具有更好的测试数据。

编辑:

where子句中的逻辑处理这些情况(按子句顺序):

  1. 如果两个列表都有序列号,则必须匹配。
  2. 其中list2较长,list1至少有一个元素。
  3. 其中list1较长,list2至少有一个元素。
  4. 其中list1为空
  5. 其中列表2为空
  6. 这些是通过反复试验得出的,因为最初的条件不起作用:

         on p.pid = t2.pid and t1.seqnum = t2.seqnum
    

    这将为NULL返回列表中额外元素的p.id值。 Podliuska的方法也可以起作用;我刚刚开始沿着这条路走下去,where条件可以解决这个问题。