4

MySQL 5.5

父表:
编号 | 事实
子表:
parent_id | 外键 | 事实

现在,我想找到有特定孩子的父母,不多也不少。就像是:

SELECT t1.`id` 
from `parent_table` t1 
  LEFT JOIN `child_table` t2 ON t1.id=t2.parent_id
WHERE t2.`fk` = 1 
  AND t2.`fk` = 3  
  AND t2.`fk` = 5 
  AND t2.`fk` = 7 
  AND t2.`fk` = 9

但这也将获得带有这组孩子的父记录:1,2,3,5,7,9。我只想要那些拥有确切孩子的父母:1,3,5,7,9。

有办法吗?

编辑: child.parent_id 和 child.fk 都不是唯一的。child.fk 是链接到另一个表的外键。(“多对多关系”)所以父母很可能有孩子 1,2,3,5,7,9。我做这个查询的全部原因是尽量避免为 1,3,5,7,9 创建一个新的父级,如果这样的父级已经存在的话。

4

5 回答 5

4

假设child.id每个child.parent_id.

SELECT  a.id, a.facts
FROM    parent a
        INNER JOIN child b
            ON a.id = b.parent_ID
WHERE   b.id IN (1,3,5,7,9) AND        -- <<== list all ChildID here
        EXISTS                         -- <<== this part checks if the parent_ID
        (                              --           present on the EXISTS clause
            SELECT  parent_ID          --           which only filters parents
            FROM    child c            --           with 5 children
            WHERE   b.parent_ID = c.parent_ID
            GROUP   BY parent_ID
            HAVING  COUNT(*) = 5       -- <<== total number of children
        )
GROUP   BY a.id, a.facts
HAVING  COUNT(*) = 5                   -- <<== total number of children
于 2013-05-19T06:58:31.040 回答
1

类似于 eggyal 的解决方案,但我只是认为我会将它作为替代方案,因为它应该在 RDBMS 中更便携;

SELECT c.parent_id
FROM child_table c
GROUP BY c.parent_id
HAVING SUM(CASE WHEN c.id IN (1,3,5,7,9) THEN 1 ELSE -1 END) = 5

5是您想要匹配的 IN 子句中的孩子的确切数量(在本例中为全部)

仅适用于不同的 children,如果有重复,它将中断。

一个用于测试的 SQLfiddle

于 2013-05-19T08:01:07.747 回答
1
SELECT   parent_id
FROM     child_table
GROUP BY parent_id
HAVING   SUM(id IN (1,3,5,7,9)) = COUNT(*)
     AND COUNT(DISTINCT id) = 5
于 2013-05-19T07:16:47.647 回答
1

这个问题被称为(精确的)关系划分。这篇文章中有很多有用的代码和解释:Divided We Stand: The SQL of Relational Division

解决它的一种方法:

SELECT p.id AS parent_id
FROM parent AS p
WHERE EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk = 1 AND c.parent_id = p.id)
  AND EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk = 3 AND c.parent_id = p.id)
  AND EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk = 5 AND c.parent_id = p.id)
  AND EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk = 7 AND c.parent_id = p.id)
  AND EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk = 9 AND c.parent_id = p.id)
  AND NOT EXISTS
      ( SELECT * FROM child AS c
        WHERE c.fk NOT IN (1,3,5,7,9) AND c.parent_id = p.id) ;

还有一个类似问题的链接,在 StackOverflow 上,您可以在其中找到 10 多种不同的解决方案(注意:这不是针对精确除法,而是针对余数除法)和性能测试(针对 Postgres):如何过滤 SQL导致具有多通关系

于 2013-05-19T07:31:24.303 回答
0

我只需要解决这个问题的更一般情况,但在 SQL Server 中。原则可能是相似的。

SetX
  |-- Child1
  |-- Child2
  |-- Child4

SetY
  |-- Child1
  |-- Child3

ParentA -- has the children defined by SetX
  |-- Child1
  |-- Child2
  |-- Child4

ParentB -- has the children defined by SetY
  |-- Child1
  |-- Child3

ParentC -- does not match any of the sets
  |-- Child1
  |-- Child2
  |-- Child3
  |-- Child4

M 问题是围绕系统的用户(父母),他们在系统中被分配的角色(孩子),以及适合用户的工作描述(集合)。

我解决它的方法是使用位掩码。每个孩子都被分配了一个唯一的 2^n 位掩码。集合的成员关系就是用户的位掩码和等于集合的位掩码和。

当有很多孩子并且位掩码有溢出的危险时,您可以使用 bigint 位掩码或多个位掩码(确保将低阶位掩码设置为零)。

这是一个用 T-SQL 编写的示例 - 很确定它会很容易转换为 MySQL(如果有人想在他们自己的答案中这样做,我很高兴)。

declare @users table (
    name varchar(10)
)

declare @skills table (
    name varchar(20)
    , id int identity (0, 1)
    , bitmask bigint
)

declare @usersWithSkills table (
    userName varchar(10)
    , skillName varchar(20)
)

declare @groups table (
    name varchar(20)
    , bitmask bigint
)

declare @skillsInGroups table (
    groupName varchar(10)
    , skillName varchar(20)
)

insert  @users (name)
values  ('Pat')
    , ('Oprah')
    , ('Millie')
    , ('Bert')

insert  @skills (name)
values  ('Latin')
    , ('Icelandic')
    , ('Physics')

insert  @groups (name)
values  ('polyglot')
    , ('modern')
    , ('omniscient')

insert  @skillsInGroups (groupName, skillName)
values  ('polyglot', 'Latin')
    , ('polyglot', 'Icelandic')
    , ('modern', 'Physics')
    , ('modern', 'Icelandic')
    , ('omniscient', 'Latin')
    , ('omniscient', 'Icelandic')
    , ('omniscient', 'Physics')

insert  @usersWithSkills (userName, skillName)
values ('Pat', 'Latin')
    , ('Pat', 'Icelandic')
    , ('Oprah', 'Latin')
    , ('Oprah', 'Icelandic')
    , ('Oprah', 'Physics')
    , ('Millie', 'Icelandic')
    , ('Millie', 'Physics')
    , ('Bert', 'Latin')

-- give each skill a bitmask value
update  @skills
set bitmask = power(2, id)

-- set the total bitmask values for each group
update  g1
set g1.bitmask = t.sum_ind
from    @groups g1
    inner join (
        select  g.name, sum_ind = sum(r.bitmask)
        from    @groups g
            inner join @skillsInGroups rg
                on rg.groupName = g.name
            inner join @skills r
                on r.name = rg.skillName
        group   by g.name
    ) t
        on t.name = g1.name

select  u1.userName, groupName = g.name
from    (
        select  userName = u.name
            , bitmask_total = sum(r.bitmask)
        from    @users u
            inner join @usersWithSkills uir
                on uir.userName = u.name
            inner join @skills r
                on r.name = uir.skillName
        group   by u.name
    ) u1
    left join @groups g
        on g.bitmask = u1.bitmask_total

我从中得到的结果是

userName   groupName
---------- --------------------
Bert       NULL
Millie     modern
Oprah      omniscient
Pat        polyglot

(4 rows affected)
于 2022-02-15T03:08:24.883 回答