当一个表为空时连接表

时间:2016-08-06 20:56:31

标签: mysql sql join

我需要加入三个表:用户通知 UserNotification 。后者只是User和Notificaion之间的交叉引用表,其中列UserID(fk to User)和LastReadNotificationID(fk to Notification)。表UserNotification应包含对Web应用程序中用户通知功能中上次读取通知的引用。因此,如果我们在ID为1和2的Notification中有两条记录,而在UserNotification中有一条记录,其中fk为Notification = 1,则表示用户没有读取我想在下次登录时显示的最后创建的通知。 / p>

现在,我需要在登录时选择用户表中的所有列,并在结果集中添加另一列(通知)。 通知应为布尔值,如果符合以下情况,则应为false:

  • 通知为空
  • 或Notification包含一个记录,例如ID = 10 AND UserNotification确实具有相应的外键。
如果符合以下情况,

通知应为true:

  • 通知包含记录 AND UserNotification为空。
  • 或通知包含记录,例如ID = 10 但是 UserNotification NOT 具有相应的外键。

问题是我无法编写满足上述所有要求的查询。我目前的查询有效,除非通知为空(因此是UserNotification)。在这种情况下,我的查询返回Notify = true;

如果已经尝试了许多不同的方法来解决(左连接,右连接,if,case when,ifnull等)这个但是我被卡住了。请帮忙。

我现在使用的查询:

SELECT ID, FirstName, LastName, Email, Password, Roles, LastLoginTime, LoginCount, Active, 
(SELECT IFNULL((SELECT 0 FROM UserNotification UN, User U 
WHERE UN.UserId = U.ID AND U.Email = :email 
AND UN.LastReadNotificationID <=> (SELECT MAX(ID) FROM Notification WHERE Display = 1)), 1)) AS Notify 
FROM User WHERE Email = :email;

3个表:

CREATE TABLE `User` (
  `ID` bigint(20) NOT NULL AUTO_INCREMENT,
  `FirstName` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
  `LastName` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
  `Email` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
  `Password` varchar(200) CHARACTER SET utf8 DEFAULT NULL,
  `Roles` varchar(200) CHARACTER SET utf8 DEFAULT NULL,
  `LastLoginTime` varchar(50) CHARACTER SET utf8 DEFAULT NULL,
  `LoginCount` int(11) DEFAULT NULL,
  `Active` bit(1) NOT NULL DEFAULT b'1',
  PRIMARY KEY (`ID`),
  UNIQUE KEY `Email` (`Email`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

CREATE TABLE `UserNotification` (
  `UserID` bigint(20) NOT NULL,
  `LastReadNotificationID` int(11) NOT NULL,
  UNIQUE KEY `UserID_UNIQUE` (`UserID`),
  KEY `fk_UserNotification_Notification_ID` (`LastReadNotificationID`),
  CONSTRAINT `fk_UserNotification_Notification_ID` FOREIGN KEY  (`LastReadNotificationID`) REFERENCES `Notification` (`ID`) ON DELETE CASCADE,
  CONSTRAINT `fk_UserNotification_User_ID` FOREIGN KEY (`UserID`) REFERENCES `User` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `Notification` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `Text` text NOT NULL,
  `Created` timestamp NOT NULL,
  `UserID` bigint(20) NOT NULL,
  `Display` bit(1) DEFAULT NULL,
  PRIMARY KEY (`ID`),
  KEY `fk_Notification_User_ID` (`UserID`),
  CONSTRAINT `fk_Notification_User_ID` FOREIGN KEY (`UserID`) REFERENCES `User` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

2 个答案:

答案 0 :(得分:0)

请扩展您想要的结果。您是否想要用户未读取的实际上次输入通知?或者你只是想知道你是否应该通知用户?

SELECT
    u.Id
    ,u.FirstName
    ,u.LastName
    ,u.Email
    ,u.Password
    ,u.Roles
    ,u.LastLoginTime
    ,u.LoginCount
    ,u.Active
    ,CASE WHEN un.UserId IS NULL THEN 1 Notify
FROM
    User u
    CROSS JOIN Notification n
    LEFT JOIN UserNotification un
    ON u.ID = un.UserId
    AND n.Id = un.LastReadNotificationId
WHERE
    U.Email = :email
    AND n.Display = 1
;

如果存在记录,此查询将为UsersNotifications以及join UserNotifications表提供每种可能的组合。它将指定通知用户尚未阅读的所有Notifications。因此所有Notifications都会重复用户。

然后,您可以限制通过使用案例陈述和分组来通知并指定用户未读取的最新通知。

SELECT
    u.Id
    ,u.FirstName
    ,u.LastName
    ,u.Email
    ,u.Password
    ,u.Roles
    ,u.LastLoginTime
    ,u.LoginCount
    ,u.Active
    ,CASE
       WHEN
          SUM(CASE
                WHEN un.UserId IS NULL THEN 1
                ELSE 0
             END) > 0 THEN 1
       ELSE 0
       END AS Notify
    ,MAX(CASE WHEN un.UserId IS NULL THEN n.ID END) AS NextNotification
FROM
    User u
    CROSS JOIN Notification n
    LEFT JOIN UserNotification un
    ON u.ID = un.UserId
    AND n.Id = un.LastReadNotificationId
WHERE
    U.Email = :email
    AND n.Display = 1
GROUP BY
    u.Id
    ,u.FirstName
    ,u.LastName
    ,u.Email
    ,u.Password
    ,u.Roles
    ,u.LastLoginTime
    ,u.LoginCount
    ,u.Active

如果用户已读取所有通知,则NextNotifcation将为NULL,因为MAX忽略空值且case语句未指定,否则该值将为null。

如果您只是想知道是否应该通知用户,请忽略/删除NextNotifcation列。

执行此操作的另一种方法与您的处理方式类似:

SELECT
    u.Id
    ,u.FirstName
    ,u.LastName
    ,u.Email
    ,u.Password
    ,u.Roles
    ,u.LastLoginTime
    ,u.LoginCount
    ,u.Active
    ,CASE
       WHEN EXISTS
          (SELECT
             *
          FROM
             Notifications n
             LEFT JOIN UserNotifications un
             ON n.Id = un.LastReadNotificationId
             AND un.UserId = u.Id
          WHERE
              n.Display = 1
          )
          THEN 1
       ELSE 0
       END AS Notify
FROM
    Users u
WHERE
    u.Email = :email
;

通常情况下,我不喜欢在列定义中添加子选择,但是为用户查看BIGINT的数据类型,使用EXISTS而不是交叉连接实际上可能是更好的性能。

答案 1 :(得分:0)

您尝试的问题可能归结为一个基本问题:您希望将三个表与左连接(A -> B -> C)链接在一起但您还希望能够推断是否还有其他行在表中不在连接逻辑和ID对之外。

BC都可以为空,这就是为什么用左连接来接近它的原因很自然。 (B&#34; s&#34;空虚&#34;当然是每个用户。)问题是B位于中间,即使{{1}也可以为空}} 不是。但是当C为空时,您无法根据加入的结果确定B的任何结论。

C

您可能更喜欢在交叉连接上使用子查询:

select
    u.ID as UserID, FirstName, LastName, Email, Password,
    Roles, LastLoginTime, LoginCount, Active,
    case
        when max_n.IsEmpty = 1 or un.LastReadNotification = max_n.ID then 0
        -- the following is equivalent and eliminates the IsEmpty flag entirely
        -- when coalesce(max_n.ID, 0) = coalesce(un.LastReadNotification, 0) then 1
        else 1 -- isn't it sufficient to just return 1 at this point?
        -- when un.LastReadNotification is null then 1 -- notification wasn't empty btw
        -- when un.LastReadNotification < agg_n.MaxID then 1 -- can't be greater, right?
    end as Notify
from
    User u
    left outer join UserNotification un
        on un.UserID = u.ID
    cross join (
        select
            case when max(ID) is not null then 0 else 1 end as IsEmpty,
            max(ID) as ID
        from Notification
    ) max_n

最终,您真正需要知道的是,是否存在ID大于每个用户最后看到的ID的通知。只抓取最高通知ID就足以让您全面做出决定。

以下是另一个想法:在初始值为0的用户表中使用select u.ID as UserID, FirstName, LastName, Email, Password, Roles, LastLoginTime, LoginCount, Active, case when coalesce((select max(ID) from Notification), 0) = coalesce(LastReadNotification, 0) else 1 end as Notify from User u left outer join UserNotification un on un.UserID = u.ID 以及在&#34中使用ID为0的虚拟行可能更容易吗? #34;通知表?基本上你根本不需要知道任何关于空虚的事情。如果你实现了0行,你最终会得到这样的结果:

LastReadNotification