MySQL从具有多个记录的多个表中选择最高连接值的单个记录

标签 mysql sql greatest-n-per-group

我有以下表格:

成员

这存储了我们系统的成员列表。

---------------------
| member_id | name  |
---------------------
| 1         | Bob   |
---------------------
| 2         | Joe   |
---------------------
| 3         | Tom   |
---------------------
| 4         | Bill  |
---------------------
| 5         | Will  |
---------------------

类别

这存储了我们系统的类别。默认情况下,类别对成员不可见。成员(member)必须拥有有效许可才能访问类别(见下文)。

----------------------
| cat_id    | name   |
----------------------
| 1         | Cat1   |
----------------------
| 2         | Cat2   |
----------------------
| 3         | Cat3   |
----------------------

许可证

存储成员拥有的许可证。一个成员可以拥有多个许可证。许可证可以有生命周期并且会过期。许可证过期后,成员(member)将无法再查看该类别。

------------------------------------------------------
| id    | catid   | subid | valid_from  | valid_to   |
------------------------------------------------------
| 1     | 1       | 1     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 2     | 1       | 2     | 1999-01-01  | 2001-01-02 |
------------------------------------------------------
| 3     | 1       | 3     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 4     | 1       | 4     | 1999-01-01  | 2000-01-01 |
------------------------------------------------------
| 5     | 1       | 5     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 6     | 2       | 1     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 7     | 2       | 2     | 1999-01-01  | 2001-01-02 |
------------------------------------------------------
| 8     | 2       | 3     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 9     | 2       | 4     | 1999-01-01  | 2000-01-01 |
------------------------------------------------------
| 10    | 2       | 5     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 11    | 3       | 1     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------
| 12    | 3       | 2     | 2014-01-01  | 2020-12-01 |
------------------------------------------------------

偏好

偏好表存储成员(member)是否希望接收与类别相关的电子邮件。成员(member)可以将偏好设置为“1”表示“希望接收”或“0”表示“不希望接收”。一个怪癖是,如果成员没有记录(或空值),我们会假设他们希望接收。

-----------------------------------
| id    | catid   | subid | pref  |
-----------------------------------
| 1     | 1       | 1     |  0    |
-----------------------------------
| 2     | 2       | 1     |  1    |
-----------------------------------
| 3     | 3       | 1     |  1    |
-----------------------------------
| 4     | 1       | 2     |  0    |
-----------------------------------
| 5     | 1       | 3     |  1    |
-----------------------------------
| 6     | 2       | 3     |  0    |
-----------------------------------

收件人

当根据类别发送电子邮件时,收件人会被记录下来,因此我们不会多次向他们发送电子邮件。

-----------------------------
| id    | emailid   | subid |
-----------------------------
| 1     | 1         | 1     |
-----------------------------
| 2     | 1         | 2     |
-----------------------------

我正在尝试编写一个查询来获取所有成员,以及他们针对一系列类别 ID 的相关许可、他们的偏好,并确保他们在收件人表中没有记录。

在伪查询中:

SELECT [all members, their licence info, and preference setting]
FROM [members table]
WHERE [member doesnt exist in the recipients table for a given emailID]

问题是我需要检查多个类别 ID,但只返回一个结果,并且仅当首选项设置为 1(或 null,或不存在)时才返回。

因此对于示例数据,假设我们正在搜索类别 ID 1、2 和 3(成员(member)必须至少拥有其中一个类别的许可证)并检查 emailID 1,唯一的结果应该是 member_id 3 (Tom) 的首选项 ID 为 6(因为它设置为 1)和许可证 ID 为 3(因为它有效并且首选项 ID 6 与其对应并且它被设置为 1)。第二个结果应该是 member_id 5 (Will),因为他有 catids 1 和 2 的许可,他没有收到 ID 为 1 的电子邮件,也没有特定的偏好设置。

原因是:成员 1 和 2 在 emailID 1 的收件人表中,成员 2 的许可证也已过期,成员 4 的许可证已过期并且成员 5 的首选项设置为 0。

我写的不太正确的查询是:

SELECT 
       members.member_id,
       members.name,
       licence.catid as licencedToCat,
       categories.cat_name as categoryName,
       licence.valid_from as licenceStart,
       licence.valid_to as licenceEnd,
       preferences.pref
FROM (`members`)
JOIN `licence` ON `licence`.`subid`=`members`.`member_id`
JOIN `preferences` ON `preferences`.`subid`=`members`.`member_id`
JOIN `categories` ON `categories`.`cat_id`=`licence`.`catid`
WHERE `licence`.`catid` IN (1,2,3)
   AND `start_date` <= '2014-12-16'
   AND `end_date` >= '2014-12-16'
   AND (pref='1' OR pref IS NULL)
   AND `members`.`member_id` NOT IN (SELECT subid FROM `recipients` WHERE `recipients`.`emailid`='1')
GROUP BY `licence`.`subid`

问题是查询返回的结果表明用户的首选项设置为 1,而实际上他们甚至没有该类别的记录集。

所需的输出是任何成员以及他们对该类别拥有的许可证,但前提是他们对该类别的偏好是 1/null/不存在,并且前提是他们没有出现在收件人表中对于给定的电子邮件 ID。

所以,如果一个成员有 2 个许可证

我很感激这是一篇很长的文章,如果你还在这里,谢谢!关于如何调整我的查询以解决此问题的任何想法?

最佳答案

我认为您的部分问题在于您使用的是所有内部联接。正如您所说,用户可能没有偏好,因此您的查询中可能不会返回一行。话虽如此,您似乎想要内部加入大多数表,因为看起来您只想要拥有许可证的成员,但您想要查看所有许可证,而不管该用户是否有偏好。所以,我将首选项设置为外部连接表:

SELECT m.*, l.catid AS licenseCat, c.name AS categoryName, 
  l.valid_from AS licenseStart, l.valid_to AS licenseEnd, p.pref AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid;

一旦我这样做了,我就写了一个子查询来提取收件人表中所有成员的 member_id 和指定的电子邮件:

SELECT subid
FROM recipients
WHERE emailid = 1;

现在您可以将其插入到您的原始查询中,并添加您的其他要求:

SELECT m.*, l.catid AS licenseCat, c.name AS categoryName, 
  l.valid_from AS licenseStart, l.valid_to AS licenseEnd, IFNULL(p.pref, 0) AS preference
FROM members m
JOIN licenses l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.catid = c.cat_id AND p.subid = l.subid
WHERE c.cat_id IN (1, 2, 3) AND
  l.valid_from <= '2014-12-06' AND l.valid_to >= '2014-12-06' AND
  m.member_id NOT IN (SELECT subid FROM recipients WHERE emailid = 1)
  AND (p.pref = 1 OR p.pref IS NULL);

您在问题中说过这应该返回 member_id 3(即 Tom),但这与您的结果不符,因为成员 5 没有偏好,所以我们应该假设他们想要电子邮件,对吧?我也不确定如何为您分组。如果一个成员有多个订阅,你想保留哪一个?

我构建了一个 SQL Fiddle并测试了我所拥有的,它真的很接近。我希望这至少可以将您推向正确的方向,我会根据需要编辑答案。

编辑

以下会给你想要的,但并不总是推荐。如果您真的不关心订阅日期(只要它符合 where 子句中的条件)并且您真的不关心订阅的类别用户,只需添加 GROUP BY m.member_id 即可为每个成员获取一行。

关于MySQL从具有多个记录的多个表中选择最高连接值的单个记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27510406/

相关文章:

mysql - 如何根据其他表中的列检索行

SQL 'CASE WHEN x' 与 'CASE x WHEN' 大于条件?

mysql - 有没有办法限制表中返回唯一值的次数?

mysql - 按值对行进行分组,但仅显示组结果中的最新行

mysql - 通过未执行的 ruby​​_block 设置 Chef 变量

php - 根据标题将csv数据导入数据库

php - 调用未定义的方法 Illuminate\Database\Query\Builder::links()

PHP 数据库编程

c# - 方法更新C#连接错误

sql - 如何使用 "max(count(*))"创建一个好的请求?