2

这是一个有点冗长的问题,对此我深表歉意。我的 SQL 技能严重缺乏(我想尽快纠正)。结果,我真的无法理解如何解决我遇到的这个问题。

基本上,我们的项目是将用户通知存储在一个表中notifications。表结构如下所示:

+-----------+-----------+----------------+------------+
|  user_id  |  subject  |     action     |     date   |
+-----------+-----------+----------------+------------+
|     1     |     2     | started_follow | 1371034287 |
|     1     |     2     | stopped_follow | 1371034287 |
|     2     |     5     |   added_item   | 1371034287 |
+-----------+-----------+----------------+------------+

user_id始终包含执行该操作的用户的 ID,并且date显然是注册通知的日期。棘手的部分是subject对另一个表的 ID 的引用,并且该表在很大程度上取决于action列的值。

所以例如,在样本数据的前两条记录中,subject是对表中的一个ID的引用users(即被关注的用户,然后被取消关注的用户)。在第三条记录中,subject是对items表中 ID 的引用。

我们还需要执行几个JOIN语句,具体取决于action. 因此,如果是added_item例如,我们需要JOIN几个其他表(以检查设置和其他要求)。

我在代码中遇到了一个遗留函数,它本质上检查表中自指定日期以来给定用户的通知数量。之前的开发者只是简单的使用了一系列的查询,然后返回了几个语句的总数SELECT COUNT(*)如下(请注意,这都是在PHPUser类里面):

// Get the number of notifications since the specified time (or of all time):
public function countNotifications($since = '')
{
    $sinceString = ($since == '') ? '' : "AND `date` > '$since'";

    // Notifications when someone follows $this:
    $started_following = $this->_database->query("SELECT COUNT(*) AS `count`
                                                  FROM `notifications`
                                                  WHERE `action` = 'started_following'
                                                    AND `subject` = '{$this->id}'
                                                    $sinceString
                                                    AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when someone stops following $this:
    $stopped_following = $this->_database->query("SELECT COUNT(*) AS `count`
                                                  FROM `notifications`
                                                  WHERE `action` = 'stopped_following'
                                                    AND `subject` = '{$this->id}'
                                                    $sinceString
                                                    AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when someone sends $this a message:
    $sent_message = $this->_database->query("SELECT COUNT(*) AS `count`
                                             FROM `notifications`
                                             WHERE `action` = 'sent_message'
                                               AND `subject` = '{$this->id}'
                                               $sinceString
                                               AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when someone favorites $this' items:
    $favorited_item = $this->_database->query("SELECT COUNT(*) AS `count`
                                               FROM `notifications`
                                               INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
                                               INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
                                               WHERE `notifications`.`action` = 'favorited_item'
                                                 AND `categories`.`owner` = '{$this->id}'
                                                 $sinceString
                                                 AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when someone adds a comment to $this' items:
    $comments = $this->_database->query("SELECT COUNT(*) AS `count`
                                         FROM `notifications`
                                         INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
                                         INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
                                         WHERE `notifications`.`action` = 'added_comment'
                                           AND `categories`.`owner` = '{$this->id}'
                                           $sinceString
                                           AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when a follower of $this adds a new item:
    $new_items = $this->_database->query("SELECT COUNT(*) AS `total`
                                         FROM `notifications`
                                         INNER JOIN `categories` ON `notifications`.`subject` = `categories`.`id`
                                         INNER JOIN (SELECT `followee` FROM `user_followers` WHERE `follower` = '{$this->id}') `followers`
                                         WHERE `notifications`.`action` = 'added_item'
                                           AND `followee` = `user_id`
                                           $sinceString
                                           AND `user_id` !=  '{$this->id}'")->fetchObject();

    // Notifications when a follower of $this adds a new collection:
    $new_collections = $this->_database->query("SELECT COUNT(*) AS `total`
                                                FROM `notifications`
                                                INNER JOIN `categories` ON `notifications`.`subject` = `categories`.`id`
                                                INNER JOIN (SELECT `followee` FROM `user_followers` WHERE `follower` = '{$this->id}') `followers`
                                                WHERE `notifications`.`action` = 'added-collection'
                                                   AND `followee` = `user_id`
                                                   $sinceString
                                                   AND `user_id` !=  '{$this->id}'")->fetchObject();

    // Notifications when a follower of $this adds a new category:
    $new_categories = $this->_database->query("SELECT COUNT(*) AS `total`
                                               FROM `notifications`
                                               INNER JOIN  `categories` ON `notifications`.`subject` =  `categories`.`id`
                                               INNER JOIN (SELECT `followee` FROM `user_followers` WHERE `follower` = '{$this->id}') `followers`
                                               WHERE `notifications`.`action` =  'added-category'
                                                 AND `followee` = `user_id`
                                                 $sinceString
                                                 AND `user_id` !=  '{$this->id}'")->fetchObject();

    // Selling Notifications:
    // Notifications when someone makes an offer for an item $this is selling:
    $offers = $this->_database->query("SELECT COUNT(*) AS `count`
                                       FROM `notifications`
                                       INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
                                       INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
                                       WHERE `notifications`.`action` = 'made_offer'
                                         AND `categories`.`owner` = '{$this->id}'
                                         $sinceString
                                         AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when someone purchases an item $this is selling:
    $purchases = $this->_database->query("SELECT COUNT(*) AS `count`
                                          FROM `notifications`
                                          INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
                                          INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
                                          INNER JOIN (SELECT COUNT(*) AS `count`, `item_id`
                                                      FROM `user_favorite_items`
                                                      WHERE `user_id` = '{$this->id}') `following` ON `items`.`id` = `following`.`item_id`
                                          WHERE `notifications`.`action` = 'bought_item'
                                            AND `following`.`count` = 1
                                            AND `categories`.`owner` = '{$this->id}'
                                            $sinceString
                                            AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Notifications when an item that $this favorited is listed for sale:
    $item_sales = $this->_database->query("SELECT COUNT(*) AS `count`
                                           FROM `notifications`
                                           INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
                                           INNER JOIN (SELECT COUNT(*) AS `count`, `item_id`
                                                       FROM `user_favorite_items`
                                                       WHERE `user_id` = '{$this->id}'
                                                    ) `following` ON `items`.`id` = `following`.`item_id`
                                           WHERE `notifications`.`action` = 'selling_item'
                                             AND `following`.`count` = 1
                                             $sinceString
                                             AND `notifications`.`user_id` != '{$this->id}'")->fetchObject();

    // Return the counts:
    return ($started_following->count + 
            $stopped_following->count +
            $sent_message->count +
            $favorited_item->count +
            $comments->count +
            $new_items->count +
            $new_collections->count +
            $new_categories->count +
            $offers->count +
            $purchases->count +
            $item_sales->count);
}

虽然这很好地完成了这项工作,但它使得获取指定日期的所有记录或与特定用户 ID 相关的所有记录变得极其困难。

我想我的问题真的是结合提供的众多 SQL 语句的最佳方法是什么?我已经尝试过LEFT JOIN,但正如您所见,我们需要将表连接到不同的列,具体取决于 的值notificationsaction. 虽然我可以使用表别名来做到这一点,但它确实往往会返回大量冗余数据。

本质上,我想结合COUNT(*)上面给出的查询,以便我们可以简单地返回notifications.*给定用户 ID 和/或时间段的所有查询。

如果可能的话,我也想避免使用UNION(出于显而易见的原因)。

很抱歉这个冗长的问题,但我试图让一切尽可能清楚。在有人问之前,我无法更改数据结构或数据库架构,因为这是针对现有站点的。

我整理了一个SQLFiddle以使事情更加清晰。

4

1 回答 1

1

这些查询的复杂性和它们的作用各不相同。

前 3 个可以组合成一个查询,返回(最多)3 行:-

SELECT action, COUNT(*) AS `count`
  FROM `notifications`
  WHERE `action` IN ( 'started_following', 'stopped_following', 'sent_message')
    AND `subject` = '{$this->id}'
    $sinceString
    AND `notifications`.`user_id` != '{$this->id}'
GROUP BY action

要强制它返回 3 行,您可以执行以下操作:-

SELECT WantedActions.WantedAction, COUNT(*) AS `count`
  FROM (SELECT 'started_following' AS WantedAction UNION SELECT 'stopped_following' UNION SELECT 'sent_message') AS WantedActions
  LEFT OUTER JOIN `notifications`
  ON WantedActions.WantedAction = notifications.action
  WHERE `subject` = '{$this->id}'
    $sinceString
    AND `notifications`.`user_id` != '{$this->id}'
GROUP BY WantedActions.WantedAction

您可能可以为他人做类似的事情

编辑

SELECT started_following, stopped_following, sent_message, favorited_item, comments, new_items, new_collections, new_categories, offers, purchases, item_sales
FROM (SELECT SUM(IF(`action` = 'started_following', 1, 0) AS started_following, 
                    SUM(IF(`action` = 'stopped_following', 1, 0) AS stopped_following, 
                    SUM(IF(`action` = 'sent_message', 1, 0) AS sent_message
            FROM `notifications`
            WHERE `subject` = '{$this->id}'
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}') Sub1
CROSS JOIN (SELECT SUM(IF(`action` = 'favorited_item', 1, 0) AS favorited_item, 
                    SUM(IF(`action` = 'added_comment', 1, 0) AS comments, 
                    SUM(IF(`action` = 'made_offer', 1, 0) AS offers
            FROM `notifications`
            INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
            INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
            WHERE `categories`.`owner` = '{$this->id}'
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}') Sub2
CROSS JOIN (SELECT SUM(IF(`action` = 'added_item', 1, 0) AS new_items, 
                    SUM(IF(`action` = 'added-collection', 1, 0) AS new_collections, 
                    SUM(IF(`action` = 'added-category', 1, 0) AS new_categories
            FROM `notifications`
            INNER JOIN `categories` ON `notifications`.`subject` = `categories`.`id`
            INNER JOIN (SELECT `followee` FROM `user_followers` WHERE `follower` = '{$this->id}') `followers`
            WHERE `followee` = `user_id`
            $sinceString
            AND `user_id` !=  '{$this->id}') Sub3
CROSS JOIN (SELECT COUNT(*) AS purchases
          FROM `notifications`
          INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
          INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
          INNER JOIN (SELECT COUNT(*) AS `count`, `item_id`
                      FROM `user_favorite_items`
                      WHERE `user_id` = '{$this->id}'
                    ) `following` ON `items`.`id` = `following`.`item_id`
          WHERE `notifications`.`action` = 'bought_item'
            AND `following`.`count` = 1
            AND `categories`.`owner` = '{$this->id}'
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}') Sub4
CROSS JOIN (SELECT COUNT(*) AS item_sales
           FROM `notifications`
           INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
           INNER JOIN (SELECT COUNT(*) AS `count`, `item_id`
                       FROM `user_favorite_items`
                       WHERE `user_id` = '{$this->id}'
                    ) `following` ON `items`.`id` = `following`.`item_id`
           WHERE `notifications`.`action` = 'selling_item'
             AND `following`.`count` = 1
             $sinceString
             AND `notifications`.`user_id` != '{$this->id}') Sub5

编辑 - 使用联合

SELECT action, COUNT(*)  AS action_count
            FROM `notifications`
            WHERE `subject` = '{$this->id}'
            AND action IN ('started_following', 'stopped_following', 'sent_message')
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}'
            GROUP BY action
UNION
SELECT action, COUNT(*)  AS action_count
            FROM `notifications`
            INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
            INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
            WHERE `categories`.`owner` = '{$this->id}'
            AND action IN ('favorited_item', 'added_comment', 'made_offer')
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}'
            GROUP BY action
UNION
SELECT action, COUNT(*)  AS action_count
            FROM `notifications`
            INNER JOIN `categories` ON `notifications`.`subject` = `categories`.`id`
            INNER JOIN (SELECT `followee` FROM `user_followers` WHERE `follower` = '{$this->id}') `followers`
            WHERE `followee` = `user_id`
            AND action IN ('added_item', 'added-collection', 'added-category')
            $sinceString
            AND `user_id` !=  '{$this->id}'
            GROUP BY action
UNION
SELECT 'purchases' AS action, COUNT(*)  AS action_count
          FROM `notifications`
          INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
          INNER JOIN `categories` ON `items`.`category` = `categories`.`id`
           INNER JOIN (SELECT user_id, COUNT(*) AS `count`, `item_id`
                       FROM `user_favorite_items`
                       GROUP BY `user_id`, item_id
                    ) `following` ON `items`.`id` = `following`.`item_id` AND `notifications`.`user_id` = `following`.`user_id`
          WHERE `notifications`.`action` = 'bought_item'
            AND `following`.`count` = 1
            AND `categories`.`owner` = '{$this->id}'
            $sinceString
            AND `notifications`.`user_id` != '{$this->id}'
UNION
SELECT 'item_sales' AS action, COUNT(*) AS action_count
           FROM `notifications`
           INNER JOIN `items` ON `notifications`.`subject` = `items`.`id`
           INNER JOIN (SELECT user_id, COUNT(*) AS `count`, `item_id`
                       FROM `user_favorite_items`
                       GROUP BY `user_id`, item_id
                    ) `following` ON `items`.`id` = `following`.`item_id` AND `notifications`.`user_id` = `following`.`user_id`
           WHERE `notifications`.`action` = 'selling_item'
             AND `following`.`count` = 1
             $sinceString
             AND `notifications`.`user_id` != '{$this->id}'
于 2013-07-01T16:53:28.650 回答