Aggregieren von SQL Reihen mit Vorrang
-
13-09-2019 - |
Frage
Ich habe eine Tabelle, die voll von Artikeln aus verschiedenen Quellen. Einige der Quellen könnten die gleiche Position haben (in meinem Beispiel würde verschiedener BBC-Nachrichten-Feeds verschiedene Quellen, aber sie kommen alle aus der BBC). Jedes Element hat eine „eindeutige“ ID, die verwendet werden kann es unter anderem von der gleichen Stelle zu identifizieren. Dies bedeutet, dass Elemente auf einer Website auf die gleiche Nachrichtengeschichte beziehen, aber unter verschiedenen Feeds veröffentlichen die gleiche „unique ID“ haben, aber das ist nicht unbedingt weltweit einzigartig.
Das Problem ist, dass ich will bei Anzeigezeit Duplikate beseitigen, so dass (je nachdem, welche Feeds Sie sehen) erhalten Sie nur höchstens eine Version jeder Geschichte, auch wenn zwei oder drei Ihrer Feeds können Links enthalten es.
Ich habe eine sources
Tabelle mit Informationen über jede Quelle und location_id
und location_precedence
Felder aus. Ich habe dann eine items
Tabelle, die jedes Element enthält, dessen unique_id
, source_id
und content
. Elemente mit dem gleichen unique_id
und Quelle location_id
höchstens einmal vorkommen sollen, mit der höchsten Quelle location_precedence
zu gewinnen.
Ich habe gedacht, dass so etwas wie:
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
würde den Trick tun, aber das scheint die Lage Prioritätsfeld zu ignorieren. Was habe ich verpasst?
Beispieldaten:
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');
Erwartete Inhalt nach Abfrage:
- Die Geschichte über Thema One
- Techy Güte
- Foo, Bar und Quux vor Gericht Schlacht
- Anti-Fubar Proteste verursachen Unruhe
- Microsoft Bleh trifft lauwarmen Empfang
- jemals daran gedacht, es in VB tun?
- Celebrity hat 'neuen Freund'
- Microsoft startet Bleh weltweit
- Tech Round-up
- Estates 'alte Nachrichten' sagen Regierung
- iranischer Arzt vergiftet wurde "
- Apple-Fans überglücklich durch iBlah
Ich habe versucht, eine Variante der Lösung von Andomar mit einigem Erfolg:
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
Mit AND s.location_priority = filter.prio
Dingen fast arbeiten, wie ich will. Da ein Element aus mehreren Quellen mit der gleichen Priorität kommen kann, können Einzelteile wiederholt werden. In diesem Fall wird ein zusätzlicher GROUP BY i.unique_id
auf der äußeren Abfrage macht den Job, und ich nehme an, es ist egal, welche Quelle „gewinnt“, wenn die Prioritäten gleich sind.
Ich hatte mit AND i.source_id = filter.source_id
versucht stattdessen, die fast arbeitet (das heißt entfällt die zusätzliche GROUP BY
), aber keine Ergebnisse aus den richtigen Quellen geben. In dem obigen Beispiel, es gibt mir „Weitere Updates auf Foo Fall“ (Quelle „BBC News England“) statt „Foo, Bar und Quux vor Gericht Schlacht“ (Quelle „BBC Technology News“. Auf die Ergebnisse der inneren Blick Abfrage, die ich erhalten:
unique_id: 'ghi'
source_id: 2
prio: 15
Beachten Sie, dass die Quell-ID nicht korrekt ist (erwartet: 3).
Lösung
Order by
lediglich die Zeilen anordnet, ist es nicht unter ihnen auswählen.
Eine der Möglichkeiten, um herauszufiltern Reihen mit einem niedrigen location_priority
ist einen inner join
als Filter zu verwenden:
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;
Eine Alternative ist ein where ... in <subquery>
Klausel, zum Beispiel:
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
);
Dieses Problem ist auch bekannt als „Auswählen von Datensatz einen konzernweite maximale Halt.“ Quassnoi hat ein schöner Artikel auf sie.
EDIT: Eine Möglichkeit, Verbindungen mit mehreren Quellen mit der gleichen Priorität zu brechen ist eine WHERE
Klausel mit einer Unterabfrage. Dieses Beispiel bricht Beziehungen auf 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 hat auch einen Artikel über Auswählen von Datensätzen Haltegruppenweise Maximum (Lösung Bindungen) :)
Andere Tipps
trete ein Selbst zu einer abgeleiteten Tabelle wie
select max(location_priority) from table where ...
Was habe ich verpasst?
Die ORDER BY
passieren, nachdem die bereits GROUP BY
jede Gruppe eine einzelne Reihe reduziert hat. Paul gibt eine Auflösung.
Was das Problem mit der Abfrage:
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
ist weder ein Aggregat noch gruppiert. Als Ergebnis, das Sie erhalten Wert unbestimmt ist.