【问题标题】:SQL: how to select a single id ("row") that meets multiple criteria from a single columnSQL:如何从单个列中选择满足多个条件的单个 id(“行”)
【发布时间】:2011-11-03 00:03:54
【问题描述】:

我有一个很窄的表:user_id, ancestry。

user_id 列是不言自明的。

祖先列包含用户祖先所在的国家/地区。

一个用户可以在表格上拥有多行,因为一个用户可以拥有来自多个国家/地区的祖先。

我的问题是:如何选择祖先来自多个指定国家的用户?

例如,显示所有祖先来自英格兰、法国和德国的用户,并为每个满足该条件的用户返回 1 行。

那是什么 SQL?

 user_id     ancestry

---------   ----------

    1        England
    1        Ireland
    2        France
    3        Germany
    3        Poland
    4        England
    4        France
    4        Germany
    5        France
    5        Germany

对于上述数据,我预计结果为“4”,因为 user_id 4 的祖先来自英国、法国和德国。

提前致谢。

附:澄清一下:是的,user_id / ancestry 列是一对唯一的,因此不会为给定用户重复国家/地区。

附言我正在寻找来自所有 3 个国家(英国、法国和德国)的用户(这些国家是任意的)。

P.P.P.S.我不是在寻找特定于某个 RDBMS 的答案。我希望“笼统地”回答这个问题。

我很满意为每个提供的查询重新生成 where 子句,生成 where 子句可以通过编程方式完成(例如,我可以构建一个函数来构建 WHERE / FROM - WHERE 子句)。

【问题讨论】:

  • 您是指拥有 一个 'England'、'France'、'Germany' 的用户,还是拥有 *all 3" 的用户?
  • 您使用的是什么 DBMS? Oracle、SQL Server、Mimer、DB2 ...?
  • 您希望该查询使用您正在寻找的国家/地区进行参数化,还是您对“重新生成”每个查询的 where 子句感到满意?
  • 很像问题I'm facing...

标签: sql select


【解决方案1】:

我遇到了与您类似的问题,只是我想要一个特定的“祖先”子集。 Hong Ning 的查询是一个好的开始,除了它将返回包含重复和/或额外祖先的组合记录(例如,它还会返回具有祖先('England'、'France'、'Germany'、'Netherlands')和(' England', 'France', 'England')。假设您只需要三个并且只需要三个,则需要以下查询:

SELECT Src.user_id
FROM yourtable Src
WHERE ancestry in ('England', 'France', 'Germany')
    AND EXISTS (
        SELECT user_id
        FROM dbo.yourtable
        WHERE user_id = Src.user_id
        GROUP BY user_id
        HAVING COUNT(DISTINCT ancestry) = 3
        )
GROUP BY user_id
HAVING COUNT(DISTINCT ancestry) = 3

【讨论】:

    【解决方案2】:

    像上面的答案,但我有一个重复的记录,所以我必须创建一个不同的子查询

    Select user_id
    (
       select distinct userid
       from yourtable
       where user_id = @userid
    
    ) t1
    where 
    ancestry in ('England', 'France', 'Germany')
    group by user_id
    having count(user_id) = 3
    

    这是我使用的,因为我有多个记录(下载日志),这会检查所有必需的文件是否已下载

    【讨论】:

      【解决方案3】:

      这个问题已经有几年了,但我是通过重复来的。我也想提出一个更通用的解决方案。如果您知道自己始终拥有固定数量的祖先,则可以使用答案中已经建议的一些自连接。如果您想要通用方法,请继续阅读。

      你在这里需要的是关系代数中的商。商或多或少是笛卡尔积的反转(或 SQL 中的交叉连接)。

      假设你的祖先集A 是(我在这里使用表格符号,我认为这样更便于理解)

      ancestry
      -----------
      'England'
      'France'
      'Germany'
      

      你的用户集U

      user_id
      --------
         1
         2
         3
      

      那么笛卡尔积C=AxU是:

      user_id  |  ancestry
      ---------+-----------
         1     | 'England'
         1     | 'France'
         1     | 'Germany'
         2     | 'England'
         2     | 'France'
         2     | 'Germany'
         3     | 'England'
         3     | 'France'
         3     | 'Germany'
      

      如果你计算集商U=C/A 那么你得到

      user_id
      --------
         1
         2
         3
      

      如果您重做笛卡尔积UXA,您将再次得到C。但请注意,对于一组T(T/A)xA 不一定会重现T。例如,如果T

      user_id  |  ancestry
      ---------+-----------
         1     | 'England'
         1     | 'France'
         1     | 'Germany'
         2     | 'England'
         2     | 'France'
      

      那么(T/A)就是

      user_id
      --------
         1
      

      (T/A)xA 将是

      user_id  |  ancestry
      ---------+------------
         1     | 'England'
         1     | 'France'
         1     | 'Germany'
      

      请注意,user_id=2 的记录已被商和笛卡尔积运算消除。

      您的问题是:哪个 user_id 在您的祖先集中拥有来自所有国家/地区的祖先?换句话说,您想要U=T/A,其中T 是您的原始集合(或您的桌子)。

      要在 SQL 中实现商,您必须执行 4 个步骤:

      1. 创建您的祖先集的笛卡尔积和 所有用户 ID。
      2. 在笛卡尔积中查找原始集合中没有伙伴的所有记录(左连接)
      3. 从 2) 的结果集中提取 user_ids
      4. 从原始集合中返回所有未包含在 3) 的结果集中的 user_ids

      让我们一步一步来。我将使用 TSQL 语法(Microsoft SQL 服务器),但它应该很容易适应其他 DBMS。作为表的名称(user_id, ancestry) 我选择ancestor

      CREATE TABLE ancestry_set (ancestry nvarchar(25))
      INSERT INTO ancestry_set (ancestry) VALUES ('England')
      INSERT INTO ancestry_set (ancestry) VALUES ('France')
      INSERT INTO ancestry_set (ancestry) VALUES ('Germany')
      
      CREATE TABLE ancestor ([user_id] int, ancestry nvarchar(25))
      INSERT INTO ancestor ([user_id],ancestry) VALUES (1,'England')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(1,'Ireland')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(2,'France')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(3,'Germany')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(3,'Poland')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'England')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'France')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(4,'Germany')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(5,'France')
      INSERT INTO ancestor ([user_id],ancestry) VALUES(5,'Germany')
      

      1) 创建您的祖先集和所有 user_id 集的笛卡尔积。

      SELECT a.[user_id],s.ancestry
      FROM ancestor a, ancestry_set s
      GROUP BY a.[user_id],s.ancestry
      

      2) 查找笛卡尔积中在原始集合中没有伙伴的所有记录(左连接)和

      3) 从 2) 的结果集中提取 user_ids

      SELECT DISTINCT cp.[user_id]
      FROM (SELECT a.[user_id],s.ancestry
            FROM ancestor a, ancestry_set s
            GROUP BY a.[user_id],s.ancestry) cp
         LEFT JOIN ancestor a ON cp.[user_id]=a.[user_id] AND cp.ancestry=a.ancestry
      WHERE a.[user_id] is null
      

      4) 从原始集合中返回所有未包含在 3) 的结果集中的 user_id

      SELECT DISTINCT [user_id]
      FROM ancestor
      WHERE [user_id] NOT IN (
         SELECT DISTINCT cp.[user_id]
         FROM (SELECT a.[user_id],s.ancestry
               FROM ancestor a, ancestry_set s
               GROUP BY a.[user_id],s.ancestry) cp
         LEFT JOIN ancestor a ON cp.[user_id]=a.[user_id] AND cp.ancestry=a.ancestry
         WHERE a.[user_id] is null
         )
      

      【讨论】:

        【解决方案4】:

        拥有其中一个三个国家/地区的用户

        SELECT DISTINCT user_id
        FROM table
        WHERE ancestry IN('England','France','Germany')
        

        拥有所有 3 个国家/地区的用户

        SELECT DISTINCT A.userID
        FROM table A
           INNER JOIN table B on A.user_id = B.user_id
           INNER JOIN table C on A.user_id = C.user_id
        WHERE A.ancestry = 'England'
           AND B.ancestry = 'Germany'
           AND C.ancestry = 'France'
        

        【讨论】:

        • -1 这将返回在英格兰、法国或德国有血统的每个用户。
        • @konerak——没错。他不清楚他是想要全部 3 个,还是只想要一个。无论如何,我为这两种可能性提供了解决方案。
        • 更好,但将祖先改为祖先并在 A=C 上加入表 C 而不是 A=B...
        • 不是一个好的解决方案,因为您需要为检查的每个可能值进行连接,如果您的数量有限,则可以查找。目前世界上有 194 个国家/地区,所以这意味着 195 个加入...不太实用。
        【解决方案5】:

        第一种方式:JOIN:

        让人们拥有多个国家:

        SELECT u1.user_id 
        FROM users u1
        JOIN users u2
        on u1.user_id  = u2.user_id 
        AND u1.ancestry <> u2.ancestry
        

        获取来自 2 个特定国家/地区的人员:

        SELECT u1.user_id 
        FROM users u1
        JOIN users u2
        on u1.user_id  = u2.user_id 
        WHERE u1.ancestry = 'Germany'
        AND u2.ancestry = 'France'
        

        对于 3 个国家/地区...加入 3 次。只得到一次结果,不同的。

        第二种方式:GROUP BY

        这将获得有 3 行(有...计数)的用户,然后您指定允许哪些行。请注意,如果您在 (user_id, ancestry) 上没有 UNIQUE KEY,则出现 3 次的 'id, england' 的用户也将匹配...所以这取决于您的表结构和/或数据。

        SELECT user_id 
        FROM users u1
        WHERE ancestry = 'Germany'
        OR ancestry = 'France'
        OR ancestry = 'England'
        GROUP BY user_id
        HAVING count(DISTINCT ancestry) = 3
        

        【讨论】:

        • -1 您的第一个代码不是他所要求的...第二个选项在正确的轨道上(您在评论中注意到他需要做什么才能使其正确)...第三个选项仅在表中不可能有重复记录的情况下才有效,这可能是这种情况,但 OP 没有指定。
        • 我没有对你投反对票,但首先你应该将WHERE 移到GROUP BY 上方。并使用COUNT(DISTINCT ancestry)
        • @Bill:谢谢!第一个是编辑错误,但 COUNT DISTINCT 是一个很好的捕获:)
        • 删除了反对票——看起来第三个选项现在可行,在 HAVING 子句中计数不同。
        【解决方案6】:

        蛮力(仅在 Oracle 系统上测试过,但我认为这是相当标准的):

        select distinct usr_id from users where user_id in (
            select user_id from (
              Select user_id, Count(User_Id) As Cc
              From users 
              GROUP BY user_id
            ) Where Cc =3
          )
          and ancestry in ('England', 'France', 'Germany')
        ;
        

        编辑:我更喜欢@HuckIt 的回答。

        【讨论】:

        • plsql 是否允许您在 WHERE 中引用列别名 (Cc)?
        【解决方案7】:
        SELECT DISTINCT (user_id) 
        FROM [user]
        WHERE user.user_id In (select user_id from user where ancestry = 'England') 
            And user.user_id In (select user_id from user where ancestry = 'France') 
            And user.user_id In (select user_id from user where ancestry = 'Germany');`
        

        【讨论】:

        • 你可以把这三个语句结合起来,不是吗?类似于 user.user_id In (select user_id from user where ancestry in ('England','France','Germany'))
        • 您需要将这三个语句“与”在一起,以确保用户来自所有 3 个国家/地区。如果将它们组合成一个 IN 子句,则只能确保它们来自 3 个国家之一。有趣地回应对 9 岁答案的 2 岁评论。我应该再检查一下。
        【解决方案8】:

        如果您想获取满足所有条件的所有 user_id,其中一种方法是:

        SELECT DISTINCT user_id FROM table WHERE ancestry IN ('England', '...', '...') GROUP BY user_id HAVING count(*) = <number of conditions that has to be satisfied> 
        

        等等。如果您需要取所有满足至少一个条件的 user_ids,那么您可以这样做

        SELECT DISTINCT user_id from table where ancestry IN ('England', 'France', ... , '...')
        

        我不知道是否有类似于 IN 的东西,但它使用 AND 而不是 OR 连接条件

        【讨论】:

        • 我认为您的第一个查询不会起作用,因为每一行只有一个祖先值。没有一行可以是“英格兰”和“法国”。
        【解决方案9】:

        试试这个:

        Select user_id
        from yourtable
        where ancestry in ('England', 'France', 'Germany')
        group by user_id
        having count(user_id) = 3
        

        最后一行表示用户的祖先拥有所有 3 个国家/地区。

        【讨论】:

        • 正确,只要该表不包含多次用户祖先组合。
        • 如果@Konerak 正确指出的问题是可能的,请选择表上的不同子查询而不是表本身。
        • 我怀疑表有重复的行,否则查询会先使用子查询过滤掉重复的行。
        猜你喜欢
        • 2013-08-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-09-08
        相关资源
        最近更新 更多