بتبسيط (التعرج) T-SQL العبارات. أي تحسن ممكن؟
-
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"
أنا أحب الجواب جدول الترجمة المقدمة من قبل! هذا هو أفضل حل
تحتاج إلى دفع القضية إلى أسفل شجرة الاستعلام بحيث يكون إسقاطها مرئيا للمجموعة. يمكن تحقيق ذلك بطريقتين:
- استخدم جدول مشتق (سبنسر بالفعل، أظهر آدم وجيريمي كيف)
استخدام تعبيرات الجدول المشترك
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