Was ist eine gute Möglichkeit, in TSQL zu überprüfen, ob zwei Datums- und Uhrzeitangaben am selben Kalendertag liegen?

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

Frage

Hier ist das Problem, das ich habe:Ich habe eine große Abfrage, bei der Datumsangaben in der Where-Klausel verglichen werden müssen, um festzustellen, ob zwei Datumsangaben am selben Tag liegen.Meine aktuelle Lösung, die scheiße ist, besteht darin, die Datumsangaben in eine UDF zu senden, um sie in Mitternacht desselben Tages umzuwandeln, und diese Daten dann auf Gleichheit zu überprüfen.Was den Abfrageplan angeht, ist dies eine Katastrophe, ebenso wie fast alle UDFs in Joins oder Where-Klauseln.Dies ist eine der wenigen Stellen in meiner Anwendung, an denen ich die Funktionen nicht aussortieren und dem Abfrageoptimierer etwas geben konnte, mit dem er tatsächlich den besten Index finden kann.

In diesem Fall erscheint es unpraktisch, den Funktionscode wieder in die Abfrage einzubinden.

Ich glaube, mir fehlt hier etwas Einfaches.

Hier ist die Funktion als Referenz.

if not exists (select * from dbo.sysobjects 
              where id = object_id(N'dbo.f_MakeDate') and               
              type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
  exec('create function dbo.f_MakeDate() returns int as 
         begin declare @retval int return @retval end')
go

alter function dbo.f_MakeDate
(
    @Day datetime, 
    @Hour int, 
    @Minute int
)
returns datetime
as

/*

Creates a datetime using the year-month-day portion of @Day, and the 
@Hour and @Minute provided

*/

begin

declare @retval datetime
set @retval = cast(
    cast(datepart(m, @Day) as varchar(2)) + 
    '/' + 
    cast(datepart(d, @Day) as varchar(2)) + 
    '/' + 
    cast(datepart(yyyy, @Day) as varchar(4)) + 
    ' ' + 
    cast(@Hour as varchar(2)) + 
    ':' + 
    cast(@Minute as varchar(2)) as datetime)
return @retval
end

go

Um die Sache noch komplizierter zu machen, füge ich Zeitzonentabellen hinzu, um das Datum mit der Ortszeit zu vergleichen, die für jede Zeile unterschiedlich sein kann:

where 
dbo.f_MakeDate(dateadd(hh, tz.Offset + 
    case when ds.LocalTimeZone is not null 
    then 1 else 0 end, t.TheDateINeedToCheck), 0, 0) = @activityDateMidnight

[Bearbeiten]

Ich beziehe den Vorschlag von @Todd ein:

where datediff(day, dateadd(hh, tz.Offset + 
    case when ds.LocalTimeZone is not null 
    then 1 else 0 end, t.TheDateINeedToCheck), @ActivityDate) = 0

Meine falsche Vorstellung davon, wie datediff funktioniert (derselbe Tag im Jahr in aufeinanderfolgenden Jahren ergibt 366, nicht 0, wie ich erwartet hatte), hat dazu geführt, dass ich viel Mühe verschwendet habe.

Der Abfrageplan hat sich jedoch nicht geändert.Ich denke, ich muss mit der ganzen Sache noch einmal ans Zeichenbrett gehen.

War es hilfreich?

Lösung

Das ist viel prägnanter:

where 
  datediff(day, date1, date2) = 0

Andere Tipps

Sie müssen die linke Seite Ihrer where-Klausel weitgehend sauber halten.Normalerweise würden Sie also so etwas tun:

WHERE MyDateTime >= @activityDateMidnight 
      AND MyDateTime < (@activityDateMidnight + 1)

(Einige Leute bevorzugen stattdessen DATEADD(d, 1, @activityDateMidnight) – aber es ist dasselbe.)

Die TimeZone-Tabelle macht die Sache allerdings etwas komplizierter.Aus Ihrem Snippet geht das etwas unklar hervor, aber es sieht so aus, als ob t.TheDateInTable in GMT mit einer Zeitzonenkennung ist und Sie dann den Offset hinzufügen, um ihn mit @activityDateMidnight zu vergleichen – was in der Ortszeit angegeben ist.Ich bin mir jedoch nicht sicher, was ds.LocalTimeZone ist.

Wenn das der Fall ist, müssen Sie stattdessen @activityDateMidnight in GMT integrieren.

where
year(date1) = year(date2)
and month(date1) = month(date2)
and day(date1) = day(date2)

Unbedingt lesen Nur in einer Datenbank können Sie durch die Änderung einiger Codezeilen eine Verbesserung von über 1000 % erzielen So können Sie sicher sein, dass der Optimierer den Index beim Umgang mit Datumsangaben effektiv nutzen kann

Eric Z Bart:

Ich speichere alle Daten in GMT.Hier ist der Anwendungsfall:Am 1., dem 2. GMT, um 23:00 Uhr EST ist etwas passiert.Ich möchte die Aktivitäten für den 1. sehen und befinde mich in EST, daher möchte ich die Aktivitäten um 23:00 Uhr sehen.Wenn ich nur die reinen GMT-Datumszeiten vergleichen würde, würde mir etwas entgehen.Jede Zeile im Bericht kann eine Aktivität aus einer anderen Zeitzone darstellen.

Richtig, aber wenn Sie sagen, dass Sie an Aktivitäten für den 1. Januar 2008 EST interessiert sind:

SELECT @activityDateMidnight = '1/1/2008', @activityDateTZ = 'EST'

Sie müssen nur konvertieren Das zu GMT (ich ignoriere die Komplikation der Abfrage für den Tag, bevor EST zu EDT geht, oder umgekehrt):

Table: TimeZone
Fields: TimeZone, Offset
Values: EST, -4

--Multiply by -1, since we're converting EST to GMT.
--Offsets are to go from GMT to EST.
SELECT @activityGmtBegin = DATEADD(hh, Offset * -1, @activityDateMidnight)
FROM TimeZone
WHERE TimeZone = @activityDateTZ

Das sollte Ihnen „01.01.2008 04:00 Uhr“ ergeben.Dann können Sie einfach in GMT suchen:

SELECT * FROM EventTable
WHERE 
   EventTime >= @activityGmtBegin --1/1/2008 4:00 AM
   AND EventTime < (@activityGmtBegin + 1) --1/2/2008 4:00 AM

Das betreffende Ereignis wird mit einer GMT-Ereigniszeit vom 02.01.2008 03:00 Uhr gespeichert.Sie benötigen nicht einmal die TimeZone in der EventTable (zumindest für diesen Zweck).

Da EventTime nicht in einer Funktion enthalten ist, handelt es sich um einen direkten Indexscan – was ziemlich effizient sein sollte.Machen Sie EventTime zu Ihrem Clustered-Index, und es wird fliegen.;)

Persönlich würde ich die App die Suchzeit in GMT umrechnen lassen, bevor die Abfrage ausgeführt wird.

Dadurch wird die Zeitkomponente für Sie aus einem Datum entfernt:

select dateadd(d, datediff(d, 0, current_timestamp), 0)

Was die Optionen angeht, haben Sie hier die Qual der Wahl.Wenn Sie Sybase oder SQL Server 2008 verwenden, können Sie Variablen vom Typ Datum erstellen und ihnen Ihre Datums-/Uhrzeitwerte zuweisen.Die Datenbank-Engine nimmt Ihnen die Zeit ab.Hier ist ein kurzer und schmutziger Test zur Veranschaulichung (Code ist im Sybase-Dialekt):

declare @date1 date
declare @date2 date
set @date1='2008-1-1 10:00'
set @date2='2008-1-1 22:00'
if @date1=@date2
    print 'Equal'
else
    print 'Not equal'

Für SQL 2005 und früher können Sie das Datum in ein Varchar-Format in einem Format konvertieren, das keine Zeitkomponente enthält.Das Folgende gibt beispielsweise den 22.08.2008 zurück

select convert(varchar,'2008-08-22 18:11:14.133',102)

Der 102-Teil gibt die Formatierung an (Online-Bücher können für Sie alle verfügbaren Formate auflisten)

Was Sie also tun können, ist, eine Funktion zu schreiben, die eine Datums-/Uhrzeitangabe übernimmt, das Datumselement extrahiert und die Uhrzeit verwirft.Etwa so:

create function MakeDate (@InputDate datetime) returns datetime as
begin
    return cast(convert(varchar,@InputDate,102) as datetime);
end

Anschließend können Sie die Funktion für Begleiter nutzen

Select * from Orders where dbo.MakeDate(OrderDate) = dbo.MakeDate(DeliveryDate)

Eric Z Bart:

Das Aktivitätsdatum soll die lokale Zeitzone angeben, jedoch keine bestimmte

Okay – zurück zum Zeichenbrett.Versuche dies:

where t.TheDateINeedToCheck BETWEEN (
    dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, @ActivityDate)
    AND
    dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, (@ActivityDate + 1))
)

Dadurch wird das @ActivityDate in die Ortszeit übersetzt und damit verglichen.Das ist Ihre beste Chance, einen Index zu verwenden, obwohl ich nicht sicher bin, ob er funktioniert. Sie sollten es versuchen und den Abfrageplan überprüfen.

Die nächste Option wäre eine indizierte Ansicht mit einem indizierten, berechneten TimeINeedToCheck in Ortszeit.Dann kehren Sie einfach zurück zu:

where v.TheLocalDateINeedToCheck BETWEEN @ActivityDate AND (@ActivityDate + 1)

was definitiv den Index verwenden würde - obwohl Sie dann einen leichten Mehraufwand für INSERT und UPDATE haben.

Ich würde die dayofyear-Funktion von datepart verwenden:


Select *
from mytable
where datepart(dy,date1) = datepart(dy,date2)
and
year(date1) = year(date2) --assuming you want the same year too

Siehe die Datepart-Referenz Hier.

In Bezug auf Zeitzonen gibt es noch einen weiteren Grund, alle Daten in einer einzigen Zeitzone (vorzugsweise UTC) zu speichern.Wie auch immer, ich denke, die Antworten mit datediff, datepart und den verschiedenen integrierten Datumsfunktionen sind die beste Wahl.

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