SQL-запрос - выбор «последней обновленной» записи в группе, лучший дизайн БД? - PullRequest
1 голос
/ 16 мая 2010

Допустим, у меня есть база данных MySQL с 3 таблицами:

Таблица 1: Персоны, с 1 столбцом ID (int)
Таблица 2: Информационные бюллетени с идентификатором 1 столбца (int)
Таблица 3: Подписки, со столбцами Person_ID (int), Newsletter_ID (int), Subscribeed (bool), Обновлено (Datetime)

Subscription.Person_ID указывает на Персона, а Subscription.Newsletter_ID указывает на Информационный бюллетень. Таким образом, каждый человек может иметь 0 или более подписок на 0 или более журналов одновременно. В таблице «Подписки» также будет храниться вся история подписок каждого человека на каждую рассылку. Если у конкретной пары Person_ID-Newsletter_ID нет строки в таблице подписок, то это эквивалентно тому, что у этой пары статус подписки равен «false».

Вот пример набора данных

Persons
ID
1
2
3

Newsletters
ID
1
2
3

Subscriptions
Person_ID  Newsletter_ID  Subscribed  Updated
2                1           true     2010-05-01
3                1           true     2010-05-01
3                2           true     2010-05-10
3                1           false    2010-05-15

Таким образом, по состоянию на 2010-05-16 гг. Лицо 1 не имеет подписки, Лицо 2 имеет подписку на Новостную рассылку 1, а Лицо 3 имеет подписку на Новостную рассылку 2. Лицо 3 некоторое время подписывалось на Новостную рассылку 1, но не больше.

Я пытаюсь сделать 2 вида запросов.

  1. Запрос, который показывает все активные подписки на момент запроса (мы можем предположить, что обновленных никогда не будет в будущем - таким образом, это означает возвращение записи с последним «обновленным» значением для каждого Person_ID-Newsletter_ID пару, если подписка имеет значение true (если последняя запись для пары Person_ID-Newsletter_ID имеет статус подписки false, то я не хочу, чтобы эта запись возвращалась)).

  2. Запрос, который возвращает все активные подписки на конкретную рассылку - такая же квалификация, как в 1. в отношении записей со значением «false» в столбце «Подписка».

Я не использую SQL / базы данных достаточно часто, чтобы сказать, хорош ли этот дизайн, или если требуемые запросы SQL будут медленными для базы данных, скажем, с 1М записей в таблице подписок.

Я использовал инструмент построения визуальных запросов в Visual Studio 2010, но даже не могу получить запрос на возврат последней обновленной записи для каждой пары Person_ID-Newsletter_ID.

Можно ли придумать SQL-запросы, которые не включают использование подзапросов (предположительно, потому что они станут слишком медленными с большим набором данных)? Если нет, то было бы лучше иметь отдельную таблицу Subscription_History, и каждый раз, когда статус подписки для пары Person_ID-Newsletter-ID добавляется в подписки, любая существующая запись для этой пары перемещается в Subscription_History (таким образом, подписки). таблица содержит только последнее обновление статуса для любой пары Person_ID-Newsletter_ID)?

Я использую .net в Windows, так что будет проще (или то же самое, или сложнее) выполнять запросы такого типа с использованием Linq? Entity Framework?

Редактировать: вот что произойдет, если я использую этот запрос:

SELECT     Person_ID, Newsletter_ID, Allocation, Updated, MAX(Updated) AS Expr1
FROM         subscriptions
GROUP BY Person_ID, Newsletter_ID

Я получаю строки 2 и 4 из таблицы подписок, которые перепутаны (в строке 2 результатов, указанных ниже):

Person_ID Newsletter_ID Subscribed Updated     Expr1 
2         1             true       2010-05-01  2010-05-01 
3         1             true       2010-05-01  2010-05-15 
3         2             true       2010-05-10  2010-05-10

Спасибо!

Ответы [ 4 ]

2 голосов
/ 17 мая 2010

Я недавно столкнулся с чем-то похожим проблема .

Я не эксперт по SQL, поэтому я не могу дать много советов о том, какой дизайн лучше для этого. Но пока плюсы не появятся, возможно, это поможет:

SELECT s.Person_ID, s.Newsletter_ID  
FROM (
 SELECT MAX(ID) AS mid
 FROM Subscriptions
 GROUP BY 
  Person_ID,Newsletter_ID
) q
JOIN Subscriptions s
ON q.mid = s.ID
WHERE s.Subscribed = 1

Обратите внимание, что я добавил столбец идентификаторов в вашу таблицу подписок (я объясню почему через секунду).

Теперь давайте разберемся, как это работает (или как я думаю, в любом случае; я был бы рад исправить, если я ошибаюсь).

Сначала вы извлекаете все записи для данного лица / бюллетеня. Это то, что делает подзапрос (да, я знаю, что вы сказали, что предпочли бы не иметь подзапросов, но я не уверен, что вы можете сделать это без одного). Я группируюсь по person_id и newsletter_id. Это может вернуть более одной строки. Обратите внимание, что я выбираю MAX (ID). Если вы используете автоинкрементный идентификатор и можете предположить, что строка с наибольшим номером в столбце идентификатора является самой новой для группы (т. Е. Если вы не вставляете идентификаторы вручную), этот подзапрос получит идентификатор последнего строка для каждого человека / бюллетень.

Итак, вы можете объединить это с таблицей подписок: условие соединения состоит в том, что идентификатор строки подписок должен совпадать с идентификатором MAX, который вы получили из подзапроса. Здесь вы рассматриваете только самые последние записи для каждого бюллетеня / человека. Затем вы отключаете неактивные подписки, используя условие WHERE.

Если вы хотите ограничить результат определенным информационным бюллетенем (или данным человеком), добавьте это условие в предложение WHERE.

Индексы должны помочь ускорить выполнение этого запроса.

Надеюсь, это поможет.

Добавлена ​​

Если по какой-то причине вы не можете гарантировать, что MAX (Subscription.ID) будет соответствовать последней вставленной строке, вы, вероятно, можете сделать что-то вроде этого (я думаю, что следует той же логике, но немного более подробно и, вероятно, менее эффективно):

SELECT Person_ID, Newsletter_ID  
FROM (
 SELECT MAX(Updated) AS upd, Newsletter_ID AS nid, Person_ID AS pid 
 FROM Subscriptions
 GROUP BY 
  Person_ID,Newsletter_ID
) q
JOIN Subscriptions s
ON q.pid = s.Person_ID AND q.nid = s.Newsletter_ID and q.upd = s.Updated
WHERE Subscribed = 1

Новое редактирование

Если подумать, я добавил, что альтернатива (с MAX(Updated)) неверна. Вы не можете точно знать, что выбранные Newsletter_ID и Person_ID в подзапросе будут Newsletter_ID и Person_ID, соответствующими строке MAX (обновлено). Поскольку эти столбцы используются для условия соединения, этот запрос может дать ложные результаты.

2 голосов
/ 17 мая 2010

разделите Subscriptions на 2 таблицы:

  • Сначала будет храниться список фактических подписок (на данный момент подписок true): Person_Id | Newsletter_Id
  • Второй будет хранить журнал подписок (его обновления или изменения статуса)
1 голос
/ 25 мая 2010

упорядоченные аналитические функции "- это стандартный метод для такого рода проблем. 1М записей, нет проблем ... в зависимости от мощности вашего компьютера, конечно.

MAX( Updated) OVER( PARTITION BY список полей, над которыми вы хотите "max" )

SELECT
  x.*
FROM
  (
    SELECT
       Person_ID
       , Newsletter_ID
       --, Subscribed
       , Updated
       , MAX(Updated) OVER( PARTITION BY Person_ID, Newsletter_ID, Subscribed) AS myUpdated
   FROM Subscriptions
  ) x 
WHERE Updated = myUpdated
0 голосов
/ 17 мая 2010

Я думаю, ваш дизайн довольно хорош.Для подзапросов нет никакой медлительности - используйте их, если это лучший способ выразить ваш запрос.

Вот запрос, который получает все последние (то есть не переопределенные) директивы:

SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated)
FROM Subscriptions GROUP BY Person_ID, Newsletter_ID

Затем вы можете использовать этот запрос как подзапрос другого запроса, чтобы получить то, что вы хотите.Для вашего запроса № 1:

SELECT x.Person_ID, x.Newsletter_ID FROM
  (SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated) 
   FROM Subscriptions GROUP BY Person_ID, Newsletter_ID) x
WHERE x.Subscribed;

Для запроса № 2:

SELECT x.Person_ID FROM
  (SELECT Person_ID, Newsletter_ID, Subscribed, MAX(Updated)
   FROM Subscriptions GROUP BY Person_ID, Newsletter_ID) x
WHERE x.Subscribed AND x.Newsletter_ID = ?

Вам определенно понадобится индекс для Newsletter_ID в таблице Subscriptions, поскольку этот запросскорее всего, будет очень избирательным.

Редактировать: Упс, столбец Подписки в подзапросе может быть произвольной строкой, а не той, которая генерирует MAX (Обновлено).Вы должны воссоединиться с оригинальным столом:

SELECT x.Person_ID, x.Newsletter_ID, y.Subscribed FROM
  (SELECT Person_ID, Newsletter_ID, MAX(Updated) as MaxUpdated
   From Subscriptions GROUP by Person_ID, Newsletter_ID) x
  JOIN Subscriptions y WHERE x.Person_ID = y.Person_ID AND
                             x.Newsletter_ID = y.Newsletter_ID AND
                             x.MaxUpdated = y.Updated
...