Каков самый простой способ заполнения пустых дат в результатах sql (на mysql или perl end)?

Я создаю быстрый csv из таблицы mysql с запросом типа:

select DATE(date),count(date) from table group by DATE(date) order by date asc;

и просто сбрасывает их в файл в perl над a:

while(my($date,$sum) = $sth->fetchrow) {
    print CSV "$date,$sumn"
}

в данных есть пробелы в дате, хотя:

| 2008-08-05 |           4 | 
| 2008-08-07 |          23 | 

Я хотел бы заполнить данные, чтобы заполнить недостающие дни записями с нулевым числом, чтобы в конечном итоге:

| 2008-08-05 |           4 | 
| 2008-08-06 |           0 | 
| 2008-08-07 |          23 | 

Я собрал действительно неудобный (и почти наверняка багги) обходной путь с массивом дней в месяц и некоторой математикой, но должно быть что-то более простое либо на стороне mysql, либо на стороне perl.

любые гениальные идеи / пощечины за то, почему я так глуп?


Я закончил с хранимой процедурой, которая создала временную таблицу для рассматриваемого диапазона дат по нескольким причинам:

  • я знаю диапазон дат, который я буду искать каждый раз
  • сервер, о котором идет речь, к сожалению, не тот, на котором я могу установить модули perl atm, и состояние его было достаточно ветхим, чтобы у него не было ничего удаленно дата:: - y установлен

perl Date / DateTime-итерационные ответы также были очень хорошими, я хотел бы выбрать несколько ответов!

9 ответов


когда вам нужно что-то подобное на стороне сервера, вы обычно создаете таблицу, содержащую все возможные даты между двумя точками времени, а затем слева присоединяете эту таблицу с результатами запроса. Что-то вроде этого:--2-->

create procedure sp1(d1 date, d2 date)
  declare d datetime;

  create temporary table foo (d date not null);

  set d = d1
  while d <= d2 do
    insert into foo (d) values (d)
    set d = date_add(d, interval 1 day)
  end while

  select foo.d, count(date)
  from foo left join table on foo.d = table.date
  group by foo.d order by foo.d asc;

  drop temporary table foo;
end procedure

в данном конкретном случае было бы лучше поставить небольшую проверку на стороне клиента, если текущая дата не равна предыдущей+1, поставить некоторые добавления строк.


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

SELECT DATE(r.date),count(d.date) 
FROM dates AS r 
LEFT JOIN table AS d ON d.date = r.date 
GROUP BY DATE(r.date) 
ORDER BY r.date ASC;

что касается вывода, я бы просто использовал ВЫБЕРИТЕ В OUTFILE вместо генерации CSV вручную. Оставляет нас свободными от беспокойства о побеге специальных персонажей.


не тупой, это не то, что делает MySQL, вставляя пустые значения даты. Я делаю это в perl с помощью двухэтапного процесса. Сначала загрузите все данные из запроса в хэш, организованный по дате. Затем я создаю объект Date::EzDate и увеличиваю его на день, так...

my $current_date = Date::EzDate->new();
$current_date->{'default'} = '{YEAR}-{MONTH NUMBER BASE 1}-{DAY OF MONTH}';
while ($current_date <= $final_date)
{
    print "$current_date\t|\t%hash_o_data{$current_date}";  # EzDate provides for     automatic stringification in the format specfied in 'default'
    $current_date++;
}

где final date-это другой объект EzDate или строка, содержащая конец диапазона дат.

EzDate сейчас не на CPAN, но вы, вероятно, можете найти другой Perl mod, который будет делать дату сравнивает и предоставляет дату incrementor.


вы могли бы использовать DateTime


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

use DateTime;
use DateTime::Format::Strptime;
my @row = $sth->fetchrow;
my $countdate = strptime("%Y-%m-%d", $firstrow[0]);
my $thisdate = strptime("%Y-%m-%d", $firstrow[0]);

while ($countdate) {
  # keep looping countdate until it hits the next db row date
  if(DateTime->compare($countdate, $thisdate) == -1) {
    # counter not reached next date yet
    print CSV $countdate->ymd . ",0\n";
    $countdate = $countdate->add( days => 1 );
    $next;
  }

  # countdate is equal to next row's date, so print that instead
  print CSV $thisdate->ymd . ",$row[1]\n";

  # increase both
  @row = $sth->fetchrow;
  $thisdate = strptime("%Y-%m-%d", $firstrow[0]);
  $countdate = $countdate->add( days => 1 );
}

я думаю, что самым простым общим решением проблемы было бы создать Ordinal таблица с наибольшим количеством строк, которые вам нужны (в вашем случае 31*3 = 93).

CREATE TABLE IF NOT EXISTS `Ordinal` (
  `n` int(10) unsigned NOT NULL AUTO_INCREMENT, PRIMARY KEY (`n`)
);
INSERT INTO `Ordinal` (`n`)
VALUES (NULL), (NULL), (NULL); #etc

далее сделать LEFT JOIN С Ordinal на ваши данные. Вот простой случай, получающийся каждый день на прошлой неделе:

SELECT CURDATE() - INTERVAL `n` DAY AS `day`
FROM `Ordinal` WHERE `n` <= 7
ORDER BY `n` ASC

две вещи, которые вам нужно будет изменить об этом, - это начальная точка и интервал. Я использовал SET @var = 'value' синтаксис ясность.

SET @end = CURDATE() - INTERVAL DAY(CURDATE()) DAY;
SET @begin = @end - INTERVAL 3 MONTH;
SET @period = DATEDIFF(@end, @begin);

SELECT @begin + INTERVAL (`n` + 1) DAY AS `date`
FROM `Ordinal` WHERE `n` < @period
ORDER BY `n` ASC;

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

SELECT COUNT(`msg`.`id`) AS `message_count`, `ord`.`date` FROM (
    SELECT ((CURDATE() - INTERVAL DAY(CURDATE()) DAY) - INTERVAL 3 MONTH) + INTERVAL (`n` + 1) DAY AS `date`
    FROM `Ordinal`
    WHERE `n` < (DATEDIFF((CURDATE() - INTERVAL DAY(CURDATE()) DAY), ((CURDATE() - INTERVAL DAY(CURDATE()) DAY) - INTERVAL 3 MONTH)))
    ORDER BY `n` ASC
) AS `ord`
LEFT JOIN `Message` AS `msg`
  ON `ord`.`date` = `msg`.`date`
GROUP BY `ord`.`date`

советы и комментарии:

  • вероятно, самой сложной частью вашего запроса было определение количества дней для использования при ограничении Ordinal. Для сравнения, преобразование этой целочисленной последовательности в даты было простым.
  • можно использовать Ordinal для всех ваших непрерывная последовательность потребностей. Просто убедитесь, что он содержит больше строк, чем ваша самая длинная последовательность.
  • вы можете использовать несколько запросов о Ordinal для нескольких последовательностей, например, перечисляя каждый будний день (1-5) за последние семь (1-7) недель.
  • вы можете сделать это быстрее, сохраняя даты в вашем Ordinal таблица, но она была бы менее гибкой. Таким образом, вам нужен только один Ordinal таблица, независимо от того, сколько раз вы его используете. Тем не менее, если скорость стоит того, попробуйте INSERT INTO ... SELECT синтаксис.

Я надеюсь, что вы выясните остальное.

select  * from (
select date_add('2003-01-01 00:00:00.000', INTERVAL n5.num*10000+n4.num*1000+n3.num*100+n2.num*10+n1.num DAY ) as date from
(select 0 as num
   union all select 1
   union all select 2
   union all select 3
   union all select 4
   union all select 5
   union all select 6
   union all select 7
   union all select 8
   union all select 9) n1,
(select 0 as num
   union all select 1
   union all select 2
   union all select 3
   union all select 4
   union all select 5
   union all select 6
   union all select 7
   union all select 8
   union all select 9) n2,
(select 0 as num
   union all select 1
   union all select 2
   union all select 3
   union all select 4
   union all select 5
   union all select 6
   union all select 7
   union all select 8
   union all select 9) n3,
(select 0 as num
   union all select 1
   union all select 2
   union all select 3
   union all select 4
   union all select 5
   union all select 6
   union all select 7
   union all select 8
   union all select 9) n4,
(select 0 as num
   union all select 1
   union all select 2
   union all select 3
   union all select 4
   union all select 5
   union all select 6
   union all select 7
   union all select 8
   union all select 9) n5
) a
where date >'2011-01-02 00:00:00.000' and date < NOW()
order by date

С

select n3.num*100+n2.num*10+n1.num as date

вы получите столбец с номерами от 0 до max(n3)*100+max(n2)*10+max (n1)

Так как здесь у нас есть max n3 как 3, SELECT вернет 399, плюс 0 - > 400 записей (даты в календаре).

вы можете настроить свой динамический календарь, ограничив его, например, от min (дата) вы должны сейчас ().


используйте модуль Perl для вычисления даты, например, рекомендуемое DateTime или Time:: Piece (core от 5.10). Просто увеличьте дату и дату печати и 0, пока дата не будет соответствовать текущему.


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