这是一个冗长的问题,我为此道歉。我的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
,但正如您所见,我们需要将表格加入不同的列,具体取决于notifications
。action
的值。虽然我可以使用表别名来执行此操作,但它确实会返回批次的冗余数据。
基本上,我想结合上面给出的COUNT(*)
个查询,以便我们只需返回给定用户ID和/或时间段的所有notifications.*
。
我也希望尽可能避免使用UNION
(出于显而易见的原因)。
对于这个冗长的问题感到抱歉,但我已尽力使一切尽可能清楚。在有人要求之前,我无法更改数据结构或数据库架构,因为这是针对现有站点的。
我已经整理了SQLFiddle以使事情变得更加清晰。
答案 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}'