2

我有一个表 Hobby,其片段如下:

Name    Activity    Hours

John    Hiking      .5
Sam     Cycling     .5
Sam     Swimming    1
Sam     Hiking      .5
John    Running     1
Sam     Sailing     1

对于 (X, Y) 中的每个人 X,我想找到 X 和 Y 没有共同点的活动时间总和。例如,如果 John = X 且 Sam = Y,那么它将产生 1,因为 Running 是 John 唯一拥有而 Sam 没有的活动。

我的代码如下:

select a.Name, b.Name, sum(a.Hours)
from Hobby a, Hobby b
where a.Name <> b.Name and a.Activity <> b.Activity
group by a.Name, b.Name;

然而,这给了我一个错误的答案。我的代码有什么问题?

4

4 回答 4

4

我觉得这是一个棘手的问题。我最初的方法是使用full outer join. 但后来我意识到,如果一个名称中的活动不匹配,那么我也不会拥有这个名称。

因此,以下查询通过获取所有名称对的列表来工作。这是一个有序列表,因此一对给定的名称只出现一次。然后将其加入到Hobby表中两次,left outer join用于获取匹配项。然而,关键是当没有匹配时,上面的行Activity仍然存在,但有一个NULL值。

where子句在任一表中查找所有Activity具有 a 的 s 。NULL这些是不匹配的。然后只需将小时数相加就很简单了:

select names.Name1, names.Name2, sum(coalesce(h1.hours, h2.hours))
from (select distinct h1.Name as name1, h2.Name as name2
      from Hobby h1 cross join Hobby h2
      where h1.Name < h2.Name
     ) names left outer join
     Hobby h1
     on names.name1 = h1.name left outer join
     Hobby h2
     on names.name2 = h2.name and
        h1.Activity = h2.Activity
where h1.Activity is null or h2.Activity is null
group by names.Name1, names.Name2;
于 2013-09-19T22:06:20.993 回答
0

你的 from 子句是

FROM Hobby a, Hobby b

在 from 子句中放置逗号表示“CROSS JOIN”,这意味着第一个表中的每一行都与第二个表中的每一行相关。鉴于您的 where 子句,我认为这给出了一些相当大的数字。

您的查询需要有所不同:

select sum(hours) 
from hobby 
where name = 'John' 
  and activity not in (
    select activity 
    from hobby 
    where name = 'Sam'
  )
于 2013-09-19T22:08:02.363 回答
0

想想group by之前的潜在结果。仅考虑表 a 所在的行John, Hiking。在您的查询中,您将拥有

John    Hiking    0.5    John    Hiking      0.5
John    Hiking    0.5    Sam     Cycling     0.5
John    Hiking    0.5    Sam     Swimming    1
John    Hiking    0.5    Sam     Hiking      0.5
John    Hiking    0.5    John    Running     1
John    Hiking    0.5    Sam     Sailing     1

使用 where 子句,您将从表 b 中删除 John 行和 Hiking 行,离开:

John    Hiking    0.5    Sam     Cycling     0.5
John    Hiking    0.5    Sam     Swimming    1
John    Hiking    0.5    Sam     Sailing     1

所以你要在这个系列中数三遍这些时间John, Sam

这是一种调整它的方法,同时仍然只使用每个表一次:

Select
    a.Name,
    b.Name Name2,
    Sum(a.Hours) / count(distinct b.activity) 
       - Sum(case when a.Activity = b.Activity then a.Hours else 0 end) as Hours
From
    Hobby a,
    Hobby b
Where
    a.Name != b.Name
Group By
    a.Name,
    b.Name

Example Fiddle

于 2013-09-19T23:11:57.983 回答
0

如果您将人员和活动列表,并将所有其他人的列表加入其中,并测试其他人是否也参加了该活动,那么它应该可以解决问题。

with
  cte_unique_names as (
    select distinct name
    from   hobby)
select
  h.name participates,
  n.name does_not_participate,
  sum(hours) hours
from
  hobby h
cross join
  cte_unique_names n
where
  n.name != h.name and
  not exists (
    select null
    from   hobby h2
    where  h2.name     = n.name and
           h2.activity = h.activity)
group by
  h.name,
  n.name
于 2013-09-19T22:49:01.907 回答