如何在匹配查询中停止添加行?

时间:2012-05-04 03:29:44

标签: sql sql-server join functional-programming

我很难将我想要的内容翻译成函数式编程,因为我认为这是必要的。基本上,我有一张表格和一份期望表。在Expectation视图中,我希望它通过表格表查看并告诉我每个人是否找到了匹配项。但是,当我尝试使用联接来完成此操作时,联接会在两个或多个表单匹配时向Expectation表添加行。我不想要这个。

以迫切的方式,我想要相当于:

ForEach (row in Expectation table)
{
    if (any form in the Form table matches the criteria)
    {
         MatchID = form.ID;
         SignDate = form.SignDate;
         ...
    }
}

我在SQL中拥有的是:

SELECT
    e.*, match.ID, match.SignDate, ...
FROM
   POFDExpectation e LEFT OUTER JOIN
   (SELECT MIN(ID) as MatchID, MIN(SignDate) as MatchSignDate, 
        COUNT(*) as MatchCount, ...
    FROM Form f
    GROUP BY (matching criteria columns)
        ) match
        ON (form.[match criteria] = expectation.[match criteria])

哪个有效,但速度很慢,每次有两个匹配,就会在Expectation结果中添加一行。在数学上我理解连接是交叉乘法,这是预期的,但我不确定如何在没有它们的情况下这样做。可能是子查询?

我无法提供太多有关实施的详细信息,但我很乐意尝试任何建议并回复结果。我有880个Expectation行,返回了942个结果。如果我只允许匹配一个表单的结果,我会得到831个结果。两者都不可取,所以如果你的确是880,那么你的答案是可接受的。

编辑:我使用的是SQL Server 2008 R2,但通用解决方案最好。

示例代码:

--DROP VIEW ExpectationView; DROP TABLE Forms; DROP TABLE Expectations;
--Create Tables and View
CREATE TABLE Forms (ID int IDENTITY(1,1) PRIMARY KEY, ReportYear int, Name varchar(100), Complete bit, SignDate datetime)
GO
CREATE TABLE Expectations (ID int IDENTITY(1,1) PRIMARY KEY, ReportYear int, Name varchar(100))
GO
CREATE VIEW ExpectationView AS select e.*, filed.MatchID, filed.SignDate, ISNULL(filed.FiledCount, 0) as FiledCount, ISNULL(name.NameCount, 0) as NameCount from Expectations e LEFT OUTER JOIN 
(select MIN(ID) as MatchID, ReportYear, Name, Complete, Min(SignDate) as SignDate, COUNT(*) as FiledCount from Forms f GROUP BY ReportYear, Name, Complete) filed
on filed.ReportYear = e.ReportYear AND filed.Name like '%'+e.Name+'%' AND filed.Complete = 1 LEFT OUTER JOIN 
(select MIN(ID) as MatchID, ReportYear, Name, COUNT(*) as NameCount from Forms f GROUP BY ReportYear, Name) name 
on name.ReportYear = e.ReportYear AND name.Name like '%'+e.Name+'%'
GO
--Insert Text Data
INSERT INTO Forms (ReportYear, Name, Complete, SignDate)
SELECT 2011, 'Bob Smith', 1, '2012-03-01' UNION ALL
SELECT 2011, 'Bob Jones', 1, '2012-10-04' UNION ALL
SELECT 2011, 'Bob', 1, '2012-07-20'
GO
INSERT INTO Expectations (ReportYear, Name)
SELECT 2011, 'Bob'
GO
SELECT * FROM ExpectationView --Should only return 1 result, returns 9

'提交'表示他们已经填写了一份表格,“名称”显示他们可能已经开始一个但未完成。我的观点有四种不同的“匹配标准” - 每一种都要严格一些,并对每种标准进行计数。 '名称仅匹配','松散匹配','匹配'(默认),'紧密匹配'(如果有多个默认匹配,则使用。

2 个答案:

答案 0 :(得分:6)

当我想保持JOIN类型的查询格式时,我就是这样做的:

SELECT
    e.*, 
    match.ID, 
    match.SignDate, 
    ...
FROM        POFDExpectation e 
OUTER APPLY (
    SELECT  TOP 1
        MIN(ID) as MatchID, 
        MIN(SignDate) as MatchSignDate, 
        COUNT(*) as MatchCount, 
        ...
    FROM    Form f
    WHERE   form.[match criteria] = expectation.[match criteria] 
    GROUP BY ID (matching criteria columns)   
    -- Add ORDER BY here to control which row is TOP 1
    ) match

它通常也表现得更好。


从语义上讲,{CROSS | OUTER} APPLY(table-expression)指定一个表表达式,该表表达式对FROM子句的前面表达式中的每一行调用一次,然后连接到它们。但是,实际上,编译器几乎完全相同地处理它。

实际差异在于,与JOIN表表达式不同,APPLY表表达式是针对每一行动态重新计算的。因此,它不依赖于ON子句,而是依赖于自己的逻辑和WHERE子句来限制/匹配前面的表表达式。这也允许它引用前面表格表达式的列值, 里面 它自己的内部子查询表达式。 (这在JOIN中是不可能的)

我们想要这个而不是JOIN的原因是我们需要在子查询中使用TOP 1来限制其返回的行,但这意味着我们需要将ON子句条件移动到内部WHERE子句,以便在 之前应用 评估TOP 1。而 意味着我们需要在这里申请,而不是更常见的JOIN。

答案 1 :(得分:1)

@RBarryYoung在我问的时候回答了这个问题,但还有第二个问题,我没有说清楚。我真正想要的是他的答案和this question的组合,所以我在这里使用的是记录:

SELECT
    e.*, 
     ...
    match.ID, 
    match.SignDate, 
    match.MatchCount
FROM 
    POFDExpectation e 
    OUTER APPLY (
        SELECT  TOP 1
            ID as MatchID, 
            ReportYear,
             ...
            SignDate as MatchSignDate, 
            COUNT(*) as MatchCount OVER ()
        FROM
            Form f
        WHERE
            form.[match criteria] = expectation.[match criteria]
        -- Add ORDER BY here to control which row is TOP 1
        ) match