Konvertieren Sie das Datum in der Datumslistenbedingung in eine Liste der Datumsbereiche


7

Ich möchte nach allen Datensätzen suchen, die an bestimmten Daten auftreten.

SELECT *
FROM table1
WHERE date(column) in ($date1, $date2, ...);

Wie viele von Ihnen jedoch wissen, verträgt sich diese Art von Vergleich nicht mit Indizes. Ich habe mich also gefragt, ob es eine einfache Möglichkeit gibt, diese Abfrage ohne großen Aufwand in etwas im Stil der folgenden Abfrage zu konvertieren (dh: kein externes Tool verwenden).

SELECT *
FROM table1
WHERE (column >= $date1 AND column < $date1 + interval 1 day)
   OR (column >= $date2 AND column < $date2 + interval 1 day)
   ...

Der Optimierer kann also weiterhin die Indizes verwenden. (Ich benutze MySQL, aber ANSI SQL wäre großartig)


Warum wird Ihr vorgeschlagener Ansatz Ihrer Meinung nach besser mit Indizes auskommen?
Mustaccio

1
Weil Indizes unterbrochen werden, wenn Sie die Datumsfunktion auf Zeitstempel- oder Datums- / Uhrzeitfelder anwenden.
Msemelman

In der ersten Abfrage Ihrer Frage verwenden Sie die date()Funktion (anscheinend zum Konvertieren columnin den dateDatentyp), während Sie dies in der zweiten Abfrage nicht tun, und dann sagen Sie, dass die Verwendung von date()"Indizes bricht". Warum verwenden Sie es überhaupt, wenn es (wie aus der zweiten Abfrage hervorgeht) nicht erforderlich ist?
Mustaccio

Antworten:


7

VORSCHLAG # 1

SELECT A.* FROM table1 A INNER JOIN
(
    SELECT '2015-03-01' dtcolumn
    UNION SELECT '2015-03-15'
    UNION SELECT '2015-04-01'
    UNION SELECT '2015-04-15'
    UNION SELECT '2015-05-01'
) B ON
A.dtcolumn >= B.dtcolumn
AND A.dtcolumn < B.dtcolumn + INTERVAL 1 DAY;

VORSCHLAG # 2

SELECT * FROM table1 WHERE
(column >= '2015-03-01' AND
column < '2015-03-01' + INTERVAL 1 DAY)
UNION
SELECT * FROM table1 WHERE
(column >= '2015-03-15' AND
column < '2015-03-15' + INTERVAL 1 DAY)
UNION
SELECT * FROM table1 WHERE
(column >= '2015-04-01' AND
column < '2015-04-01' + INTERVAL 1 DAY)
UNION
SELECT * FROM table1 WHERE
(column >= '2015-04-15' AND
column < '2015-04-15' + INTERVAL 1 DAY)
UNION
SELECT * FROM table1 WHERE
(column >= '2015-05-01' AND
column < '2015-05-01' + INTERVAL 1 DAY);

Ich bevorzuge Vorschlag Nr. 1. Ich denke es ist klarer.
Msemelman

Und "wartbarer", wenn Sie dies als wartbare Abfrage bezeichnen können.
Msemelman

1

Versuchen Sie so etwas: (Ich habe dies unter Oracle gemacht, es sollte meistens woanders funktionieren. Die WITH-Klauseln dienen hauptsächlich dazu, nur Beispieldaten zu fälschen. Also nicht unbedingt notwendig.)

  with w_date_list as (  -- just some sample input dates - these are from your IN list (note that you want to re-org them as a "table" not an IN list - there's ways of doing that if you need help with that step
        select to_date('01-apr-2015','dd-mon-yyyy') cdate from dual union all
        select to_date('02-apr-2015','dd-mon-yyyy') cdate from dual union all
        select to_date('03-apr-2015','dd-mon-yyyy') cdate from dual union all
        select to_date('04-apr-2015','dd-mon-yyyy') cdate from dual union all
        select to_date('05-apr-2015','dd-mon-yyyy') cdate from dual
        ),
        w_date_rng as (      -- re-organize them into ranges using LEAD analytic function
           select cdate start_date, 
                  nvl(lead(cdate) over (order by cdate), cdate + 1 )  end_date  -- last one, just default to 1 day
             from w_date_list
           )
  select *
    from (select to_date('01-jan-2015 03:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('01-mar-2015 03:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('01-apr-2015 03:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('01-apr-2015 10:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('01-apr-2015 13:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('02-apr-2015 03:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('02-apr-2015 21:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('04-apr-2015 03:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('04-apr-2015 15:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('04-apr-2015 15:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('04-apr-2015 15:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('05-apr-2015 08:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual union all
          select to_date('05-apr-2015 16:14:46','dd-mon-yyyy hh24:mi:ss') yourdate from dual
           ) table1   ,  -- this just some fake data for your "table1" table.
         w_date_rng   wd
   where table1.yourdate between wd.start_date and wd.end_date  -- join the two with range ... it'll use an index on "yourdate" if it exists
  /

Ergebnisse:

  YOURDATE             START_DATE           END_DATE
  -------------------- -------------------- --------------------
  01-apr-2015 03:14:46 01-apr-2015 00:00:00 02-apr-2015 00:00:00
  01-apr-2015 10:14:46 01-apr-2015 00:00:00 02-apr-2015 00:00:00
  01-apr-2015 13:14:46 01-apr-2015 00:00:00 02-apr-2015 00:00:00
  02-apr-2015 03:14:46 02-apr-2015 00:00:00 03-apr-2015 00:00:00
  02-apr-2015 21:14:46 02-apr-2015 00:00:00 03-apr-2015 00:00:00
  04-apr-2015 03:14:46 04-apr-2015 00:00:00 05-apr-2015 00:00:00
  04-apr-2015 15:14:46 04-apr-2015 00:00:00 05-apr-2015 00:00:00
  04-apr-2015 15:14:46 04-apr-2015 00:00:00 05-apr-2015 00:00:00
  04-apr-2015 15:14:46 04-apr-2015 00:00:00 05-apr-2015 00:00:00
  05-apr-2015 08:14:46 05-apr-2015 00:00:00 06-apr-2015 00:00:00
  05-apr-2015 16:14:46 05-apr-2015 00:00:00 06-apr-2015 00:00:00

  11 rows selected.

Dies könnte "verallgemeinert" werden als:

select *
 from table1   ,
      ( select cdate start_date, 
               nvl(lead(cdate) over (order by cdate), cdate + 1 )  end_date 
          from (  select to_date('01-apr-2015','dd-mon-yyyy') cdate from dual union all
                  select to_date('02-apr-2015','dd-mon-yyyy') cdate from dual union all
                  select to_date('03-apr-2015','dd-mon-yyyy') cdate from dual union all
                  select to_date('04-apr-2015','dd-mon-yyyy') cdate from dual union all
                  select to_date('05-apr-2015','dd-mon-yyyy') cdate from dual
                  )  w_date_list
        )   wd
where table1.yourdate between wd.start_date and wd.end_date  
/

Das sollte auf jeder Datenbank funktionieren ... MYSQL, Oracle, was auch immer.

Sie müssen nur den Datumslistenbereich eingeben - es ist am besten, ihn als eine andere Tabelle oder so einzugeben ...

Ich könnte zeigen, wie es geht Oracle, aber wahrscheinlich nicht besonders nützlich für Sie :) Könnte eine andere Frage dafür brauchen, wenn nötig.


Vielen Dank an @ypercube, dass diese Antwort nicht breit genug ist. Zu beachten ist, dass mysql die Anweisung "with" nicht unterstützt (obwohl es sich um SQL-99 stackoverflow.com/questions/324935/mysql-with-clause handelt ), sodass die Verwendung einer Unterauswahl erforderlich wäre.
Msemelman

Nun, ich habe die WITH-Anweisung hauptsächlich als Platzhalter für Daten verwendet, aber sicher werde ich das aktualisieren ..;)
Ditto

Mein Kommentar war nicht (nur) über with. nvl()und to_date()sind proprietäre Oracle-Funktionen. withund lead()und oversind Standard-SQL, aber nicht in MySQL implementiert. Diese Antwort mag für Oracle in Ordnung sein, erfordert jedoch erhebliche Änderungen und Anstrengungen, damit sie in MySQL funktioniert. Nur dualwürde so funktionieren wie es ist (obwohl es eine andere nicht standardmäßige Funktion ist.)
ypercubeᵀᴹ

Abgesehen davon betweenist das einfach falsch. Das OP wurde korrekt verwendet >=und <in der Frage gibt es keinen Grund, es zu ändern. Und ich verstehe nicht, warum wir lead()sowieso verwenden müssen. Es scheint zu ändern, was das OP zu etwas anderem will.
Ypercubeᵀᴹ

0

Ich stimme dem zweiten Vorschlag von RolandoMySQLDBA zu, dass die Suche nach den Ergebnissen zu schnell ist, UNIONanstatt sie ORzu verwenden. Und wir wissen auch:

Das Standardverhalten für UNIONist , dass doppelte Zeilen werden entfernt aus dem Ergebnis. Das optionale DISTINCTSchlüsselwort hat keine andere Auswirkung als die Standardeinstellung, da es auch das Entfernen doppelter Zeilen angibt. Mit dem optionalen ALLSchlüsselwort wird keine doppelte Zeile entfernt, und das Ergebnis enthält alle übereinstimmenden Zeilen aus allen SELECTAnweisungen.

Und als Ihre Anforderung denke ich, dass Ihre Daten in einer separaten Reihenfolge vorliegen, in der keine doppelte Zeile erstellt wird. Um das Entfernen doppelter Zeilen zu vermeiden , die eine versteckte Reihenfolge von und usw. enthalten, empfehle ich Ihnen, UNION ALLanstelle von Folgendes zu verwenden UNION: so was:

SELECT * FROM table1 WHERE (column >= $date1 AND column < $date1 + interval 1 day)
UNION ALL
SELECT * FROM table1 WHERE (column >= $date2 AND column < $date2 + interval 1 day)
UNION ALL
...
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.