Аналитический запрос - PullRequest
4 голосов
/ 28 января 2009

Ищу один запрос, который может преобразовать следующую информацию в таблицу

name:time    :state
a   :10:00 AM:login
b   :10:05 AM:login
a   :10:06 AM:chatting
a   :10:08 AM:Idle
b   :10:11 AM:chatting
a   :10:10 AM:Logout
b   :10:12 AM:Logout

примерно так (учитывая период времени с 10:00 до 10:15 как период запроса)

name: State    :Duration
a   : chatting :2 Minutes 
a   : Idle     :2 Minutes
b   : chatting :1 Minute

Может ли это быть сделано ТОЛЬКО с использованием SQL? Использую Informix версии 11.5

Ответы [ 2 ]

5 голосов
/ 30 января 2009

Это можно сделать одним оператором SQL. Вот доказательство.

Настройка

CREATE TEMP TABLE eventtable
(
    name CHAR(3) NOT NULL,
    time DATETIME HOUR TO MINUTE NOT NULL,
    state CHAR(8) NOT NULL
);

INSERT INTO eventtable(name, time, state) VALUES('a', '10:00', 'login');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:05', 'login');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:06', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:08', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('a', '10:10', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('b', '10:12', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:01', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:02', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:03', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:04', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:05', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:06', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:07', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:08', 'Logout');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:09', 'login');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:11', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:12', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:13', 'chatting');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:14', 'Idle');
INSERT INTO eventtable(name, time, state) VALUES('c', '10:15', 'Logout');

Правильный запрос

Обратите внимание на условия. Таблица результатов должна исключать периоды между 'login' и первым другим событием; кроме того, он должен исключать период между «Выходом из системы» и следующим событием (предположительно, «входом в систему»). Самосоединение между таблицей в столбце name и затем асимметричным объединением в столбце time (с использованием <) обеспечивает порядок событий. Суб-выбор NOT EXISTS гарантирует, что рассматриваются только смежные события. Использование BETWEEN AND в подзапросе является ошибкой, поскольку оно включает его конечные точки, и крайне важно, чтобы r1.time и r2.time были исключены из диапазона; Мне понадобилось несколько минут, чтобы обнаружить эту ошибку (запрос был выполнен, но строк не было, но почему ?)!

SELECT r1.name, r1.state, r2.TIME - r1.TIME AS duration
    FROM eventtable r1, eventtable r2
    WHERE r1.name = r2.name
      AND r1.time < r2.time
      AND r1.state != 'login'
      AND r1.state != 'Logout'
      AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND NOT EXISTS (SELECT 1 FROM eventtable r3
                            WHERE r3.time > r1.time AND r3.time < r2.time
                      AND r3.name = r1.name
                      AND r3.name = r2.name);

Это дает ответ:

name state      duration
a    chatting   0:02
a    Idle       0:02
b    chatting   0:01

c    chatting   0:01
c    Idle       0:01
c    Idle       0:01
c    Idle       0:01
c    chatting   0:01
c    Idle       0:01
c    chatting   0:01
c    Idle       0:01

Значение продолжительности - ИНТЕРВАЛЬНЫЙ ЧАС В МИНУТУ; если вы хотите получить значение в считанные минуты, вы должны преобразовать его с использованием приведения (используя 4 для точности, чтобы обеспечить интервалы до 1440 минут или 1 день; данные для более длинных периодов времени неоднозначны):

(r2.time - r1.time)::INTERVAL MINUTE(4) TO MINUTE

Или:

CAST (r2.time - r1.time AS INTERVAL MINUTE(4) TO MINUTE)

IBM Informix Dynamic Server (IDS) имеет очень подробные записи для постоянных времени. В стандартном SQL вы можете использовать TIME в качестве типа и TIME '10: 00: 00' в качестве значения, но в строгом стандартном SQL потребуются секунды. IDS действительно предоставляет точные типы, которые люди хотят, такие как DATETIME HOUR TO MINUTE. Вы также написали бы INTERVAL MINUTE (4) в стандартном SQL; 'TO MINUTE' должен быть необязательным.

Неверный запрос

В своем комментарии к ответу Рэя Хидайата я указал, что подзапрос EXISTS необходим для обеспечения того, что рассматриваемые события являются смежными - никаких промежуточных событий нет. Вот тот же запрос с начальным и конечным временем, добавленным к выходным данным, и отсутствующим предложением EXISTS (и «duration», переименованным в «lapse»):

SELECT r1.name, r1.state, r2.TIME - r1.TIME AS lapse,
       r1.time AS start, r2.time AS end
    FROM eventtable r1, eventtable r2
    WHERE r1.name = r2.name
      AND r1.time < r2.time
      AND r1.state != 'login'
      AND r1.state != 'Logout'
      AND r1.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE
      AND r2.time BETWEEN DATETIME(10:00) HOUR TO MINUTE
                      AND DATETIME(10:15) HOUR TO MINUTE;

Это дает ответ:

name state     lapse start end
a    chatting   0:04 10:06 10:10
a    chatting   0:02 10:06 10:08
a    Idle       0:02 10:08 10:10
b    chatting   0:01 10:11 10:12
c    chatting   0:13 10:02 10:15
c    chatting   0:12 10:02 10:14
c    chatting   0:11 10:02 10:13
c    chatting   0:10 10:02 10:12
c    chatting   0:09 10:02 10:11
c    chatting   0:07 10:02 10:09
c    chatting   0:06 10:02 10:08
c    chatting   0:05 10:02 10:07
c    chatting   0:04 10:02 10:06
c    chatting   0:03 10:02 10:05
c    chatting   0:02 10:02 10:04
c    chatting   0:01 10:02 10:03
c    Idle       0:12 10:03 10:15
c    Idle       0:11 10:03 10:14
c    Idle       0:10 10:03 10:13
c    Idle       0:09 10:03 10:12
c    Idle       0:08 10:03 10:11
c    Idle       0:06 10:03 10:09
c    Idle       0:05 10:03 10:08
c    Idle       0:04 10:03 10:07
c    Idle       0:03 10:03 10:06
c    Idle       0:02 10:03 10:05
c    Idle       0:01 10:03 10:04
c    Idle       0:10 10:05 10:15
c    Idle       0:09 10:05 10:14
c    Idle       0:08 10:05 10:13
c    Idle       0:07 10:05 10:12
c    Idle       0:06 10:05 10:11
c    Idle       0:04 10:05 10:09
c    Idle       0:03 10:05 10:08
c    Idle       0:02 10:05 10:07
c    Idle       0:01 10:05 10:06
c    Idle       0:08 10:07 10:15
c    Idle       0:07 10:07 10:14
c    Idle       0:06 10:07 10:13
c    Idle       0:05 10:07 10:12
c    Idle       0:04 10:07 10:11
c    Idle       0:02 10:07 10:09
c    Idle       0:01 10:07 10:08
c    chatting   0:04 10:11 10:15
c    chatting   0:03 10:11 10:14
c    chatting   0:02 10:11 10:13
c    chatting   0:01 10:11 10:12
c    Idle       0:03 10:12 10:15
c    Idle       0:02 10:12 10:14
c    Idle       0:01 10:12 10:13
c    chatting   0:02 10:13 10:15
c    chatting   0:01 10:13 10:14
c    Idle       0:01 10:14 10:15

Это показывает, как каждая подходящая начальная строка для пользователя 'c' сопоставляется с каждой приемлемой конечной строкой, давая много ложных строк данных. Подзапрос NOT EXISTS является распространенной темой при работе с запросами на основе времени. Вы можете найти информацию об этих операциях в « Разработка приложений, ориентированных на время на SQL » (PDF доступен онлайн по адресу URL), а также в «Дата, Дарвен и Лоренцос» Временные данные и реляционная модель ».

3 голосов
/ 28 января 2009

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

Редактировать: вот что я придумал

SELECT l.userid, l.state, SUM(t.minutes) AS duration
FROM Log l 
INNER JOIN (
    SELECT l1.id, (l2.time - l1.time) AS minutes
    FROM Log l1, Log l2
    WHERE l2.time == ( 
        -- find the next entry --
        SELECT TOP 1 ls.time
        FROM Log ls
        WHERE ls.Time > l1.Time && ls.userid = l1.userid
        ORDER BY ls.Time
    )
) t ON l.id == t.id
GROUP BY l.userid, l.state
ORDER BY l.userid

Это полупсевдокод, я составил все имена таблиц и тому подобное, и вы не сможете просто вычесть один раз из другого, вы, вероятно, будете использовать функцию DATEDIFF. Помимо этого, хотя, я думаю, что это суть этого. Я думаю, что SQL - один из самых удивительных языков, вы можете делать практически все с небольшим количеством кода.

...