Sqlite3: надо картезианское свидание - PullRequest
5 голосов
/ 02 ноября 2009

У меня есть таблица со списком игр, в которые играли в базе данных sqlite3. Поле «datetime» - это дата и время окончания игры. Поле «длительность» - это количество секунд, которое длилась игра. Я хочу знать, в каком проценте за последние 24 часа было запущено как минимум 5 игр одновременно. Я выяснил, сколько игр запущено в данный момент:

select count(*)
from games
where strftime('%s',datetime)+0 >= 1257173442 and
      strftime('%s',datetime)-duration <= 1257173442

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

select count(*)
from (
  select count(*) as concurrent, d.second
  from games g, date d
  where strftime('%s',datetime)+0 >= d.second and
        strftime('%s',datetime)-duration <= d.second and
        d.second >= strftime('%s','now') - 24*60*60 and
        d.second <= strftime('%s','now')
  group by d.second) x
where concurrent >=5

Есть ли способ создать эту таблицу дат на лету ? Или что я могу получить эффект, подобный этому, без необходимости фактически создавать новую таблицу , которая будет просто списком всех секунд на этой неделе?

Спасибо

Ответы [ 4 ]

3 голосов
/ 03 ноября 2009

Во-первых, я не могу придумать, как решить вашу проблему, создав таблицу на лету или без помощи дополнительной таблицы. К сожалению.

Я предлагаю вам полагаться на статическую таблицу чисел.

Создать фиксированную таблицу в формате:

CREATE TABLE Numbers (
    number INTEGER PRIMARY KEY
);

Заполните его количеством секунд в 24 часах (24 * 60 * 60 = 84600). Я бы использовал любой язык сценариев, чтобы сделать это, используя оператор вставки:

insert into numbers default values;

Теперь таблица чисел имеет номера от 1 до 84600. Ваш запрос будет изменен на:

select count(*)
  from (
        select count(*) as concurrent, strftime('%s','now') - 84601 + n.number second
          from games g, numbers n
         where strftime('%s',datetime)+0 >= strftime('%s','now') - 84601 + n.number and
               strftime('%s',datetime)-duration <= strftime('%s','now') - 84601 + n.number
         group by second) x
 where concurrent >=5

Без процедурных формулировок, я думаю, это лучшее, что вы сможете сделать.

2 голосов
/ 04 ноября 2009

Кевин, скорее всего, побьет меня там до изюминки (+1), но я опубликую этот вариант, так как он немного отличается.

Ключевые идеи

  • Отображение данных в поток событий с атрибутами времени и «полярностью» (= начало или конец игры)
  • Сохраняйте общее количество игр, открытых во время каждого события. (это делается путем формирования самостоятельного соединения в потоке событий)
  • Найдите время, когда количество игр (по словам Кевина) увеличивается до 5 или до 4
  • Небольшая хитрость: сложите все до 4 раз и уберите до 5 - порядок не важен
  • Результат - количество секунд, проведенных в 5 или более открытых играх

У меня нет sqllite, поэтому я тестировал с MySQL и не удосужился ограничить временное окно, чтобы сохранить здравомыслие. Не должно быть трудно пересмотреть.

Кроме того, и что еще более важно, я не думал, что делать, если игры открыты в начале или в конце периода!

Что-то подсказывает мне, что здесь есть большое упрощение, но я еще не заметил его.

SELECT SUM( event_time )  
FROM (
SELECT  -ga.event_type * ga.event_time AS event_time,
    SUM(  ga.event_type * gb.event_type ) event_type
FROM
    ( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
          , 1 event_type
      FROM    games g1
      UNION
      SELECT UNIX_TIMESTAMP( g1.endtime )
          , -1
      FROM    games g1 ) AS ga,
    ( SELECT UNIX_TIMESTAMP( g1.endtime - g1.duration ) AS event_time
          , 1 event_type
      FROM    games g1
      UNION
      SELECT UNIX_TIMESTAMP( g1.endtime )
          , -1
      FROM    games g1 ) AS gb
WHERE
    ga.event_time >= gb.event_time
GROUP BY ga.event_time
HAVING SUM( ga.event_type * gb.event_type ) IN ( -4, 5 )
) AS gr
2 голосов
/ 03 ноября 2009

Отличный вопрос!

Вот запрос, который я думаю даст вам, что вы хотите, без использования отдельной таблицы. Обратите внимание, что это не проверено (поэтому, вероятно, содержит ошибки), и я предположил, что datetime - это столбец типа int с # секундами, чтобы избежать тонны strftime.

select sum(concurrent_period) from (
  select min(end_table.datetime - begin_table.begin_time) as concurrent_period
  from (
    select g1.datetime, g1.num_end, count(*) as concurrent
    from (
      select datetime, count(*) as num_end
             from games group by datetime
    ) g1, games g2
    where g2.datetime >= g1.datetime and
          g2.datetime-g2.duration < g1.datetime and
          g1.datetime >= strftime('%s','now') - 24*60*60 and
          g1.datetime <= strftime('%s','now')+0
  ) end_table, (
    select g3.begin_time, g1.num_begin, count(*) as concurrent
    from (
      select datetime-duration as begin_time,
             count(*) as num_begin
             from games group by datetime-duration
    ) g3, games g4
    where g4.datetime >= g3.begin_time and
          g4.datetime-g4.duration < g3.begin_time and
          g3.begin_time >= strftime('%s','now') - 24*60*60 and
          g3.begin_time >= strftime('%s','now')+0
  ) begin_table
  where end_table.datetime > begin_table.begin_time
        and begin_table.concurrent < 5
        and begin_table.concurrent+begin_table.num_begin >= 5
        and end_table.concurrent >= 5
        and end_table.concurrent-end_table.num_end < 5
  group by begin_table.begin_time
) aah

Основная идея состоит в том, чтобы составить две таблицы: одну с # одновременными играми в начале каждой игры и одну с # одновременными играми в конце. Затем соедините таблицы вместе и собирайте строки только в «критических точках», где количество одновременных игр пересекается с 5. Для каждого критического времени начала принимайте критическое время окончания, которое произошло раньше, и которое, как мы надеемся, дает все периоды, когда по крайней мере 5 игр выполнялись одновременно.

Надеюсь, это не слишком запутанно, чтобы быть полезным!

0 голосов
/ 03 ноября 2009

Почему бы вам не урезать дату и сохранить только время, если вы фильтруете свои данные для любой данной даты, каждый раз уникален. Таким образом, вам понадобится только таблица с номерами от 1 до 86400 (или меньше, если вы используете большие интервалы), вы можете создать два столбца «от» и «до», чтобы определить интервалы. Я не знаком с функциями SQLite, но согласно руководству вы должны использовать функцию strftime в следующем формате: ЧЧ: ММ: СС.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...