Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Составление SQL-запросов > Запрос завязанных на времени данных


Автор: SoWa 26.7.2011, 18:23
Привет, друзья! Помогите пожалуйста составить запрос для следующей задачи:
Предположим, есть таблица, и ней три рабочих столбца - "Время", "Пользователь" и "Последнее действие"
Пользователь - он и есть пользователь, время ставится триггером при добавлении записи в таблицу, а Последнее действие может принимать два значения: "подписался" и "отписался" (subscribe & unsubscribe).
Очевидно, что пользователь может сперва подписаться, потом отписаться, потом снова подписаться и снова отписаться. И нужно узнать список всех пользователей, которые на данный момент подписаны.
Например, данные в таблице(дял простоты время цифрами по порядку):
1  Вася  подписался
2  Миша  подписался
3  Витя  подписался
4  Витя  отписался
5  Миша  отписаля
6  Витя  подписался

В результате запроса мне нужно получить подписанных сейчас: Васю и Витю.
Как проверить статус одного конкретного пользователя я догадался:
SELECT Пользователь FROM Activities WHERE Последнее действие="подписался" AND Пользователь=Витя ORDER BY Время LIMIT 1
и увидеть, подписан сейчас Витя или нет. (Может быть я забыл DESC...)

А как проделать такое с громадной таблицей и получить именно список всех подписанных на данный момент.

PS Прошу не предлагать варианта SELECT Пользователь FROM Activities WHERE Последнее действие="подписался"
Потому что задача не предполагает хранение по одному экземпляру пользователя и переустанове ему свойства "Последнее действие". Именно добавление каждого нового действия новой строкой. Такая штука нужна для разработки потоковой шины над базой данных.

Заранее спасибо!

Автор: Gluttton 26.7.2011, 18:41
SoWa, а можно выбрать максимальное время с группировкой по пользователям получив при этом последнее действие каждого пользователя, а затем в WHERE выбрать тех, у которых "подписался"? Или я не внимательно читал задание smile ?

Автор: SoWa 26.7.2011, 19:04
А это как?  smile 
Меня максимальное время настораживает - в таблицу постоянно сыплются новые записи... Если это чем-то поможет пониманию :(

Добавлено через 1 минуту и 14 секунд
Если бы я знал, что такое "с группировкой", то конечно, в конце подписать WHERE = ... и дело в шляпе. Но я не знаю, что за группировка (

Автор: Gluttton 26.7.2011, 19:11
Цитата(SoWa @  26.7.2011,  19:04 Найти цитируемый пост)
А это как?

Код

select
    user,
    max (insert_time)
from
    some_table
    group by user
    having user_action = "subscribe"


Цитата(SoWa @  26.7.2011,  19:04 Найти цитируемый пост)
Меня максимальное время настораживает - в таблицу постоянно сыплются новые записи... 

Безусловно такой запрос будет не из быстрых, т.к., если я не ошибаюсь, то для поиска максимального элемента должна будет выполнена сортировка и на больших объемах данных этот вариант не годится. Но! при существующей постановке задачи мне кажется, что приемлимого решения нет и необходимо вводить дополнительные решения ну там флаги какие нибудь, там тригеры или ещё чего-нибудь... Например можно сделать отдельную таблицу с одной единственной записью состояний всех пользователей и поставить триггер на вставку данных. При этом выбирать данные будет очень просто, но зато вставка в таблицу будет происходить медленнее...

Поправил запрос - добавил user в select.

И не WHERE, а HAVING - я был не прав.

Автор: SoWa 26.7.2011, 19:19
Ой, спасибо огромное! Сейчас MS SQL поставится - буду проверять smile Лови плюсик! /*Но не уходи пока, вдруг не получится О_О*/
Разумеется, это медленное решение, как архитектор я это понимаю, и конечно буду оптимизировать. А пока моя задача - накидать приемлимо-понятный код на известном мне языке(C#) и показать завтра ребятам, чтобы они уже начинали делать рабочую версию со всеми прибамбасами.

Автор: solenko 26.7.2011, 19:47
Цитата(Gluttton @  26.7.2011,  18:11 Найти цитируемый пост)
Выделить всёкод SQL
Код

select
    user, user_action
    max (insert_time)
from
    some_table
    group by user
    having user_action = "subscribe"


код некорректный. Некоторый СУБД будут плеваться на смешение в запросе агретных функций а некоторые просто выдавать случайные данные для user_action.

Оптимальный вариант (можно сказать стандарт) для решения такой задачи:
1. Форматируем дату так, чтобы она правильно сортировалась в строковом виде, например, YYYYMMDDHIS
2. Присоединяем српава интересуэщую нас колонку со статусом (user_action)
3. группируем по id пользователя и выбираем MAX получившейся колонки
4. В select разделяем статус и дату (проблемы не составляет, т.к. блинна даты точно известна)

Второй вариант join таблици самой на себя, но он существенно тормознутее

Добавлено через 1 минуту и 58 секунд
Кстати, раз уж используете триггеры, то почему не денормализировать данные и не хранить _актуальный_ статус для каждого пользователя? 

Автор: SoWa 26.7.2011, 20:05
Цитата(solenko @  26.7.2011,  19:47 Найти цитируемый пост)
1. Форматируем дату так, чтобы она правильно сортировалась в строковом виде, например, YYYYMMDDHIS2. Присоединяем српава интересуэщую нас колонку со статусом (user_action)3. группируем по id пользователя и выбираем MAX получившейся колонки4. В select разделяем статус и дату (проблемы не составляет, т.к. блинна даты точно известна)

Я не знаком с понятием присоединение справа, группировками и прочими. Мне просто нужен запросик, вот и все :(

Невозможно что-то приписывать пользователям в нашей задаче. Централизованное хранилище пользователей находится не в этой таблице - это первое.
Второе - модель данных системы предполагает, что пользователь может быть подписан на один тип данных и не подписан на другой.
Т.е. пользователи у нас(а быть точнее - у нас нет такого понятия вообще - есть Source) болванки с какими-то данными, никак не относящимися к потоку.


Автор: solenko 26.7.2011, 20:22
Цитата(SoWa @  26.7.2011,  19:05 Найти цитируемый пост)
Я не знаком с понятием присоединение справа, группировками и прочими. 

А я не знаком с ms sql. Для mysql будет примерно так:
Код

SELECT source_id, 
   LEFT(MAX(CONCAT(DATE_FORMAT(date_field, '%Y%m%d%H%i%s' ), action)), 14) as last_date,
   SUBSTRING(MAX(CONCAT(DATE_FORMAT(date_field, '%Y%m%d%H%i%s' ), action)), 15) as last_action
FROM history_table
GROUP BY source_id


Автор: Gluttton 26.7.2011, 22:16
Цитата(SoWa @  26.7.2011,  19:19 Найти цитируемый пост)
Ой, спасибо огромное! 

Незачто!
Цитата(SoWa @  26.7.2011,  19:19 Найти цитируемый пост)
Лови плюсик! /*Но не уходи пока, вдруг не получится О_О*/

Судя по всему преждевременно ;) .

solenko, 
Цитата(solenko @  26.7.2011,  19:47 Найти цитируемый пост)
код некорректный. 

Да, действительно код не валидный.

Не мне кажеться, что "кнопка" должна где то быть ;) .

Автор: Zloxa 26.7.2011, 22:48
Цитата(solenko @  26.7.2011,  19:47 Найти цитируемый пост)
Оптимальный вариант (можно сказать стандарт) для решения такой задачи

Яхуууу  smile , это же старый, добрый http://forum.vingrad.ru/index.php?showtopic=238891&view=findpost&p=1717573!!!!!

Цитата(SoWa @  26.7.2011,  19:19 Найти цитируемый пост)
MS SQL 

Какая версия? если 2005 аnd over

Код

select s.пользователь
from 
  (SELECT s.*, row_number() over (partition by Пользователь order by Время desc) rn  FROM Activities s) s
where s.rn = 1 adn s.[Последнее действие] = 'subscribe'

Автор: SoWa 27.7.2011, 06:24
А что такое s?

Добавлено через 2 минуты и 25 секунд
Извиняюсь, не важно что такое s - главное, что это сработало!!!

Автор: Zloxa 27.7.2011, 07:28
Цитата(SoWa @  27.7.2011,  06:24 Найти цитируемый пост)
А что такое s?

Прсевдоним набора данных. Предназначен для разрешения конфликта имен. MS SQL обязует псевдонимить подзапросы. Даже там, где конфликтов быть не может  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)