关联字段时出错

时间:2012-04-02 19:02:07

标签: sql join correlation correlated-subquery

执行以下操作时出错:

SELECT *
from cGift c1 left join
    (select c2.gidnumb, "t" new from cGift c2 where c2.gidnumb = 
        (select c3.gidnumb from cgift c3 where c3.id = c2.id and c3.date =
            (select min(c4.date) from cgift c4 where c2.id == c4.id ))) tNew
    on tNew.gidnumb = c1.gidnumb

基本上我有表格cgift,上面有捐款清单。我需要一个返回cgift的查询,其中包含一个包含“t”或null的额外列。每个捐赠者(cgift.id)的第一笔捐款(cgift.date)应为“t”,其余捐赠者为空。

示例:

gidnumb..id....date......new
10.......1.....2/1/2010..null
11.......2.....1/1/2010..t
12.......3.....1/1/2010..t
13.......1.....3/1/2010..null
14.......2.....2/1/2010..null
15.......4.....1/1/2010..t
16.......1.....1/1/2010..t

空值可以是blancs或f或wtvr。

任何人都可以告诉我我的查询有什么问题,这让我疯狂。

3 个答案:

答案 0 :(得分:2)

我认为以下几乎适用于任何SQL产品:

SELECT
    cGift.gidnumb,
    cGift.id,
    cGift.date,
    first.isfirst
FROM cGift
LEFT JOIN (
    SELECT id, MIN(date) AS date, 't' AS isfirst
    FROM cGift
    GROUP BY id
) first ON cGift.id = first.id AND cGift.date = first.date

更新(解决其他标准):

如果某人可能会在MIN(date)上多次捐款,并且您只希望将一笔捐款标记为t,则可以执行以下操作:

SELECT
    cGift.gidnumb,
    cGift.id,
    cGift.date,
    first.isfirst
FROM cGift
LEFT JOIN (
    SELECT
        MIN(g.gidnumb) AS g.gidnumb,
        g.id,
        g.date,
        't' AS isfirst
    FROM cGift g
    INNER JOIN (
        SELECT id, MIN(date) AS date
        FROM cGift
        GROUP BY id
    ) f ON g.id = f.id AND g.date = f.date
    GROUP BY g.id, g.date
) first ON cGift.id = first.id AND cGift.date = first.date

也就是说,最里面的查询找到每个人的最小天数,就像之前的解决方案一样,但它也会详细列出具有特定gidnumb值的列表,确保每个人只有一行匹配此列表在cGift表中。

该查询仍应在任何DBMS中运行。考虑到双重分组,它可能效率较低。这是一个替代方案,它也只使用标准SQL,没有特定于供应商的功能(它应该比以前的查询更灵活):

SELECT
    gidnumb,
    id,
    date,
    CASE
        WHEN NOT EXISTS (
            SELECT *
            FROM cGift
            WHERE id = g.id
                AND (
                    date < g.date OR
                    date = g.date AND gidnumb < g.gidnumb
                )
        )
        THEN 't'
    END AS isfirst
FROM cGift g

正如您所看到的,isfirst列是使用表格上的自检来计算的:如果此表中没有相同id的行和更早的日期,或者如果日期相同,较小的gidnumb,此行应标记为t。如果ELSE没有CASE部分,则隐含ELSE NULL。如果您愿意,可以添加ELSE 'f'

之类的内容

尽管如此,您的SQL产品可能拥有可以通过构建可能更简单和更有效的查询而受益的功能。例如,有些产品支持排名功能(它们已经是SQL标准的一部分,只是它们还没有被普遍支持),而这就是你可以用名为ROW_NUMBER()的排名函数做的事情:

SELECT
    gidnumb,
    id,
    date,
    CASE ROW_NUMBER() OVER (PARTITION BY id ORDER BY date, gidnumb)
        WHEN 1
        THEN 't'
    END AS isfirst
FROM cGift g

此查询对按id对其进行分区并按date排序,然后按gidnumb排序的行进行排名。在这种情况下排名为1的每一行都应成为应与t区分的行。

答案 1 :(得分:1)

我在查询中看不到错误。

但我建议您稍微修改一下您的查询。

这将返回每个身份的第一笔捐款

SELECT id, gidnymb, date, "t" As new_column
FROM cGift
GROUP BY id
HAVING date = MIN(date);

这将返回下一次捐款

SELECT id, gidnymb, date, null AS new_column
FROM cGift
LEFT JOIN (
    SELECT id, gidnymb, date, "t" As new_column
    FROM cGift
    GROUP BY id
    HAVING date = MIN(date)
) USING(id)
WHERE  new_column IS NULL

现在使用UNION合并它。

(SELECT id, gidnymb, date, "t" As new_column
FROM cGift
GROUP BY id
HAVING date = MIN(date))

UNION

(SELECT id, gidnymb, date, null AS new_column
FROM cGift
LEFT JOIN (
    SELECT id, gidnymb, date, "t" As new_column
    FROM cGift
    GROUP BY id
    HAVING date = MIN(date)
) USING(id)
WHERE  new_column IS NULL)

我没有测试它,但你已经有了这个想法。

答案 2 :(得分:0)

在SQL Server 2005+中,您可以执行此操作

SELECT
    gidnumb,
    id,
    Date,
    CASE RANK() OVER (PARTITION BY id ORDER BY Date)
        WHEN 1 Then 't'
        ELSE Null
    END
FROM
    cGift