Wat is die mees eenvoudige manier om pad leë datums in SQL resultate (op óf MySQL of perl einde)?

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

  •  09-06-2019
  •  | 
  •  

Vra

Ek is die bou van 'n vinnige CSV uit 'n MySQL tabel met 'n navraag soos:

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

en hulle net storting na 'n lêer in perl oor 'n:

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

Daar is op datum gapings in die data, al is:

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

Ek wil graag pad die data in die vermiste dae met 'n nul-telling inskrywings te eindig met te vul:

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

Ek geklap saam 'n baie ongemaklike (en byna seker karretjie) tydelike oplossing met 'n verskeidenheid van dae-per-maand en 'n paar wiskunde, maar daar moet iets meer eenvoudig óf op die MySQL of perl kant wees.

Enige geniale idees / klappe in die gesig vir hoekom my is die feit dat so dom?


Ek het uiteindelik gaan met 'n gestoor proses wat 'n tydelike tabel gegenereer vir die periode in vraag vir 'n paar redes:

  • Ek weet die datum bereik Ek sal die uitkyk wees vir elke keer as
  • Die bediener betrokke was ongelukkig nie een wat ek perl modules op OTM kan installeer, en die toestand van dit was afgeleefde genoeg dat dit niks gehad het nie naastenby Datum :: - y geïnstalleer

Die perl Datum / Date Time-iterating antwoorde was ook baie goed, ek wens ek kon meer as een antwoord kies!

Was dit nuttig?

Oplossing

As jy iets soos dit op die bediener kant nodig, jy gewoonlik te skep 'n tafel wat alle moontlike datums tussen twee punte in die tyd bevat, en dan links by hierdie tafel met navraag resultate. Iets soos hierdie:

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

In hierdie spesifieke geval is dit beter sou wees om 'n bietjie tjek op die kliënt kant sit, as die huidige datum is nie previos + 1, sit 'n paar Daarbenewens snare.

Ander wenke

As ek te doen gehad met hierdie probleem, in ontbreek datums ek 'n verwysing tafel wat net soos vervat al die datums Ek stel belang in en by die datatabel op die veld datum eintlik geskep in te vul. Dis ru, maar dit werk.

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;

As vir uitvoer, ek wil net gebruik KIES IN outfile in plaas van die opwekking van die CSV met die hand. Laat ons vry van sorge te maak oor die ontsnapping spesiale karakters sowel.

nie stom, dit is nie iets wat MySQL doen, plaas die leë datum waardes. Ek doen dit in perl met 'n twee-stap proses. In die eerste plek te laai al die data van die navraag in 'n gemors georganiseer deur datum. Dan, Ek skep 'n Datum :: EzDate voorwerp en inkrementeer dit oordag, so ...

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++;
}

waar finale datum is nog 'n EzDate voorwerp of 'n string met die einde van jou datum bereik.

EzDate is nie op CPAN nou nie, maar jy kan waarskynlik 'n ander perl mod daardie datum vergelyk sal doen en gee 'n datum incrementor.

Jy kan 'n Datum tyd gebruik voorwerp:

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";
}

Wat die bo-kode doen, is dit hou die laaste gedrukte datum gestoor in 'n DateTime voorwerp $dt, en wanneer die huidige datum is meer as een dag in die toekoms, dit vermeerderings $dt deur een dag (en druk dit 'n lyn te CSV) totdat dit is dieselfde as die huidige datum.

Op hierdie manier wat jy nie ekstra tafels nodig, en hoef nie te gaan haal al jou rye in advance.

Ek hoop jy sal uit te vind die res.

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

Met

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

jy sal 'n kolom met getalle van 0 kry om maksimum (N3) * 100 + max (N2) * 10 + max (N1)

Sedert hier het ons maksimum n3 as 3, sal SELECT terugkeer 399, plus 0 -.> 400 rekords (datums in kalender)

Jy kan inskakel jou dinamiese kalender deur die beperking van dit, byvoorbeeld, van min (datum) jy nou moet ().

Aangesien jy weet nie waar die leemtes is, en nog jy wil al die waardes (vermoedelik) van die eerste datum in jou lys tot die laaste een, iets soos te doen:

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 );
}

Hmm, wat blyk meer ingewikkeld as wat ek gedink het dit sou wees om te wees .. Ek hoop dit maak sin!

Ek dink die eenvoudigste algemene oplossing vir die probleem sou wees om 'n Ordinal tafel met die hoogste aantal rye wat jy nodig het te skep (in jou geval 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

Volgende, doen 'n LEFT JOIN van Ordinal op jou data. Hier is 'n eenvoudige saak, om elke dag in die laaste week:

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

Die twee dinge wat jy nodig sou wees om te verander oor hierdie is die beginpunt en die interval. Ek het SET @var = 'value' sintaksis gebruik vir duidelikheid.

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;

So die finale kode iets sou lyk soos hierdie, as jy vas teen die aantal boodskappe per dag kry oor die afgelope drie maande:

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`

Wenke en Kommentaar:

  • Waarskynlik die moeilikste deel van jou navraag is die bepaling van die aantal dae om te gebruik wanneer die beperking van Ordinal. In vergelyking met die transformasie wat heelgetal volgorde in datums was maklik.
  • Jy kan Ordinal gebruik vir al jou ononderbroke-volgorde nodig het. Maak net seker dit bevat meer rye as jou langste ry.
  • Jy kan verskeie navrae gebruik op Ordinal vir verskeie rye, byvoorbeeld notering elke weekdag (1-5) vir die afgelope sewe (1-7) weke.
  • Jy kan dit maak vinniger deur datums stoor in jou Ordinal tafel, maar dit sal minder buigsaam wees. Hierdie manier waarop jy net een nodig het Ordinal tafel, maak nie saak hoeveel keer jy dit gebruik. Tog, as die spoed is die moeite werd, probeer die INSERT INTO ... SELECT sintaksis.

Gebruik 'n paar Perl module tot op datum berekeninge te doen, soos aanbeveel Datum tyd of Time :: Stuk (kern van 5.10). Net inkrementeer datum en gedrukte datum en 0 tot datum sal die huidige pas.

Ek weet nie of dit sal werk, maar hoe gaan dit as jy 'n nuwe tabel wat al die moontlike datums vervat geskep (wat dalk die probleem wees met hierdie idee, as die omvang van die datums gaan onvoorspelbaar verander .. .) en dan 'n links te sluit op die twee tafels? Ek dink dit is 'n gek oplossing indien daar 'n groot aantal moontlike datums, of geen manier om die eerste en laaste datum voorspel, maar as die omvang van die datums óf vaste is of maklik om uit te werk, dan is dit dalk werk.

Gelisensieer onder: CC-BY-SA met toeskrywing
Nie verbonde aan StackOverflow
scroll top