بتبسيط (التعرج) T-SQL العبارات. أي تحسن ممكن؟

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

  •  11-09-2019
  •  | 
  •  

سؤال

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

select count(callid) ,
case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end as duration
from callmetatbl
where programid = 1001 and callDuration > 0
group by case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
end

تحرير: يعني حقا أن أسأل كيفية الحصول على مصدر حالة واحدة، ولكن تعديلات الحالة مرحب بها على أي حال (على الرغم من أنه أقل فائدة لأن الفواصل الزمنية سيتم تعديلها وقد تم إنشاؤها تلقائيا).

كما تم النظر فيها من قبل بعض الناس، فإن الطلب هو بالفعل تعويم، لذا فإن بعض الحلول المدرجة غير صالحة لحالة استخدامي، من خلال ترك القيم من الفواصل الزمنية.

الدروس:

  • ابحث عن أنماط في تعبير الحالة لتقليلها إن أمكن وتجدها

     case
        when callDuration > 0 AND callDuration < 30 then 1
        when callDuration > 600 then 12
        else floor(callDuration/60) + 2  end
     end as duration
    
  • استخدم طرق عرض مضمنة للحصول على مصدر واحد للحالة

    select count(d.callid), d.duration
    from (   
       select callid
            , case
               when callDuration > 0 AND callDuration < 30 then 1
               when callDuration > 600 then 12
               else floor(callDuration/60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
    group by d.duration
    
  • أو استخدام تعبيرات الجدول المشترك

       with duration_case as (
          select callid ,
          case
            when callDuration > 0 AND callDuration < 30 then 1
            when callDuration > 600 then 12
            else floor(callDuration/60) + 2  end
          end as duration
       from callmetatbl
       where programid = 1001 and callDuration > 0 )
        select count(callid), duration
        from duration_case
        group by duration
    
  • أو استخدم وظيفة محددة للمستخدم (لا مثال حتى الآن :-))

  • أو استخدم طاولة بحث وانضمام

    DECLARE @t TABLE(durationFrom float, durationTo float, result INT)
    --populate table with values so the query works
    select count(callid) , COALESCE(t.result, 12)
    from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom 
    AND callDuration < t.durationTo 
    where programid = 1001 and callDuration > 0
    

شكرا للجميع، وأواجه صعوبة في اختيار إجابة مقبولة، حيث غطت العديد من أجزاء مختلفة من السؤال (وكنت هناك اعتقادها بأنها كانت سؤالا بسيطا مع إجابة واضحة :-)، آسف على الارتباك).

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

المحلول

س: كيفية الحصول على اسم مستعار لاستخدامه في جملة المجموعة

نهج واحد هو استخدام عرض مضمون. [عدل] الإجابة من Remus Rusanu (+1!) يعطي مثالا على تعبير طاولة مشترك لإنجاز نفس الشيء. [/تعديل

يحصل لك طريقة العرض المضمنة على "اسم مستعار" بسيط للتعبير المعقدة الذي يمكنك الرجوع إليه بعد ذلك في جملة جملة في استعلام خارجي:

select count(d.callid)
     , d.duration
  from (select callid
             , case
               when callDuration >= 600 then 12
               when callDuration >= 540 then 11
               when callDuration >= 480 then 10
               when callDuration >= 420 then 9
               when callDuration >= 360 then 8
               when callDuration >= 300 then 7
               when callDuration >= 240 then 6
               when callDuration >= 180 then 5
               when callDuration >= 120 then 4
               when callDuration >=  60 then 3
               when callDuration >=  30 then 2
               when callDuration >    0 then 1
               --else null
               end as duration
             from callmetatbl
            where programid = 1001
              and callDuration > 0
       ) d
group by d.duration

دعونا فك ذلك.

  • يسمى الاستعلام الداخلي (المسافة البادئة) و عرض مضمن (لقد أعطيناها اسم مستعار d)
  • في الاستعلام الخارجي، يمكننا الرجوع إلى الاسم المستعار duration من d

يجب أن تكون كافية للإجابة على سؤالك. إذا كنت تبحث عن تعبير بديل مكافئ، فإنه من tkblues. (+1 !) هل الإجابة الصحيحة (تعمل على الحدود وللغير الأعداد الصحيحة.)

مع التعبير البدائل من Tekblues (+1!):

select count(d.callid)
     , d.duration
  from (select callid
             , case 
               when callduration >=30 and callduration<600
                    then floor(callduration/60)+2
               when callduration>0 and callduration< 30
                    then 1 
               when callduration>=600
                    then 12
               end as duration
          from callmetatbl
         where programid = 1001
           and callDuration > 0
       ) d
 group by d.duration

(يجب أن يكون هذا كافيا للإجابة على سؤالك.)


تحديث:] عينة وظيفة تعريف المستخدم (بديل للتعبير الفضائي)

CREATE FUNCTION [dev].[udf_duration](@cd FLOAT)
RETURNS SMALLINT
AS
BEGIN
  DECLARE @bucket SMALLINT
  SET @bucket = 
  CASE
  WHEN @cd >= 600 THEN 12
  WHEN @cd >= 540 THEN 11
  WHEN @cd >= 480 THEN 10
  WHEN @cd >= 420 THEN 9
  WHEN @cd >= 360 THEN 8
  WHEN @cd >= 300 THEN 7
  WHEN @cd >= 240 THEN 6
  WHEN @cd >= 180 THEN 5
  WHEN @cd >= 120 THEN 4
  WHEN @cd >=  60 THEN 3
  WHEN @cd >=  30 THEN 2
  WHEN @cd >    0 THEN 1
  --ELSE NULL
  END
  RETURN @bucket
END

select count(callid)
     , [dev].[udf_duration](callDuration)
  from callmetatbl
 where programid = 1001
   and callDuration > 0
 group by [dev].[udf_duration](callDuration)

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

وظيفة المثال هذا يعادل التعبير الأصلي. تعبير حالة المرجع المرجع لا يحتوي على أي فجوات، لكنه يشير إلى كل "نقطة توقف" مرتين، أفضل اختبار فقط الحد الأدنى فقط. (عودة الحالة عندما تكون الشرط راضيا. القيام الاختبارات في الاتجاه المعاكس يتيح للقضية غير المعالجة (<= 0 أو null) من خلال أي اختبار، ELSE NULL ليس من الضروري، ولكن يمكن إضافته للكمال.

تفاصيل اضافية

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

عرض مخزن

نلاحظ أن في النسق عرض يمكن أيضا تخزينه كتعريف في قاعدة البيانات. ولكن ليس هناك سبب للقيام بذلك، بخلاف "إخفاء" التعبير المعقد من بيانك.

تبسيط التعبير المعقدة

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

إضافة جدول قاعدة البيانات جدول "البحث"

توصي بعض الإجابات بإضافة جدول "بحث" إلى قاعدة البيانات. لا أرى أن هذا ضروري حقا. يمكن القيام به بالطبع، ويمكن أن يكون منطقي إذا كنت ترغب في أن تكون قادرا على استخلاص قيم مختلفة ل duration من callDuration, ، على الطاير، بدون الحاجة إلى تعديل استفسارك و بدون الاضطرار إلى تشغيل أي بيانات DDL (على سبيل المثال لتغيير تعريف العرض، أو تعديل وظيفة تعريف المستخدم).

مع انضمام إلى جدول "البحث"، فائدة واحدة هي أنه يمكنك إرجاع الاستعلام مجموعات نتيجة مختلفة عن طريق إجراء عمليات DML فقط على جدول "البحث".

لكن هذه الميزة نفسها قد تكون في الواقع عيب كذلك.

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

بعض الافتراضات الكبيرة

يبدو أن الكثير من الإجابات الواردة هنا تفترض ذلك callDuration هو نموذج بيانات عدد صحيح. يبدو أنهم أغفلوا احتمال عدم وجود عدد صحيح، لكن ربما فاتني أن الكتلة في السؤال.

إنه حالة اختبار بسيطة إلى حد ما لإظهار ذلك:

callDuration BETWEEN 0 AND 30

يكون ليس أي ما يعادل

callDuration > 0 AND callDuration < 30

نصائح أخرى

هل هناك أي سبب لا تستخدمه betweenب تصريحات القضية نفسها لا تبدو سيئة للغاية. إذا كنت تكره ذلك حقا، فيمكنك رمي كل هذا في طاولة وتعريزه.

Durations
------------------
low   high   value
0     30     1
31    60     2

إلخ...

(SELECT value FROM Durations WHERE callDuration BETWEEN low AND high) as Duration

تحرير: أو، في حالة يتم فيها استخدام العوامات و between يصبح مرهقا.

(SELECT value FROM Durations WHERE callDuration >= low AND callDuration <= high) as Duration

يمكن كتابة القضية مثل هذا:

case 
when callduration >=30 and callduration<600 then floor(callduration/60)+2
when callduration>0 and callduration< 30 then 1 
when callduration>=600 then 12
end

لم تكن هناك حاجة، واستبدلها من قبل "حيث يتكالف> 0"

أنا أحب الجواب جدول الترجمة المقدمة من قبل! هذا هو أفضل حل

تحتاج إلى دفع القضية إلى أسفل شجرة الاستعلام بحيث يكون إسقاطها مرئيا للمجموعة. يمكن تحقيق ذلك بطريقتين:

  1. استخدم جدول مشتق (سبنسر بالفعل، أظهر آدم وجيريمي كيف)
  2. استخدام تعبيرات الجدول المشترك

    with duration_case as (
    select callid ,
    case
        when callDuration > 0 and callDuration < 30 then 1
        when callDuration >= 30 and callDuration < 60 then 2
        when callDuration >= 60 and callDuration < 120 then 3
        when callDuration >= 120 and callDuration < 180 then 4
        when callDuration >= 180 and callDuration < 240 then 5
        when callDuration >= 240 and callDuration < 300 then 6
        when callDuration >= 300 and callDuration < 360 then 7
        when callDuration >= 360 and callDuration < 420 then 8
        when callDuration >= 420 and callDuration < 480 then 9
        when callDuration >= 480 and callDuration < 540 then 10
        when callDuration >= 540 and callDuration < 600 then 11
        when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0 )
       select count(callid), duration
       from duration_case
       group by duration
    

كلا الحلول تعادل في كل الاحترام. أجد CTES أكثر قابلية للقراءة، وبعضها يفضل أن الجداول المشتقة أكثر محمولة.

يقسم callDuration بحلول الساعة 60:

case
        when callDuration between 1 AND 29 then 1
        when callDuration > 600 then 12
        else (callDuration /60) + 2  end
end as duration

لاحظ أن between شاملة من الحدود، وأنا أفترض أنه سيتم التعامل مع الكاليد كعدد صحيح.


تحديث:
اجمع هذا مع بعض الإجابات الأخرى، ويمكنك الحصول على الاستعلام بأكمله إلى هذا:

select count(d.callid), d.duration
from (   
       select callid
            , case
                when callDuration between 1 AND 29 then 1
                when callDuration > 600 then 12
                else (callDuration /60) + 2  end
              end as duration
        from callmetatbl
        where programid = 1001
              and callDuration > 0
    ) d
group by d.duration
select count(callid), duration from
(
    select callid ,
    case
            when callDuration > 0 and callDuration < 30 then 1
            when callDuration >= 30 and callDuration < 60 then 2
            when callDuration >= 60 and callDuration < 120 then 3
            when callDuration >= 120 and callDuration < 180 then 4
            when callDuration >= 180 and callDuration < 240 then 5
            when callDuration >= 240 and callDuration < 300 then 6
            when callDuration >= 300 and callDuration < 360 then 7
            when callDuration >= 360 and callDuration < 420 then 8
            when callDuration >= 420 and callDuration < 480 then 9
            when callDuration >= 480 and callDuration < 540 then 10
            when callDuration >= 540 and callDuration < 600 then 11
            when callDuration >= 600 then 12
    end as duration
    from callmetatbl
    where programid = 1001 and callDuration > 0
) source
group by duration

غير مختبر:

select  count(callid) , duracion
from
    (select 
        callid,
        case        
            when callDuration > 0 and callDuration < 30 then 1        
            when callDuration >= 30 and callDuration < 60 then 2        
            when callDuration >= 60 and callDuration < 120 then 3        
            when callDuration >= 120 and callDuration < 180 then 4        
            when callDuration >= 180 and callDuration < 240 then 5        
            when callDuration >= 240 and callDuration < 300 then 6        
            when callDuration >= 300 and callDuration < 360 then 7        
            when callDuration >= 360 and callDuration < 420 then 8        
            when callDuration >= 420 and callDuration < 480 then 9        
            when callDuration >= 480 and callDuration < 540 then 10        
            when callDuration >= 540 and callDuration < 600 then 11        
            when callDuration >= 600 then 12        
            else 0
        end as duracion
    from callmetatbl
    where programid = 1001) GRP
where duracion > 0
group by duracion

إضافة جميع الحالات إلى متغير الجدول والقيام بالانضمام الخارجي

DECLARE @t TABLE(durationFrom INT, durationTo INT, result INT)
--        when callDuration > 0 and callDuration < 30 then 1
INSERT INTO @t VALUES(1, 30, 1);
--        when callDuration >= 30 and callDuration < 60 then 2
INSERT INTO @t VALUES(30, 60, 2);

select count(callid) , COALESCE(t.result, 12)
from callmetatbl JOIN @t AS t ON callDuration >= t.durationFrom AND callDuration  < t.durationTo 
where programid = 1001 and callDuration > 0

ها هي طلقة في ذلك. يمكن إجراء جميع المكونات التي تحتاجها في SQL مستقيم.

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from 
      callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

إليك SQL الذي استخدمته في الاختبار. (ليس لدي مكالوتي الشخصية الخاصة بي؛)

select
  count(1) as total
 ,(fixedDuration / divisor) + adder as duration
from
(
    select
      case/*(30s_increments_else_60s)*/when(callDuration<60)then(120)else(60)end as divisor
     ,case/*(increment_by_1_else_2)*/when(callDuration<30)then(1)else(2)end as adder
     ,(/*duration_capped@600*/callDuration+600-ABS(callDuration-600))/2 as fixedDuration
     ,callDuration
    from -- callmetatbl -- using test view below
      (  
       select 1001 as programid,   0 as callDuration union
       select 1001 as programid,   1 as callDuration union
       select 1001 as programid,  29 as callDuration union
       select 1001 as programid,  30 as callDuration union
       select 1001 as programid,  59 as callDuration union
       select 1001 as programid,  60 as callDuration union
       select 1001 as programid, 119 as callDuration union
       select 1001 as programid, 120 as callDuration union
       select 1001 as programid, 179 as callDuration union
       select 1001 as programid, 180 as callDuration union
       select 1001 as programid, 239 as callDuration union
       select 1001 as programid, 240 as callDuration union
       select 1001 as programid, 299 as callDuration union
       select 1001 as programid, 300 as callDuration union
       select 1001 as programid, 359 as callDuration union
       select 1001 as programid, 360 as callDuration union
       select 1001 as programid, 419 as callDuration union
       select 1001 as programid, 420 as callDuration union
       select 1001 as programid, 479 as callDuration union
       select 1001 as programid, 480 as callDuration union
       select 1001 as programid, 539 as callDuration union
       select 1001 as programid, 540 as callDuration union
       select 1001 as programid, 599 as callDuration union
       select 1001 as programid, 600 as callDuration union
       select 1001 as programid,1000 as callDuration
      ) as callmetatbl
    where
      programid = 1001
    and 
      callDuration > 0
) as foo
group by
  (fixedDuration / divisor) + adder

يظهر إخراج SQL أدناه، حيث تحسب 2 سجلات لكل مدة (دلو) من 1 إلى 12.

total  duration
2             1
2             2
2             3
2             4
2             5
2             6
2             7
2             8
2             9
2            10
2            11
2            12

فيما يلي النتائج من الاستعلام الفرعي "FOO":

divisor adder   fixedDuration  callDuration
120         1               1             1
120         1              29            29
120         2              30            30
120         2              59            59
60          2              60            60
60          2             119           119
60          2             120           120
60          2             179           179
60          2             180           180
60          2             239           239
60          2             240           240
60          2             299           299
60          2             300           300
60          2             359           359
60          2             360           360
60          2             419           419
60          2             420           420
60          2             479           479
60          2             480           480
60          2             539           539
60          2             540           540
60          2             599           599
60          2             600           600
60          2             600          1000

هتافات.

ما هو الخطأ في وظيفة تعريف المستخدم هنا؟ يمكنك تنظيف الكود بصريا وتركز الوظيفة بهذه الطريقة. الأداء من الحكمة، لا أستطيع أن أرى الاضطرار مروعا للغاية إلا إذا كنت تفعل شيئا متخلفا بالفعل داخل UDF.

إنشاء جدول بحث ل duration
باستخدام طاولة البحث سوف تسرع SELECT بيان كذلك.

فيما يلي النتيجة النهائية لكيفية البحث عن طاولة البحث.

select  count(a.callid), b.ID as duration
from    callmetatbl a
        inner join DurationMap b 
         on a.callDuration >= b.Minimum
        and a.callDuration < IsNUll(b.Maximum, a.CallDuration + 1)
group by  b.ID

هنا هو جدول البحث.

create table DurationMap (
    ID          int identity(1,1) primary key,
    Minimum     int not null,
    Maximum     int 
)

insert  DurationMap(Minimum, Maximum) select 0,30
insert  DurationMap(Minimum, Maximum) select 30,60
insert  DurationMap(Minimum, Maximum) select 60,120
insert  DurationMap(Minimum, Maximum) select 120,180
insert  DurationMap(Minimum, Maximum) select 180,240
insert  DurationMap(Minimum, Maximum) select 240,300
insert  DurationMap(Minimum, Maximum) select 300,360
insert  DurationMap(Minimum, Maximum) select 360,420
insert  DurationMap(Minimum, Maximum) select 420,480
insert  DurationMap(Minimum, Maximum) select 480,540
insert  DurationMap(Minimum, Maximum) select 540,600
insert  DurationMap(Minimum) select 600
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top