ما هي الطريقة الجيدة للتحقق مما إذا كان هناك تاريخان في نفس يوم التقويم في TSQL؟

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

سؤال

هذه هي المشكلة التي أواجهها:لدي استعلام كبير يحتاج إلى مقارنة أوقات التاريخ في جملة المكان لمعرفة ما إذا كان هناك تاريخان في نفس اليوم.الحل الحالي الذي أقترحه، وهو أمر سيئ، هو إرسال أوقات التاريخ إلى UDF لتحويلها إلى منتصف ليل نفس اليوم، ثم التحقق من تلك التواريخ للتأكد من تساويها.عندما يتعلق الأمر بخطة الاستعلام، فهذه كارثة، كما هو الحال مع جميع UDFs تقريبًا في الصلات أو عبارات حيث.هذا هو أحد الأماكن الوحيدة في تطبيقي التي لم أتمكن فيها من استئصال الوظائف وإعطاء مُحسِّن الاستعلام شيئًا يمكنه استخدامه بالفعل لتحديد أفضل فهرس.

في هذه الحالة، يبدو دمج رمز الوظيفة مرة أخرى في الاستعلام غير عملي.

أعتقد أنني أفتقد شيئًا بسيطًا هنا.

ها هي الوظيفة للرجوع إليها.

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

ولتعقيد الأمور، أقوم بالانضمام إلى جداول المناطق الزمنية للتحقق من التاريخ مقابل التوقيت المحلي، والذي قد يكون مختلفًا لكل صف:

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

[يحرر]

أنا أدمج اقتراح @ Todd:

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

إن مفهومي الخاطئ حول كيفية عمل datediff (ينتج في نفس اليوم من العام في سنوات متتالية 366، وليس 0 كما توقعت) جعلني أضيع الكثير من الجهد.

لكن خطة الاستعلام لم تتغير.أعتقد أنني بحاجة للعودة إلى لوحة الرسم بكل شيء.

هل كانت مفيدة؟

المحلول

هذا أكثر إيجازًا:

where 
  datediff(day, date1, date2) = 0

نصائح أخرى

يجب عليك إلى حد كبير الحفاظ على الجانب الأيسر من جملة المكان نظيفة.لذا، عادةً، ستفعل شيئًا مثل:

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

(يفضل بعض الأشخاص DATEADD(d, 1,@activityDateMidnight) بدلاً من ذلك - ولكنه نفس الشيء).

لكن جدول TimeZone يعقد الأمر قليلاً.الأمر غير واضح بعض الشيء من المقتطف الخاص بك، ولكن يبدو أن t.TheDateInTable بتوقيت جرينتش مع معرف المنطقة الزمنية، وأنك تقوم بعد ذلك بإضافة الإزاحة للمقارنة مع @activityDateMidnight - وهو بالتوقيت المحلي.لست متأكدًا من ماهية ds.LocalTimeZone.

إذا كان الأمر كذلك، فأنت بحاجة إلى تحويل @activityDateMidnight إلى توقيت جرينتش بدلاً من ذلك.

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

تأكد من القراءة فقط في قاعدة البيانات يمكنك الحصول على تحسين بنسبة 1000% عن طريق تغيير بضعة أسطر من التعليمات البرمجية بحيث تكون متأكدًا من أن المُحسِّن يمكنه استخدام الفهرس بشكل فعال عند العبث بالتواريخ

إريك زد بيرد:

أقوم بتخزين جميع التواريخ بتوقيت جرينتش.إليك حالة الاستخدام:حدث شيء ما في الساعة 11:00 مساءً بتوقيت شرق الولايات المتحدة في اليوم الأول، وهو الثاني بتوقيت جرينتش.أريد أن أرى النشاط لليوم الأول، وأنا في التوقيت الشرقي الشرقي لذلك أريد أن أرى نشاط الساعة 11 مساءً.إذا قمت فقط بمقارنة أوقات توقيت جرينتش الأولية، فسوف أفتقد الأشياء.يمكن أن يمثل كل صف في التقرير نشاطًا من منطقة زمنية مختلفة.

صحيح، ولكن عندما تقول أنك مهتم بالنشاط ليوم 1 كانون الثاني (يناير) 2008 بتوقيت شرق الولايات المتحدة:

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

تحتاج فقط إلى تحويل الذي - التي إلى توقيت جرينتش (أنا أتجاهل تعقيدات الاستعلام عن اليوم السابق للذهاب إلى التوقيت الشرقي للولايات المتحدة، أو العكس):

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

والتي ينبغي أن تعطيك "1/1/2008 4:00 صباحًا".وبعد ذلك، يمكنك فقط البحث بتوقيت جرينتش:

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

يتم تخزين الحدث المعني مع توقيت الحدث بتوقيت جرينتش بتاريخ 2/1/2008 الساعة 3:00 صباحًا.لا تحتاج حتى إلى المنطقة الزمنية في EventTable (لهذا الغرض، على الأقل).

نظرًا لأن EventTime ليس في وظيفة، يعد هذا فحصًا مباشرًا للفهرس - والذي يجب أن يكون فعالاً جدًا.اجعل EventTime فهرسك المجمع، وسوف يطير.;)

شخصيًا، سأطلب من التطبيق تحويل وقت البحث إلى توقيت جرينتش قبل تشغيل الاستعلام.

سيؤدي هذا إلى إزالة مكون الوقت من التاريخ بالنسبة لك:

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

أنت مدلل للاختيار من حيث الخيارات هنا.إذا كنت تستخدم Sybase أو SQL Server 2008، فيمكنك إنشاء متغيرات من النوع date وتعيين قيم التاريخ والوقت لها.يتخلص محرك قاعدة البيانات من الوقت المناسب لك.إليك اختبارًا سريعًا وقذرًا للتوضيح (الرمز موجود بلغة Sybase):

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'

بالنسبة لـ SQL 2005 والإصدارات الأقدم، ما يمكنك فعله هو تحويل التاريخ إلى varchar بتنسيق لا يحتوي على مكون الوقت.على سبيل المثال العوائد التالية 2008.08.22

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

يحدد الجزء 102 التنسيق (يمكن للكتب عبر الإنترنت أن تدرج لك جميع التنسيقات المتاحة)

لذا، ما يمكنك فعله هو كتابة دالة تأخذ وقتًا وتاريخًا وتستخرج عنصر التاريخ وتتجاهل الوقت.مثل ذلك:

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

يمكنك بعد ذلك استخدام الوظيفة للرفاق

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

إريك زد بيرد:

المقصود من تاريخ النشاط هو الإشارة إلى المنطقة الزمنية المحلية، وليس منطقة زمنية محددة

حسنًا - عد إلى لوحة الرسم.جرب هذا:

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

والذي سيترجم @ActivityDate إلى التوقيت المحلي، ويقارن به.هذه هي أفضل فرصة لك لاستخدام فهرس، على الرغم من أنني لست متأكدًا من نجاحه - يجب عليك تجربته والتحقق من خطة الاستعلام.

سيكون الخيار التالي هو طريقة عرض مفهرسة، مع TimeINeedToCheck مفهرسة ومحسوبة بالتوقيت المحلي.ثم عليك فقط العودة إلى:

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

والذي سيستخدم الفهرس بالتأكيد - على الرغم من أن لديك حملًا طفيفًا على INSERT وUPDATE بعد ذلك.

سأستخدم وظيفة dayofyear الخاصة بـ datepart:


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

راجع مرجع datepart هنا.

فيما يتعلق بالمناطق الزمنية، هناك سبب آخر لتخزين جميع التواريخ في منطقة زمنية واحدة (يفضل التوقيت العالمي المنسق).على أي حال، أعتقد أن الإجابات باستخدام datediff وdatepart ووظائف التاريخ المختلفة المضمنة هي أفضل رهان لك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top