【问题标题】:Select count field of rows that are related to another table选择与另一个表相关的行的计数字段
【发布时间】:2012-09-28 23:26:23
【问题描述】:

我长期以来一直在努力解决这个问题,但不知道如何解决它。我很难描述,所以请耐心等待。有两个表:

表“用户”

UserId PK
Gender

表格“表格”

FormId PK
UserId1 FK
UserId2 FK
Type

表单始终与两个用户相关,但并非所有用户都有相关表单。现在我想只计算那些具有相关表单的用户的指定性别。

因此,我想拥有某物。像这样:

# |  Gender | GenderCount
1 |  male   |     43
2 |  female |     12
3 |  trans  |     2

我尝试了以下 SQL 脚本,但结果并不明显(所有 GenderCount 的总和大于实际用户数)

SELECT u.Gender AS 'Gender', COUNT(u.Gender) AS 'GenderCount' 
FROM Users u, Forms f 
WHERE ((f.UserId1 = u.UserId) 
    OR (f.UserId2 = u.UserId)) 
AND (Type = 'Foo') 
GROUP BY Gender 
ORDER BY GenderCount 
DESC

有什么技巧可以解决这个问题吗?

【问题讨论】:

  • 为什么不 COUNT(DISTINCT u.Gender)?
  • @WillPalmer 如果我使用 COUNT(DISTINCT u.Gender) 我会得到male => 1, female => 1, trans => 1
  • 啊,我想我现在明白了。您想知道每种性别中有多少人回答了任何表单,但您想确保每个用户只计算一次,对吗?
  • @WillPalmer 正确。感谢您解决我的问题。

标签: sql


【解决方案1】:

我们来看看你想要什么:

  • 每种性别有多少人回答了任何形式?
  • 注意:每个用户都应该只计算一次,无论他们填写了多少表格。

这样说,答案变得相当明显,至少在伪代码中:

SELECT
    u.Gender,
    COUNT(u.Gender)
FROM
    Users u
WHERE
    [User has answered a form]
GROUP BY
    u.Gender

确定用户是否已回答表单的最简单方法取决于所使用的 SQL 的特定风格。您需要使用子查询。有几种访问方式可供选择。

IN是最常用的方法:

SELECT
    u.Gender        Gender,
    COUNT(u.Gender) GenderCount
FROM
    Users u
WHERE
    u.id IN (
        SELECT f.UserId1 user_id FROM Forms f WHERE Type = 'Foo'
        UNION
        SELECT f.UserId2 user_id FROM Forms f WHERE Type = 'Foo'
    )
GROUP BY
    Gender
ORDER BY
    GenderCount DESC

在可用的情况下,EXISTS 阅读起来更自然,有时速度更快:

SELECT
    u.Gender        Gender,
    COUNT(u.Gender) GenderCount
FROM
    Users u
WHERE
    EXISTS(
        SELECT '1'
        FROM Forms f
        WHERE
            (f.UserId1 = u.id OR f.UserId2 = u.id)
            AND Type = 'Foo'
    )
GROUP BY
    Gender
ORDER BY
    GenderCount DESC

关于速度:查询优化器通常会在可能的情况下将IN 转换为EXISTS,以避免不必要地选择额外的行。但是,使用多列需要ORUNION,所以即使在这种情况下也可能很漂亮。即:ORUNION 都不能很好地处理索引。

【讨论】:

    【解决方案2】:
    SELECT u1.Gender AS 'Gender', COUNT(*) AS 'GenderCount'
    FROM
        Users u1 
            INNER JOIN 
        (SELECT DISTINCT u.UserId
        FROM 
            Users u
                INNER JOIN Forms f ON ((f.UserId1 = u.UserId) 
                                    OR (f.UserId2 = u.UserId))
                                    AND (f.Type = 'Foo')) T ON T.UserId = u1.UserId
    GROUP BY Gender 
    ORDER BY GenderCount DESC
    

    【讨论】:

    • 在将第 1 行中的脚本更正为 SELECT u1.Gender 后,它似乎工作了。
    • 我赞成您的回答,因为您的脚本产生了正确的性别数量。不过 Will 使用EXISTS 的方法似乎更聪明,因为它避免了DISTINCT,这会导致sql server 的额外负载。
    【解决方案3】:

    跳过每个用户生成多行的连接:

    SELECT Gender, COUNT(Gender) AS 'GenderCount' 
    FROM Users
    WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo' 
                     UNION 
                     SELECT UserId2 FROM Forms WHERE Type = 'Foo')
    GROUP BY Gender 
    ORDER BY GenderCount DESC
    

    或者,如果您希望避免使用 UNION(顺便说一句,这在这种情况下完全有效),您可以像这样使用 OR:

    SELECT Gender, COUNT(Gender) AS 'GenderCount' 
    FROM Users
    WHERE UserId IN (SELECT UserId1 FROM Forms WHERE Type = 'Foo')
       OR UserId IN (SELECT UserId2 FROM Forms WHERE Type = 'Foo')
    GROUP BY Gender 
    ORDER BY GenderCount DESC
    

    正如其他人所指出的,也有一些方法可以使用 JOIN 来做到这一点。但是,JOIN 为 DBMS 引擎增加了不必要的复杂性,因为它首先需要匹配行,然后减少到 DISTINCT 值。

    【讨论】:

    • 您可以避免并集并使用 OR 代替。最终一切都归结为同一件事......
    • @PinnyM 简单地将UNION 替换为OR 似乎会导致关键字“OR”和“)”附近的语法不正确
    • @PinnyM 如果我删除FormUsers,您的第一个答案似乎才有效。这是否建议作为别名?
    • 正确 - 它是作为一个别名,对于非连接子查询来说是无效的 SQL。修复并添加了 OR 选项的代码。
    • 我赞成您的回答,因为您的脚本产生了正确的性别数量。但是 Will 使用 EXISTS 的方法似乎更聪明,因为我不必两次指定 WHERE 子句,它可能会稍微快一些。
    【解决方案4】:

    你应该使用

    count(distinct u.UserId)
    

    这样用户只会被计算一次:count(distinct field_name) 会计算 field_name 中包含的唯一值的数量,因此在主键上计算不同的值会为您提供唯一用户的数量,这就是您要查找的内容。

    另外,您最好不要加入,而是使用这样的 in 子句

    select Gender, count(distinct UserId) as GenderCount
    from Users
    where u.UserId in (select UserId1 from Forms) or u.UserId in (select UserId2 from Forms)
    

    它可能也会稍微快一点。

    【讨论】:

    • 这里相同:使用您的脚本会产生一个性别总和,远远大于实际用户数
    猜你喜欢
    • 2013-06-04
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 2016-07-14
    • 2019-11-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多