2017-03-15 71 views
2

我有两个表。一个拥有Objects,另一个拥有关于每个对象的Settings。并非Objects表中的所有行在Settings表中都有对应的行。 Settings表中有一个特殊的行,应该用于“其他”对象。如何加入“其他”行

如何在ObjectsSettings之间创建连接,以便在有或没有“其他”设置的情况下获得给定设置?

例如,考虑下面的脚本:

CREATE TABLE #Objects (Code nvarchar(20) not null); 
CREATE TABLE #Settings (Code nvarchar(20) not null, Value int not null); 

INSERT INTO #Objects 
VALUES 
    ('A'), 
    ('B'), 
    ('D') 

INSERT INTO #Settings 
VALUES 
    ('A', 1), 
    ('B', 2), 
    ('C', 3), 
    ('Other', 4) 

SELECT 
    #Objects.Code, 
    #Settings.Value 
FROM 
    #Objects 
    JOIN #Settings 
     ON #Objects.Code = #Settings.Code 
     OR #Settings.Code = 'Other' 

DROP TABLE #Settings, #Objects 

我想要得到这样的:

Code | Value 
---- | ----- 
A | 1 
B | 2 
D | 4 

我真的开始是:

Code | Value 
----- | ----- 
A  | 1 
A  | 4 
B  | 2 
B  | 4 
D  | 4 
+1

我很好奇看到EXE cution计划对迄今为止的四个工作回答中的每一个都与来自合理大小和索引表的实际数据进行了对比。对于样本数据,它可能会给出与所有答案相同或相似的计划,并且任何差异都太小而无法衡量。 –

+0

现在有五个工作答案:)但是我不愿意投票给他们中的任何一个而没有看到计划。 –

+0

@JoelCoehoorn可悲的是,我的表现要求并不严格('在1000个左右的对象上半小时内跑步'),但是我的开发预算是('你还没有完成它,如果不是为什么?')。所以我猜大数据查询计划将不得不等待另一天。我同意,尽管做分析会很好。 –

回答

1

使用left join得到null其中o.Code在没有匹配 ,并且使用coalesce()s.Valuenull时返回指定的替换value#Settings

您可以使用isnull()而不是​​3210,结果在这个例子中是相同的。

我不知道这是否可以接受,但它返回正确的结果:

select 
    o.Code 
    , coalesce(s.Value,x.Value) as Value 
from #Objects o 
    left join #Settings s 
    on o.Code = s.Code 
    cross join (
    select top 1 value 
    from #Settings 
    where Code = 'Other' 
    ) x 

rextester演示:http://rextester.com/EBUG86037

回报:

+------+-------+ 
| Code | Value | 
+------+-------+ 
| A |  1 | 
| B |  2 | 
| D |  4 | 
+------+-------+ 

在形式@ RBarryYoung更喜欢:

select 
    o.Code 
    , coalesce(s.Value,x.Value) as Value 
from #Objects o 
    left join #Settings s 
    on o.Code = s.Code 
    inner join #Settings x 
    on x.Code = 'Other' 

这更简洁(节省您很多keystrokes)并生成与我的初始答案相同的执行计划。不管它在做什么或多或少都是由你来决定的,我喜欢这两者。

+0

不错,尽管我更喜欢Code ='Other''上的'inner join(..)到'cross join(..其中Code ='Other')'。 – RBarryYoung

+0

@RBarryYoung谢谢你,我更新了我的答案,包括你的选择。 – SqlZim

2

您可以用APPLY做到这一点:

SELECT o.Code, s.Value 
FROM #Objects o 
CROSS APPLY (
    SELECT TOP 1 * 
    FROM #Settings s 
    WHERE s.Code = o.Code or s.Code = 'Other' 
    ORDER BY case when s.Code = o.Code then 0 else 1 end 
) s 

为了好玩:

SELECT o.Code, s2.Value 
FROM #Objects o 
LEFT JOIN #Settings s1 on s1.Code = o.Code 
INNER JOIN #Settings s2 on s2.Code = coalesce(s1.Code, 'Other') 
:由 GurvjyaoSqlZim,这是在同一个基本主题的所有变化,从答案的混合

到目前为止,这种方法(LEFT JOIN + INNER JOIN ON COALESCE())是我最喜欢的选择。

请注意,这仅适用于每个对象记录只能有一个设置记录的情况。如果这种情况发生变化,那么应用回答仍然可以工作,但其他答案可能无效。

+0

我喜欢这个答案,主要是因为我得到的问题实际上是一些更复杂的方式的一小部分,我已经使用了一个APPLY。 –

1

如果有将是一个“其他”值,那么你可以做的join两次 - 一个left join,另一个实际上是一个cross join

select o.Code, 
    coalesce(s.Value, s2.value) as value 
from #Objects o 
left join #Settings s on o.Code = s.Code 
join #Settings s2 on s2.Code = 'Other' 
1

另一种方法是使用CTE添加一个附加列[Alternative_code]为[#Object]表,对于[代码]的值为“其他”在[#Settings] 不存在,然后使用这个CTE与#Settings表加入如下所示

; with c as (
    select alternative_Code = isnull(s.code, 'Other'), o.Code 
    from #Objects o 
    left join #Settings s 
     on o.Code = s.Code) 
select c.Code, s.value 
from c 
inner join #Settings s 
on c.alternative_Code = s.Code 
+0

你可以在没有cte的情况下做到这一点。只需加入设置两次,并在第二次加入的情况下进行合并。 –