2008-08-22 15 views
17

Hier ist das Problem, das ich habe: Ich habe eine große Abfrage, die Datumsangaben in der WHERE-Klausel vergleichen muss, um zu sehen, ob zwei Daten am selben Tag sind. Meine derzeitige Lösung, die saugt, besteht darin, die Datumsangaben in eine UDF zu senden, um sie in Mitternacht desselben Tages zu konvertieren, und dann diese Daten auf Gleichheit zu überprüfen. Wenn es um den Abfrageplan geht, ist dies ein Desaster, wie fast alle UDFs in Joins oder Where-Klauseln. Dies ist einer der wenigen Bereiche in meiner Anwendung, in dem ich nicht in der Lage war, die Funktionen zu rooten und dem Abfrageoptimierer etwas zu geben, das er tatsächlich verwenden kann, um den besten Index zu finden.Was ist ein guter Weg, um zu überprüfen, ob zwei Datumsangaben am selben Kalendertag in TSQL sind?

In diesem Fall scheint das Zusammenführen des Funktionscodes zurück in die Abfrage unpraktisch.

Ich denke, ich vermisse etwas Einfaches hier.

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 

Angelegenheiten zu erschweren, Ich trete auf Tabellen Zeitzone das Datum gegen die lokale Zeit zu überprüfen, die für jede Zeile anders sein könnte:

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]

I bin mit @ Todds Vorschlag:

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

Mein Missverständnis darüber, wie datediff funktioniert (am selben Tag des Jahres in aufeinanderfolgenden Jahren ergibt 366, nicht 0, wie ich erwartet hatte) verursachte mir eine Menge Mühe zu verschwenden.

Aber der Abfrageplan hat sich nicht geändert. Ich denke, ich muss mit dem ganzen Ding zurück zum Zeichenbrett gehen.

+0

Bitte beachten Sie Mark Brackett Antwort, die das ** Recht ** ist. Ich weiß, dass deine Frage 2 Jahre alt ist, aber lass uns bitte nicht die falschen Leute führen, die diese Frage besuchen. Todd Tingens Antwort funktioniert, ist aber eine schreckliche Leistung, wie Sie zu der Zeit gefunden haben! – ErikE

+0

Die Antwort von Mark ist für diese Situation korrekt, weil der Optimierer davon abhängig war. Mit dateadd kann ich auf einfache und übersichtliche Weise feststellen, ob zwei Daten am gleichen Kalendertag liegen, was die ursprüngliche Frage war. – Todd

Antwort

51

Das ist viel prägnanter:

where 
    datediff(day, date1, date2) = 0 
+6

Warum hat das 9 Stimmen und erhielt die Antwort? Es ist kurz und gut, und führte das OP auf den falschen Weg. Da das zweite Datum, @ActivityDate, fest ist, können wir die Mathematik auf die rechte Seite verschieben und eine viel bessere Leistung erzielen. – ErikE

+2

Mit @emtucifor abgestimmt. Diese Logik kann von SQL Server nicht angezeigt werden. – DForck42

+0

@ErikE, ich verstehe nicht ganz, was du meinst, hast du ein Code-Snippet? – Joyce

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

dies wird für Sie Zeitkomponente von einem Datum entfernen:

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

Sie haben ziemlich viel mit der linken Seite des halten, wo Klausel sauber. Also, normalerweise würden Sie so etwas wie:

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

(Einige Leute bevorzugen DATEADD (d, 1, @activityDateMidnight) statt - aber es ist die gleiche Sache).

Die TimeZone-Tabelle kompliziert jedoch ein wenig. Es ist ein wenig unklar von Ihrem Snippet, aber es sieht aus wie t.DeDateInTable ist in GMT mit einer Zeitzone Kennung, und dass Sie dann den Offset hinzufügen, um gegen @activityDateMidnight zu vergleichen - die in Ortszeit ist. Ich bin mir aber nicht sicher, was ds.LocalTimeZone ist.

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

0

Sie die Qual der Wahl in Bezug auf die Optionen durcheinander. Wenn Sie Sybase oder SQL Server 2008 verwenden, können Sie Variablen vom Typ date erstellen und ihnen Ihre datetime-Werte zuweisen. Die Datenbank-Engine wird die Zeit für Sie los.Hier ist ein schneller und schmutziger Test zu veranschaulichen (-Code ist in Sybase Dialekt):

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

Für SQL 2005 und früher, was Sie tun können, ist das Datum zu einem varchar in einem Format konvertieren, das nicht die Zeitkomponente hat. Zum Beispiel gibt folgende Formel 2008.08.22

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

Der 102 Teil (online Bücher für Sie alle verfügbaren Formate Liste), um die Formatierung gibt

Also, was Sie tun können, ist eine Funktion schreiben, die eine nimmt datetime und extrahiert das Datumselement und verwirft die Zeit. Wie so:

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

können Sie dann verwenden, um die Funktion für Begleiter

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

würde ich die dayofyear Funktion der Datumsteil verwenden:


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

Siehe Datumsteil Referenz here.

0

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

2

Eric Z Beard:

speichere ich tun alle Termine in GMT. Hier ist der Anwendungsfall: Etwas passierte um 11:00 Uhr EST am 1., das ist der 2. GMT. Ich möchte Aktivität für den 1. sehen, und ich bin in EST, also werde ich die 11 PM Aktivität sehen wollen. Wenn ich gerade GMT-Rohdaten verglich, würde ich Dinge vermissen. Jede Zeile im Bericht kann eine Aktivität aus einer anderen Zeitzone darstellen.

Richtig, aber wenn Sie sagen, Sie für 1. Januar 2008 EST in Tätigkeit interessiert sind:

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

Sie müssen nur konvertieren, dass GMT (ich ignoriere die Komplikation des Abfragens für den Tag vor 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 

, die Sie ‚2008.01.01 04.00‘ geben sollte. Dann können Sie nur 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 fragliche Ereignis ist mit einem GMT von Eventtime 2008.01.02 03.00 gespeichert. Sie brauchen die TimeZone nicht einmal in der EventTable (zumindest zu diesem Zweck).

Da EventTime nicht in einer Funktion ist, ist dies ein gerader Index-Scan - was ziemlich effizient sein sollte. Machen Sie EventTime zu Ihrem Clustered-Index, und er wird fliegen. ;)

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

1

Eric Z Beard:

die Aktivität Datum sollte die lokale Zeitzone, um anzuzeigen, aber keine spezifischen

Ok - zurück zum Zeichenbrett. Versuchen Sie Folgendes:

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

, die das @ ActivityDate auf lokale Zeit übersetzen und dagegen vergleichen. Das ist die beste Chance für die Verwendung eines Indexes, obwohl ich nicht sicher bin, ob es funktioniert. Sie sollten es versuchen und den Abfrageplan überprüfen.

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

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

, die auf jeden Fall den Index verwenden würde - wenn Sie einen leichten Overhead auf INSERT und UPDATE haben dann.

Verwandte Themen