كيفية إزالة الجزء الزمني من قيمة التاريخ والوقت (SQL Server)؟

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

سؤال

إليك ما أستخدمه:

SELECT CAST(FLOOR(CAST(getdate() as FLOAT)) as DATETIME)

أعتقد أنه قد تكون هناك طريقة أفضل وأكثر أناقة.

متطلبات:

  • يجب أن يتم بأسرع ما يمكن (كلما قل عدد الممثلين، كان ذلك أفضل).
  • النتيجة النهائية يجب أن تكون أ datetime اكتب، وليس سلسلة.
هل كانت مفيدة؟

المحلول

SQL Server 2008 وما فوق

في SQL Server 2008 وما فوق، بالطبع الطريقة الأسرع هي Convert(date, @date).يمكن إرجاع هذا إلى أ datetime أو datetime2 اذا كان ضروري.

ما هو الأفضل حقًا في SQL Server 2005 والأقدم؟

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

التحويلات العائمة ليست دقيقة

أولاً، سأبتعد عن التحويل datetime ل float, لأنه لا يتم التحويل بشكل صحيح.قد تفلت من القيام بعملية إزالة الوقت بدقة، ولكن أعتقد أن استخدامها فكرة سيئة لأنها تخبر المطورين ضمنيًا أن هذه عملية آمنة و ليس.إلق نظرة:

declare @d datetime;
set @d = '2010-09-12 00:00:00.003';
select Convert(datetime, Convert(float, @d));
-- result: 2010-09-12 00:00:00.000 -- oops

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

كما أنها ليست حتى الطريقة الأسرع!

الدليل - اختبار الأداء

إذا كنت تريد إجراء بعض الاختبارات بنفسك لترى كيف تتكامل الطرق المختلفة، فسوف تحتاج إلى برنامج الإعداد هذا لتشغيل الاختبارات بشكل أعمق:

create table AllDay (Tm datetime NOT NULL CONSTRAINT PK_AllDay PRIMARY KEY CLUSTERED);
declare @d datetime;
set @d = DateDiff(Day, 0, GetDate());
insert AllDay select @d;
while @@ROWCOUNT != 0
   insert AllDay
   select * from (
      select Tm =
         DateAdd(ms, (select Max(DateDiff(ms, @d, Tm)) from AllDay) + 3, Tm)
      from AllDay
   ) X
   where Tm < DateAdd(Day, 1, @d);
exec sp_spaceused AllDay;  -- 25,920,000 rows

يرجى ملاحظة أن هذا يؤدي إلى إنشاء جدول بحجم 427.57 ميغابايت في قاعدة بياناتك وسيستغرق تشغيله ما بين 15 إلى 30 دقيقة.إذا كانت قاعدة البيانات الخاصة بك صغيرة وتم تعيينها على نمو بنسبة 10%، فستستغرق وقتًا أطول مما لو كان حجمها كبيرًا بدرجة كافية أولاً.

الآن بالنسبة للبرنامج النصي لاختبار الأداء الفعلي.يرجى ملاحظة أنه من المفيد عدم إعادة الصفوف إلى العميل لأن هذا مكلف للغاية بالنسبة لـ 26 مليون صف وسيخفي اختلافات الأداء بين الطرق.

نتائج الأداء

set statistics time on;
-- (All queries are the same on io: logical reads 54712)
GO
declare
    @dd date,
    @d datetime,
    @di int,
    @df float,
    @dv varchar(10);

-- Round trip back to datetime
select @d = CONVERT(date, Tm) from AllDay; -- CPU time = 21234 ms,  elapsed time = 22301 ms.
select @d = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 23031 ms, elapsed = 24091 ms.
select @d = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23782 ms, elapsed = 24818 ms.
select @d = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 36891 ms, elapsed = 38414 ms.
select @d = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 102984 ms, elapsed = 109897 ms.
select @d = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 103390 ms,  elapsed = 108236 ms.
select @d = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 123375 ms, elapsed = 135179 ms.

-- Only to another type but not back
select @dd = Tm from AllDay; -- CPU time = 19891 ms,  elapsed time = 20937 ms.
select @di = CAST(Tm - 0.50000004 AS int) from AllDay; -- CPU = 21453 ms, elapsed = 23079 ms.
select @di = DATEDIFF(DAY, 0, Tm) from AllDay; -- CPU = 23218 ms, elapsed = 24700 ms
select @df = FLOOR(CAST(Tm as float)) from AllDay; -- CPU = 29312 ms, elapsed = 31101 ms.
select @dv = CONVERT(VARCHAR(8), Tm, 112) from AllDay; -- CPU = 64016 ms, elapsed = 67815 ms.
select @dv = CONVERT(CHAR(8), Tm, 112) from AllDay; -- CPU = 64297 ms,  elapsed = 67987 ms.
select @dv = CONVERT(VARCHAR(10), Tm, 101) from AllDay; -- CPU = 65609 ms, elapsed = 68173 ms.
GO
set statistics time off;

بعض التحليلات المتجولة

بعض الملاحظات حول هذا.أولاً، إذا كنت تقوم فقط بإجراء GROUP BY أو مقارنة، فليست هناك حاجة للتحويل مرة أخرى إلى datetime.لذلك يمكنك توفير بعض وحدة المعالجة المركزية عن طريق تجنب ذلك، إلا إذا كنت بحاجة إلى القيمة النهائية لأغراض العرض.يمكنك حتى التجميع حسب القيمة غير المحولة ووضع التحويل فقط في عبارة SELECT:

select Convert(datetime, DateDiff(dd, 0, Tm))
from (select '2010-09-12 00:00:00.003') X (Tm)
group by DateDiff(dd, 0, Tm)

وانظر أيضًا كيف تستغرق التحويلات الرقمية وقتًا أطول قليلاً للتحويل إليها مرة أخرى datetime, ، لكن ال varchar التحويل يتضاعف تقريبا؟يكشف هذا عن جزء وحدة المعالجة المركزية المخصص لحساب التاريخ في الاستعلامات.هناك أجزاء من استخدام وحدة المعالجة المركزية لا تتضمن حساب التاريخ، ويبدو أن هذا يقترب من 19875 مللي ثانية في الاستعلامات أعلاه.ثم يتطلب التحويل بعض المبلغ الإضافي، لذلك إذا كان هناك تحويلان، فسيتم استخدام هذا المبلغ مرتين تقريبًا.

المزيد من الفحص يكشف ذلك مقارنة بـ Convert(, 112), ، ال Convert(, 101) يحتوي الاستعلام على بعض نفقات وحدة المعالجة المركزية الإضافية (نظرًا لأنه يستخدم ملف varchar؟)، لأن التحويل الثاني يعود إلى date لا يكلف نفس تكلفة التحويل الأولي إلى varchar, ولكن مع Convert(, 112) وهو أقرب إلى نفس التكلفة الأساسية لوحدة المعالجة المركزية البالغة 20000 مللي ثانية.

فيما يلي تلك الحسابات الخاصة بوقت وحدة المعالجة المركزية التي استخدمتها في التحليل أعلاه:

     method   round  single   base
-----------  ------  ------  -----
       date   21324   19891  18458
        int   23031   21453  19875
   datediff   23782   23218  22654
      float   36891   29312  21733
varchar-112  102984   64016  25048
varchar-101  123375   65609   7843
  • دائري هو وقت وحدة المعالجة المركزية لرحلة العودة إلى datetime.

  • أعزب هو وقت وحدة المعالجة المركزية (CPU) لتحويل واحد إلى نوع البيانات البديل (الذي له تأثير جانبي يتمثل في إزالة جزء الوقت).

  • قاعدة هو حساب الطرح من single الفرق بين الدعوتين: single - (round - single).إنه رقم تقريبي يفترض التحويل من وإلى نوع البيانات هذا و datetime هو نفسه تقريبا في أي من الاتجاهين.يبدو أن هذا الافتراض ليس مثاليًا ولكنه قريب لأن القيم كلها قريبة من 20000 مللي ثانية مع استثناء واحد فقط.

شيء آخر مثير للاهتمام هو أن التكلفة الأساسية تساوي تقريبًا التكلفة الفردية Convert(date) الطريقة (والتي يجب أن تكون تكلفتها 0 تقريبًا، حيث يمكن للخادم استخراج جزء اليوم الصحيح داخليًا من أول أربع بايتات من datetime نوع البيانات).

خاتمة

لذا فإن ما يبدو عليه الأمر هو أن الاتجاه الواحد varchar تستغرق طريقة التحويل حوالي 1.8 ميكروثانية والاتجاه الواحد DateDiff تستغرق الطريقة حوالي 0.18 ثانية.أعتمد هذا على وقت "وحدة المعالجة المركزية الأساسية" الأكثر تحفظًا في الاختبار الذي أجريته والذي يبلغ 18458 مللي ثانية إجماليًا لـ 25,920,000 صف، وبالتالي 23218 مللي ثانية / 25920000 = 0.18 ميكروثانية.يبدو التحسن الواضح بمقدار 10 أضعاف كثيرًا، لكنه بصراحة صغير جدًا حتى تتعامل مع مئات الآلاف من الصفوف (617 ألف صف = توفير ثانية واحدة).

حتى في ضوء هذا التحسن المطلق الصغير، في رأيي، فإن DateAdd تفوز الطريقة لأنها أفضل مزيج من الأداء والوضوح.الجواب الذي يتطلب "الرقم السحري" ل 0.50000004 سوف يعض شخص ما في يوم من الأيام (خمسة أصفار أو ستة؟؟؟)، بالإضافة إلى أنه من الصعب فهمه.

ملاحظات إضافية

عندما أحصل على بعض الوقت سأتغير 0.50000004 ل '12:00:00.003' وانظر كيف يحدث ذلك.يتم تحويله إلى نفسه datetime قيمة وأجد أنه من الأسهل بكثير أن نتذكر.

بالنسبة للمهتمين، تم إجراء الاختبارات المذكورة أعلاه على خادم حيث يقوم @@Version بإرجاع ما يلي:

Microsoft SQL Server 2008 (RTM) - 10.0.1600.22 (Intel X86) 9 يوليو 2008 14:43:34 حقوق النشر (c) 1988-2008 Microsoft Corporation Standard Edition على نظام التشغيل Windows NT 5.2 (النسخة 3790:حزمة الخدمة 2)

نصائح أخرى

يحتوي SQL Server 2008 على إصدار جديد تاريخ نوع البيانات وهذا يبسط هذه المشكلة إلى:

SELECT CAST(CAST(GETDATE() AS date) AS datetime)

إيتسيك بن غان حسابات التاريخ والوقت، الجزء الأول (مجلة SQL Server، فبراير 2007) تعرض ثلاث طرق لإجراء مثل هذا التحويل (الأبطأ إلى الأسرع;الفرق بين الطريقة الثانية والثالثة صغير):

SELECT CAST(CONVERT(char(8), GETDATE(), 112) AS datetime)

SELECT DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0)

SELECT CAST(CAST(GETDATE() - 0.50000004 AS int) AS datetime)

الأسلوب الخاص بك (الصب ل يطفو) يقترحه أحد القراء في عدد أبريل من المجلة.ووفقا له، فإنه يتمتع بأداء مماثل لأداء التقنية الثانية المذكورة أعلاه.

لك CAST-FLOOR-CAST يبدو بالفعل أن هذه هي الطريقة المثلى، على الأقل في MS SQL Server 2005.

بعض الحلول الأخرى التي رأيتها تحتوي على تحويل سلسلة، مثل Select Convert(varchar(11), getdate(),101) فيها، وهو أبطأ بعامل 10.

أرجوك حاول:

SELECT CONVERT(VARCHAR(10),[YOUR COLUMN NAME],105) [YOURTABLENAME]

SQL2005:أوصي بالإرسال بدلاً من dateadd.على سبيل المثال،

select cast(DATEDIFF(DAY, 0, datetimefield) as datetime)

متوسط ​​حوالي 10% أسرع في مجموعة البيانات الخاصة بي، من

select DATEADD(DAY, DATEDIFF(DAY, 0, datetimefield), 0)

(وكان الإرسال إلى Smalldatetime أسرع)

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