এটি অবশ্যই অনিচ্ছাকৃত আচরণের মতো বলে মনে হচ্ছে। এটি সত্য যে কোনও পরিকল্পনার প্রতিটি ধাপে কার্ডিনালিটি অনুমানের প্রয়োজন হয় না তবে এটি একটি তুলনামূলকভাবে সহজ ক্যোয়ারী পরিকল্পনা এবং চূড়ান্ত কার্ডিনালিটি অনুমান কোয়েরিটি যা করছে তার সাথে সামঞ্জস্যপূর্ণ নয়। এত কম কার্ডিনালিটির অনুমানের ফলে আরও জটিল পরিকল্পনায় জোয়ারের ধরণের পছন্দগুলি এবং অন্যান্য টেবিলের জন্য প্রবাহের অ্যাক্সেসের পদ্ধতিগুলি খারাপ ফলাফল হতে পারে।
পরীক্ষার এবং ত্রুটির মাধ্যমে আমরা কয়েকটি অনুরূপ ক্যোয়ারী নিয়ে আসতে পারি যার জন্য সমস্যাটি উপস্থিত হয় না:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
আমরা আরও প্রশ্নের সাথে আসতে পারি যার জন্য সমস্যাটি উপস্থিত হয়:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
এখানে একটি প্যাটার্ন উপস্থিত রয়েছে: যদি এর মধ্যে এমন কোনও ভাব থাকে CASE
যা কার্যকর হয় বলে আশা করা হয় না এবং ফলাফল প্রকাশটি কোনও টেবিলের বিপরীতে সাবকোয়ারি হয় তবে সারি অনুমানটি সেই অভিব্যক্তির পরে 1 এ চলে যায়।
যদি আমি একটি ক্লাস্টার ইনডেক্স সহ কোনও টেবিলের বিপরীতে কোয়েরিটি লিখি তবে নিয়মগুলি কিছুটা পরিবর্তন। আমরা একই তথ্য ব্যবহার করতে পারি:
CREATE TABLE dbo.X_CI (ID INT NOT NULL, PRIMARY KEY (ID))
INSERT INTO dbo.X_CI WITH (TABLOCK)
SELECT * FROM dbo.X_HEAP;
UPDATE STATISTICS X_CI WITH FULLSCAN;
এই ক্যোয়ারিতে একটি 1000 সারির চূড়ান্ত অনুমান রয়েছে:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI;
তবে এই ক্যোয়ারিতে একটি 1 সারির চূড়ান্ত অনুমান রয়েছে:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI;
এটি আরও খনন করার জন্য আমরা ক্যোরি অপ্টিমাইজার কীভাবে নির্বাচনের গণনা সম্পাদন করেছে সে সম্পর্কে তথ্য পেতে অননুমোদিত ট্রেস ফ্ল্যাগ 2363 ব্যবহার করতে পারি । আমি অনাকাঙ্ক্ষিত ট্রেস পতাকা 8606 এর সাথে ট্রেস পতাকাটি জোড়া লাগানো সহায়ক বলে মনে করেছি । টিএফ 23৩৩ প্রকল্পটি সাধারণীকরণের পরে সরলীকৃত গাছ এবং গাছ উভয়ের জন্য বেছে বেছে গণনা দিয়েছে বলে মনে হচ্ছে। উভয় ট্রেস পতাকা সক্ষম করা তা পরিষ্কার করে দেয় কোন গাছে কোন গণনা প্রযোজ্য।
আসুন প্রশ্নে পোস্ট করা মূল প্রশ্নের জন্য এটি চেষ্টা করে দেখুন:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
এখানে আউটপুট অংশের কিছু অংশ যা আমি মনে করি কিছু মন্তব্যের সাথে প্রাসঙ্গিক:
Plan for computation:
CSelCalcColumnInInterval -- this is the type of calculator used
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID -- this is the column used for the calculation
Pass-through selectivity: 0 -- all rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- the row estimate after the join will still be 1000
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1 -- no rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter) -- the row estimate after the join will still be 1
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- here is the row estimate after the previous join
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: X_OTHER_TABLE_2)
এখন আসুন এটির অনুরূপ ক্যোয়ারির জন্য এটি চেষ্টা করি যাতে সমস্যা নেই। আমি এটি ব্যবহার করতে যাচ্ছি:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
একেবারে শেষে ডিবাগ আউটপুট:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollConstTable(ID=4, CARD=1) -- this is different than before because we select a constant instead of from a table
আসুন অন্য কোয়েরি চেষ্টা করুন যার জন্য খারাপ সারি অনুমানটি উপস্থিত রয়েছে:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
একেবারে শেষে কার্ডিনালিটির অনুমানটি 1 সারিতে নেমে আসে, আবার পাস-থ্রো সিলেকটিভিটি = 1 পরে আবার কার্ডিনালিটির অনুমান 0.501 এবং 0.499 এর নির্বাচনের পরে সংরক্ষণ করা হয়।
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.501
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=12, CARD=1 x_jtLeftOuter) -- this is associated with the ELSE expression
CStCollOuterJoin(ID=11, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=10, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=4, CARD=1 TBL: X_OTHER_TABLE)
আসুন আবার আর একটি একই ধরণের ক্যোয়ারিতে স্যুইচ করা যাক এতে সমস্যা নেই। আমি এটি ব্যবহার করতে যাচ্ছি:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ডিবাগ আউটপুটে কখনওই কোনও পদক্ষেপ হয় না যার পাসের মধ্য দিয়ে 1 টি বেছে নেওয়া হয় The কার্ডিনালিটির অনুমান 1000 সারিতে থাকে।
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
End selectivity computation
যখন ক্লাস্টার ইনডেক্স সহ কোনও টেবিল জড়িত তখন ক্যোয়ারীর কী হবে? সারি প্রাক্কলন ইস্যু সহ নিম্নোক্ত প্রশ্নটি বিবেচনা করুন:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
ডিবাগ আউটপুটটির সমাপ্তি আমরা ইতিমধ্যে যা দেখেছি তার অনুরূপ:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_CI].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
তবে ইস্যু ছাড়াই সিআইয়ের বিরুদ্ধে অনুসন্ধানের আলাদা আউটপুট রয়েছে। এই ক্যোয়ারী ব্যবহার করে:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
বিভিন্ন ক্যালকুলেটর ব্যবহার হচ্ছে ফলাফল Results CSelCalcColumnInInterval
আর প্রদর্শিত হবে না:
Plan for computation:
CSelCalcFixedFilter (0.559)
Pass-through selectivity: 0.559
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
...
Plan for computation:
CSelCalcUniqueKeyFilter
Pass-through selectivity: 0.001
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE)
উপসংহারে, আমরা নিম্নলিখিত শর্তাবলী subquery পরে একটি খারাপ সারি অনুমান পেতে প্রদর্শিত হবে:
CSelCalcColumnInInterval
নির্বাচনশীলতা ক্যালকুলেটর ব্যবহার করা হয়। কখন এটি ব্যবহার করা হয় তা আমি ঠিক জানি না তবে বেস টেবিলটি যখন একটি গাদা হয়ে থাকে তখন এটি প্রায়শই প্রদর্শিত হবে।
পাস-থ্রো সিলেকটিভিটি = ১. অন্য কথায়, CASE
এক্সপ্রেশনগুলির মধ্যে একটির সারিগুলির জন্য মিথ্যা হিসাবে মূল্যায়ন করা হবে বলে আশা করা হচ্ছে। প্রথম CASE
অভিব্যক্তিটি সমস্ত সারিটির জন্য সত্য বলে মূল্যায়ন করে তা বিবেচ্য নয়।
এখানে একটি বাহ্যিক যোগদান রয়েছে CStCollBaseTable
। অন্য কথায়, CASE
ফলাফলের প্রকাশটি একটি টেবিলের বিপরীতে subquery হয়। একটি ধ্রুবক মান কাজ করবে না।
সম্ভবত এই শর্তগুলির মধ্যে কোয়েরি অপ্টিমাইজারটি অনিচ্ছাকৃতভাবে নেস্টেড লুপের অভ্যন্তরীণ অংশে করা কাজের পরিবর্তে বাইরের টেবিলের সারি প্রাক্কলনে পাস-থ্রো সিলেকটিভিটি প্রয়োগ করছে। এটি সারি প্রাক্কলনটি 1 এ কমিয়ে আনবে।
আমি দুটি কাজের ক্ষেত্র সন্ধান করতে সক্ষম হয়েছি। APPLY
সাবকিউয়ের পরিবর্তে ব্যবহার করার সময় আমি সমস্যাটি পুনরুত্পাদন করতে পারিনি। ট্রেস পতাকা 2363 এর আউটপুট এর সাথে খুব আলাদা ছিল APPLY
। প্রশ্নের মূল প্রশ্নটি পুনরায় লেখার একটি উপায় এখানে:
SELECT
h.ID
, a.ID2
FROM X_HEAP h
OUTER APPLY
(
SELECT CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END
) a(ID2);
উত্তরাধিকারসূত্রে সিই সমস্যাটি এড়িয়ে চলেছে।
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
এই ইস্যুটির জন্য একটি সংযুক্ত আইটেম জমা দেওয়া হয়েছিল (পল হোয়াইট তার উত্তরে প্রদত্ত কিছু বিবরণ সহ)।