在MySQL中组合多个查询

时间:2013-07-01 16:33:33

标签: mysql sql

这是一个冗长的问题,我为此道歉。我的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是对users表中的ID的引用(即被跟踪,然后取消关注的用户)。在第三条记录中,subject是对items表中ID的引用。

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

我在代码中遇到了遗留函数,它基本上检查了自指定日期以来给定用户表中存在多少通知。之前的开发人员只使用了一系列查询,然后返回了几个SELECT COUNT(*)语句的总数,如下所示(请注意,这都在PHP User类中):

// 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(*)个查询,以便我们只需返回给定用户ID和/或时间段的所有notifications.*

我也希望尽可能避免使用UNION(出于显而易见的原因)。

对于这个冗长的问题感到抱歉,但我已尽力使一切尽可能清楚。在有人要求之前,我无法更改数据结构或数据库架构,因为这是针对现有站点的。

我已经整理了SQLFiddle以使事情变得更加清晰。

1 个答案:

答案 0 :(得分: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}'