SQL 결과(mysql 또는 Perl 끝)에서 빈 날짜를 채우는 가장 간단한 방법은 무엇입니까?

StackOverflow https://stackoverflow.com/questions/75752

  •  09-06-2019
  •  | 
  •  

문제

다음과 같은 쿼리를 사용하여 mysql 테이블에서 빠른 csv를 작성하고 있습니다.

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

다음을 통해 Perl의 파일에 덤프합니다.

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

하지만 데이터에는 날짜 차이가 있습니다.

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

누락된 날짜를 0개 항목으로 채우기 위해 데이터를 채우고 싶습니다.

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

나는 월별 일수 배열과 약간의 수학을 사용하여 정말 어색하고 (거의 확실히 버그가 있는) 해결 방법을 함께 사용했지만 mysql이나 Perl 측면에서는 좀 더 간단한 방법이 있어야 합니다.

내가 왜 그렇게 바보처럼 굴고 있는지에 대한 어떤 천재적인 아이디어나 뺨을 때리나요?


나는 몇 가지 이유로 문제의 날짜 범위에 대한 임시 테이블을 생성하는 저장 프로시저를 사용하게 되었습니다.

  • 매번 찾을 날짜 범위를 알고 있습니다.
  • 불행하게도 문제의 서버는 atm에 Perl 모듈을 설치할 수 있는 서버가 아니었고 상태가 너무 낡아서 원격으로 아무것도 설치되지 않았습니다. Date::-y

Perl Date/DateTime 반복 답변도 매우 좋았습니다. 여러 답변을 선택할 수 있었으면 좋겠습니다!

도움이 되었습니까?

해결책

서버 측에서 이와 같은 것이 필요한 경우 일반적으로 두 시점 사이의 가능한 모든 날짜를 포함하는 테이블을 만든 다음 이 테이블을 쿼리 결과와 조인합니다.이 같은:

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

이 특별한 경우에는 클라이언트 측에서 약간의 확인을 하는 것이 더 나을 것입니다. 현재 날짜가 previos+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;

출력에 관해서는 그냥 사용하겠습니다. 아웃파일로 선택 CSV를 직접 생성하는 대신.특수 문자를 탈출하는 것에 대한 걱정도 없어집니다.

바보가 아닙니다. 이것은 MySQL이 빈 날짜 값을 삽입하는 작업이 아닙니다.저는 Perl에서 2단계 프로세스를 통해 이 작업을 수행합니다.먼저 쿼리의 모든 데이터를 날짜별로 구성된 해시로 로드합니다.그런 다음 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++;
}

여기서 최종 날짜는 다른 EzDate 객체이거나 날짜 범위의 끝을 포함하는 문자열입니다.

EzDate는 현재 CPAN에 없지만 날짜 비교를 수행하고 날짜 증분기를 제공하는 다른 Perl 모드를 찾을 수 있습니다.

당신은 날짜 시간 물체:

use DateTime;
my $dt;

while ( my ($date, $sum) = $sth->fetchrow )  {
    if (defined $dt) {
        print CSV $dt->ymd . ",0\n" while $dt->add(days => 1)->ymd lt $date;
    }
    else {
        my ($y, $m, $d) = split /-/, $date;
        $dt = DateTime->new(year => $y, month => $m, day => $d);
    }
    print CSV, "$date,$sum\n";
}

위 코드의 역할은 마지막으로 인쇄된 날짜를DateTime 물체 $dt, 그리고 현재 날짜가 미래에 하루 이상이면 증가합니다. $dt 하루만큼(그리고 한 줄씩 인쇄합니다.CSV) 현재 날짜와 같을 때까지.

이렇게하면 여분의 테이블이 필요하지 않으며 모든 행을 미리 가져올 필요가 없습니다.

나머지는 당신이 알아내길 바랍니다.

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)까지의 숫자가 포함된 열을 얻게 됩니다.

여기서는 최대 n3이 3이므로 SELECT는 399와 0 -> 400개의 레코드(달력의 날짜)를 반환합니다.

예를 들어 최소(날짜)부터 지금()까지로 제한하여 동적 달력을 조정할 수 있습니다.

간격이 어디에 있는지 모르지만 목록의 첫 번째 날짜부터 마지막 ​​날짜까지 모든 값(아마도)을 원하므로 다음과 같이 수행하십시오.

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;

따라서 지난 3개월 동안의 일일 메시지 수를 얻기 위해 참여하는 경우 최종 코드는 다음과 같습니다.

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 여러 시퀀스의 경우(예: 지난 7주 동안 매주 평일(1-5) 나열)
  • 날짜를 저장하면 더 빠르게 만들 수 있습니다. Ordinal 테이블이지만 유연성이 떨어집니다.이렇게 하면 하나만 필요합니다. Ordinal 테이블은 몇 번 사용해도 상관없습니다.그래도 속도가 그만한 가치가 있다면 시도해 보세요. INSERT INTO ... SELECT 통사론.

권장되는 DateTime 또는 Time::Piece(5.10의 핵심)와 같은 일부 Perl 모듈을 사용하여 날짜 계산을 수행합니다.날짜를 늘리고 날짜를 인쇄하면 날짜까지 0이 현재와 일치합니다.

이것이 효과가 있을지는 모르겠지만, 가능한 모든 날짜가 포함된 새 테이블을 생성한다면 어떨까요(날짜 범위가 예측할 수 없게 변경된다면 이것이 이 아이디어의 문제일 수 있습니다...). 그런 다음 두 테이블에 대해 왼쪽 조인을 수행합니까?가능한 날짜가 너무 많거나 첫 번째 날짜와 마지막 날짜를 예측할 수 있는 방법이 없다면 이는 미친 해결책이라고 생각합니다. 그러나 날짜 범위가 고정되어 있거나 계산하기 쉬운 경우에는 이것이 효과가 있을 수 있습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top