Was ist der einfachste Weg, um Pad leer Daten in SQL-Ergebnisse (entweder mysql oder Perl-Ende)?

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

  •  09-06-2019
  •  | 
  •  

Frage

Ich baue eine schnelle csv aus einer MySQL-Tabelle mit einer Abfrage wie:

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

und sie in eine Datei in Perl über eine nur Dumping:

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

Es gibt Datum Lücken in den Daten, aber:

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

Ich mag Pad die Daten in den fehlenden Tagen mit Null-count Einträgen zu füllen, um am Ende mit:

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

Ich schlug zusammen eine wirklich peinlich (und mit ziemlicher Sicherheit Buggy) Abhilfe mit einer Reihe von Tagen pro Monat und einige Mathe, aber es hat etwas einfacher entweder auf dem MySQL oder Perl-Seite sein.

Jede Genie Idee / Ohrfeigen dafür, warum ich so dumm ist zu sein?


ich am Ende mit einer gespeicherten Prozedur gehen, die eine temporäre Tabelle für den Datumsbereich in Frage für ein paar Gründe generiert:

  • Ich kenne den Datumsbereich I für jedes Mal suchen würde
  • Der betreffenden Server war leider nicht ein, dass ich Perl-Module auf atm installieren kann, und der Zustand war altersschwach genug, dass es nicht etwas aus der Ferne aufgenommen hat :: - y installiert

Die Perl-Datum / Datetime-Iterieren Antworten waren auch sehr gut, ich wünschte, ich mehrere Antworten auswählen kann!

War es hilfreich?

Lösung

Wenn Sie so etwas wie das auf Server-Seite benötigen, erstellen Sie in der Regel eine Tabelle, die alle möglichen Daten zwischen zwei Zeitpunkten enthält, und dann nach links in dieser Tabelle mit Abfrageergebnissen kommen. So etwas wie folgt aus:

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 diesem speziellen Fall wäre es besser, ein wenig Kontrolle auf der Client-Seite zu setzen, wenn das aktuelle Datum nicht previos + 1 ist, setzen einige Zusatz-Strings.

Andere Tipps

Als ich mit diesem Problem zu tun hatte, in fehlenden Daten zu füllen ich eine Referenztabelle tatsächlich erstellt, die nur alle Daten enthalten Ich habe Interesse an und schloss sich der Datentabelle auf dem Datumsfeld. Es ist grob, aber es funktioniert.

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;

Wie für die Ausgabe, würde ich nur INTO OUTFILE SELECT anstelle der CSV von Hand zu erzeugen. Lassen uns frei von Sorgen über zu entkommen Sonderzeichen als auch.

nicht dumm, das ist nicht etwas, das MySQL der Fall ist, die leeren Datumswerte eingefügt wird. Ich tue dies in Perl mit einem zweistufigen Verfahren. Laden Sie zunächst alle Daten aus der Abfrage in ein Hash nach Datum organisiert. Dann erstelle ich ein Date :: EzDate Objekt und erhöhe es von Tag, also ...

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

wo Endtermin ist ein weiterer EzDate Objekt oder eine Zeichenfolge, die das Ende des Zeitraums enthält.

EzDate ist momentan nicht auf CPAN, aber man kann wahrscheinlich ein anderes Perl mod finden, das Datum tun vergleicht und ein Datum Inkrementierer bieten.

Sie können eine Datetime Objekt:

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

Was der oben genannte Code tut, ist es das letzte gedruckte Datum in einer gespeicherten hält DateTime Objekt $dt, und wenn das aktuelle Datum ist mehr als ein Tag in die Zukunft, erhöht es $dt um einen Tag (und druckt es eine Linie CSV), bis er das gleiche wie das aktuelle Datum ist.

Auf diese Weise können nicht zusätzliche Tabellen brauchen, und müssen nicht alle holen Ihre Zeilen im Voraus.

Ich hoffe, Sie werden den Rest herauszufinden.

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

Mit

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

Sie werden eine Spalte mit Zahlen von 0 bis max (n3) erhalten * 100 + max (n2) * 10 + max (n1)

hier Da wir max n3 als 3 haben, wird SELECT 399 zurückkehren, plus 0 -.> 400 Datensätze (Termine im Kalender)

Sie können tune Ihre dynamischen Kalender durch sie zu begrenzen, beispielsweise von min (Datum) Sie haben jetzt ().

Da Sie nicht wissen, wo die Lücken sind, und doch wollen Sie alle Werte (vermutlich) ab dem ersten Tag in der Liste zu dem letzten, so etwas wie:

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, das komplizierter erwiesen, als ich dachte, es wäre .. Ich hoffe, es macht Sinn!

Ich denke, die einfachste allgemeine Lösung für das Problem mit der höchsten Anzahl von Zeilen eine Ordinal Tabelle zu erstellen wäre, die Sie benötigen (in Ihrem Fall 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

Als nächstes tun, um eine LEFT JOIN von Ordinal auf Ihre Daten. Hier ist ein einfacher Fall, jeden Tag in der letzten Woche bekommen:

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

Die beiden Dinge, die Sie benötigen würden, um dies zu ändern, sind der Ausgangspunkt und das Intervall. Ich habe SET @var = 'value' Syntax für Klarheit verwendet.

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 ist der endgültige Code in etwa so aussehen würde, wenn Sie die Anzahl der Nachrichten pro Tag in den letzten drei Monaten erhalten wurden Beitritt:

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`

Tipps und Kommentare:

  • Wahrscheinlich der schwierigste Teil Ihrer Abfrage die Anzahl der Tage war die Bestimmung zu verwenden, wenn Ordinal begrenzen. Zum Vergleich: in Daten, die ganzzahlige Sequenz Umwandlung war einfach.
  • Sie können Ordinal für alle Ihre ununterbrochene-Sequenz benötigt verwenden. So stellen Sie sicher, dass es mehr Zeilen als Ihre längste Sequenz enthält.
  • Sie können mehrere Anfragen auf Ordinal für mehrere Sequenzen, zum Beispiel jeden Tag Auflistung (1-5) für die letzten sieben (1-7) Wochen.
  • Sie könnten es schneller von Daten in Ihrer Ordinal Tabelle zu speichern, aber es wäre weniger flexibel. Auf diese Weise brauchen Sie nur ein Ordinal Tisch, egal wie oft Sie es verwenden. Dennoch, wenn die Geschwindigkeit es wert ist, versuchen Sie die INSERT INTO ... SELECT Syntax.

Verwenden Sie einige Perl-Modul Datumsberechnungen, wie empfohlene DateTime- oder Time :: Piece (Kern von 5,10) zu tun. Erhöhen Sie einfach Datum und Druckdatum und 0 bis Datum wird übereinstimmen Strom.

Ich weiß nicht, ob das funktionieren würde, aber wie wäre es, wenn Sie eine neue Tabelle erstellt, die alle möglichen Daten enthalten (das könnte das Problem mit dieser Idee sein, wenn der Bereich von Daten unvorhersehbar ändern wird .. .) und ein LEFT JOIN auf den beiden Tabellen dann tun? Ich denke, es ist eine verrückte Lösung, wenn es eine große Anzahl von möglichen Daten ist, oder keine Möglichkeit, das erste und die letzte Datum vorherzusagen, aber wenn der Bereich von Daten entweder fest oder leicht zu trainieren, dann könnte dies funktionieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top