У меня есть таблица, полная статей из разных источников. Некоторые источники могут иметь одно и то же место (в моем примере разные новостные каналы BBC будут разными источниками, но все они происходят из Би-би-си). Каждый элемент имеет уникальный идентификатор, который может использоваться для идентификации его среди других из того же места. Это означает, что элементы, относящиеся к одной и той же новостной ленте на сайте, но опубликованные в разных каналах, будут иметь один и тот же "уникальный идентификатор", но это не обязательно глобально уникально.
Проблема заключается в том, что я хочу исключить дубликаты во время отображения, так что (в зависимости от того, какие каналы вы видите) вы получаете не более одной версии каждой истории, хотя два или три из ваших каналов могут содержать ссылки на он.
У меня есть таблица sources
с информацией об каждом источнике и location_id
и location_precedence
. Затем у меня есть таблица items
, содержащая каждый элемент, unique_id
, source_id
и content
. Элементы с теми же unique_id
и source location_id
должны появляться не более одного раза с наивысшим уровнем выигрыша location_precedence
.
Я бы подумал, что что-то вроде:
SELECT `sources`.`name` AS `source`,
`items`.`content`,
`items`.`published`
FROM `items` INNER JOIN `sources`
ON `items`.`source_id` = `sources`.`id` AND `sources`.`active` = 1
GROUP BY `items`.`unique_id`, `sources`.`location_id`
ORDER BY `sources`.`location_priority` DESC
сделал бы трюк, но это, похоже, игнорирует поле приоритета местоположения. Что я пропустил?
Пример данных:
CREATE TABLE IF NOT EXISTS `sources` (
`id` int(10) unsigned NOT NULL auto_increment,
`location_id` int(10) unsigned NOT NULL,
`location_priority` int(11) NOT NULL,
`active` tinyint(1) unsigned NOT NULL default '1',
`name` varchar(150) NOT NULL,
`url` text NOT NULL,
PRIMARY KEY (`id`),
KEY `active` (`active`)
);
INSERT INTO `sources` (`id`, `location_id`, `location_priority`, `active`, `name`, `url`) VALUES
(1, 1, 25, 1, 'BBC News Front Page', 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/front_page/rss.xml'),
(2, 1, 10, 1, 'BBC News England', 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/england/rss.xml'),
(3, 1, 15, 1, 'BBC Technology News', 'http://newsrss.bbc.co.uk/rss/newsonline_uk_edition/technology/rss.xml'),
(4, 2, 0, 1, 'Slashdot', 'http://rss.slashdot.org/Slashdot/slashdot'),
(5, 3, 0, 1, 'The Daily WTF', 'http://syndication.thedailywtf.com/TheDailyWtf');
CREATE TABLE IF NOT EXISTS `items` (
`id` bigint(20) unsigned NOT NULL auto_increment,
`source_id` int(10) unsigned NOT NULL,
`published` datetime NOT NULL,
`content` text NOT NULL,
`unique_id` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_id` (`unique_id`,`source_id`),
KEY `published` (`published`),
KEY `source_id` (`source_id`)
);
INSERT INTO `items` (`id`, `source_id`, `published`, `content`, `unique_id`) VALUES
(1, 1, '2009-12-01 16:25:53', 'Story about Subject One', 'abc'),
(2, 2, '2009-12-01 16:21:31', 'Subject One in story', 'abc'),
(3, 3, '2009-12-01 16:17:20', 'Techy goodness', 'def'),
(4, 2, '2009-12-01 16:05:57', 'Further updates on Foo case', 'ghi'),
(5, 3, '2009-12-01 15:53:39', 'Foo, Bar and Quux in court battle', 'ghi'),
(6, 2, '2009-12-01 15:52:02', 'Anti-Fubar protests cause disquiet', 'mno'),
(7, 4, '2009-12-01 15:39:00', 'Microsoft Bleh meets lukewarm reception', 'pqr'),
(8, 5, '2009-12-01 15:13:45', 'Ever thought about doing it in VB?', 'pqr'),
(9, 1, '2009-12-01 15:13:15', 'Celebrity has 'new friend'', 'pqr'),
(10, 1, '2009-12-01 15:09:57', 'Microsoft launches Bleh worldwide', 'stu'),
(11, 2, '2009-12-01 14:57:22', 'Microsoft launches Bleh in UK', 'stu'),
(12, 3, '2009-12-01 14:57:22', 'Microsoft launches Bleh', 'stu'),
(13, 3, '2009-12-01 14:42:15', 'Tech round-up', 'vwx'),
(14, 2, '2009-12-01 14:36:26', 'Estates 'old news' say government', 'yza'),
(15, 1, '2009-12-01 14:15:21', 'Iranian doctor 'was poisoned'', 'bcd'),
(16, 4, '2009-12-01 14:14:02', 'Apple fans overjoyed by iBlah', 'axf');
Ожидаемый контент после запроса:
Я пробовал вариант решения Andomar с некоторым успехом:
SELECT s.`name` AS `source`,
i.`content`,
i.`published`
FROM `items` i
INNER JOIN `sources` s
ON i.`source_id` = s.`id`
AND s.`active` = 1
INNER JOIN (
SELECT `unique_id`, `source_id`, MAX(`location_priority`) AS `prio`
FROM `items` i
INNER JOIN `sources` s ON s.`id` = i.`source_id` AND s.`active` = 1
GROUP BY `location_id`, `unique_id`
) `filter`
ON i.`unique_id` = `filter`.`unique_id`
AND s.`location_priority` = `filter`.`prio`
ORDER BY i.`published` DESC
LIMIT 50
С AND s.location_priority = filter.prio
все работает так, как я хочу. Поскольку элемент может поступать из нескольких источников с одинаковым приоритетом, элементы могут быть повторены. В этом случае дополнительный GROUP BY i.unique_id
для внешнего запроса выполняет задание, и я полагаю, что не имеет значения, какой источник "выигрывает", если приоритеты равны.
Я попробовал вместо AND i.source_id = filter.source_id
, который почти работает (т.е. удаляет лишний GROUP BY
), но не дает результатов от правильных источников. В приведенном выше примере он дает мне "Дальнейшие обновления в случае Foo" (источник "BBC News England" ), а не "Foo, Bar and Quux in court battle" (источник "BBC Technology News". Глядя на результаты внутреннего запроса, я получаю:
unique_id: 'ghi'
source_id: 2
prio: 15
Обратите внимание, что исходный идентификатор неверен (ожидается: 3).
Order by
просто заказывает строки, он не выбирает среди них.
Один из способов отфильтровать строки с нижним location_priority
- использовать inner join
как фильтр:
SELECT s.name, i.content, i.published
FROM items i
INNER JOIN sources s
ON i.source_id = s.id
AND s.active = 1
INNER JOIN (
SELECT unique_id, max(location_priority) as prio
FROM items i
INNER JOIN sources s ON s.id = i.source_id AND s.active = 1
GROUP BY unique_id) filter
ON i.unique_id = filter.unique_id
AND s.location_priority = filter.prio;
Альтернативой является предложение where ... in <subquery>
, например:
SELECT s.name, i.content, i.published
FROM items i
INNER JOIN sources s
ON i.source_id = s.id
AND s.active = 1
WHERE (i.unique_id, s.location_priority) IN (
SELECT unique_id, max(location_priority)
FROM items i
INNER JOIN sources s ON s.id = i.source_id AND s.active = 1
GROUP BY unique_id
);
Эта проблема также известна как "Выбор записей, имеющих максимальный уровень для всей группы". Quassnoi написал хорошую статью на нем.
EDIT: одним из способов разрыва связей с несколькими источниками с одинаковым приоритетом является предложение WHERE
с подзапросом. Этот пример разбивает связи на i.id DESC
:
SELECT s.name, i.unique_id, i.content, i.published
FROM (
SELECT unique_id, min(location_priority) as prio
FROM items i
INNER JOIN sources s ON s.id = i.source_id AND s.active = 1
GROUP BY unique_id
) filter
JOIN items i
JOIN sources s
ON s.id = i.source_id
AND s.active = 1
WHERE i.id =
(
SELECT i.id
FROM items i
JOIN sources s
ON s.id = i.source_id
AND s.active = 1
WHERE i.unique_id = filter.unique_id
AND s.location_priority = filter.prio
ORDER BY i.id DESC
LIMIT 1
)
В Quassnoi также есть статья о выбор записей, содержащих групповой максимум (разрешение связей):)
сделать самостоятельное соединение с производной таблицей, например
select max(location_priority) from table where ...
Что я пропустил?
ORDER BY
происходит после того, как GROUP BY
уже уменьшил каждую группу до одной строки. Пол дает одно разрешение.
Что касается проблемы с запросом:
SELECT `unique_id`, `source_id`, MAX(`location_priority`) AS `prio`
FROM `items` i
INNER JOIN `sources` s ON s.`id` = i.`source_id` AND s.`active` = 1
GROUP BY `location_id`, `unique_id`
source_id
не является ни агрегатом, ни сгруппированным. В результате, какое значение вы получаете, является неопределенным.
any()
:)