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

时间:2014-12-16 17:26:21

标签: mysql sql greatest-n-per-group

我有以下表格:

成员信息

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

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

类别

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

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

许可证

存储成员拥有的许可证。一个成员可以拥有许多许可证。许可证可以有使用寿命并且将过期。许可证到期后,该成员将无法再查看该类别。

------------------------------------------------------
| 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 |
------------------------------------------------------

偏好

首选项表存储成员是否希望接收与类别相关的电子邮件。会员可以设置' 1'希望收到'或者' 0' 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]

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

因此,对于示例数据,鉴于我们正在搜索categoryIDs 1,2和3(成员必须拥有至少一个这些类别的许可证)并检查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。 / p>

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

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 /不存在且仅在他们不出现时在给定的emailID的收件人表中。

因此,如果会员有2个许可

我很欣赏这是一个很长的阅读,所以,谢谢,如果你还在这里!关于如何调整我的查询以解决这个问题的任何想法?

2 个答案:

答案 0 :(得分:1)

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

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(这是汤姆)但是这与你的结果不符,因为成员5没有偏好,所以我们应该假设他们想要一封电子邮件吗?我也不确定如何为你分组。如果某个成员有多个订阅,您想要保留哪个订阅?

我构建了一个SQL Fiddle并测试了我拥有的内容并且非常接近。我希望这至少能帮助你朝着正确的方向前进,我会根据需要编辑答案。

修改

以下内容将为您提供所需内容,但并不总是建议您这样做。如果您确实不关心订阅日期(只要它符合where子句中的条件)并且您真的不关心类别用户,只需添加GROUP BY m.member_id即可为每个成员获取一行。

答案 1 :(得分:0)

因此,最终的查询就像这些,测试和工作:

SELECT 
       m.member_id,
       m.email,
       l.catid as licencedToCat,
       c.cat_name as categoryName,
       l.valid_from as licenceStart,
       l.valid_to as licenceEnd,
       COALESCE(p.pref, 1) pref
FROM members m
JOIN licence l ON l.subid = m.member_id
JOIN categories c ON c.cat_id = l.catid
LEFT JOIN preferences p ON p.subid= m.member_id AND p.cat_id = l.cat_id
LEFT JOIN recipients r ON r.subid = m.member_id
WHERE l.catid IN (1,2,3)
   AND start_date <= '2014-12-16' AND end_date >= '2014-12-16'
   AND COALESCE(p.pref, 1) = 1
   AND COALESCE(r.emailid, 0) = 0-- assuming with emailid = 0 it remains valid as recipient
GROUP BY m.member_id

但是,出于查询的目的,DISTINCT m.*子句中只有SELECT会丢弃GROUP BY