في الإصدار 24.3 من ClickHouse، كان المحلّل مفعّلًا افتراضيًا.
يمكنك الاطّلاع على مزيد من التفاصيل حول كيفية عمله هنا.
حالات عدم التوافق المعروفة
على الرغم من إصلاح عدد كبير من الأخطاء وإضافة تحسينات جديدة، فإنه يقدّم أيضًا بعض التغييرات غير المتوافقة مع الإصدارات السابقة في سلوك ClickHouse. يُرجى قراءة التغييرات التالية لمعرفة كيفية إعادة كتابة استعلاماتك لتناسب المحلّل.
لم تعد الاستعلامات غير الصالحة تُحسَّن
كانت البنية التحتية السابقة لتخطيط الاستعلامات تطبّق تحسينات على مستوى AST قبل خطوة التحقّق من صحة الاستعلام. وكانت هذه التحسينات قادرة على إعادة كتابة الاستعلام الأوّلي بحيث يصبح صالحًا وقابلًا للتنفيذ.
في المحلّل، يجري التحقّق من صحة الاستعلام قبل خطوة التحسين. وهذا يعني أن الاستعلامات غير الصالحة التي كان يمكن تنفيذها سابقًا لم تعد مدعومة. وفي مثل هذه الحالات، يجب تصحيح الاستعلام يدويًا.
المثال 1
يستخدم الاستعلام التالي العمود number في قائمة الإسقاط، مع أن المتاح بعد التجميع هو toString(number) فقط.
في المحلّل القديم، كان GROUP BY toString(number) يُحسَّن إلى GROUP BY number,، مما يجعل الاستعلام صالحًا.
SELECT number
FROM numbers(1)
GROUP BY toString(number)المثال 2
تحدث المشكلة نفسها في هذا الاستعلام. يُستخدَم العمود number بعد التجميع مع مفتاح آخر.
كان محلّل الاستعلام السابق يُصحّح هذا الاستعلام بنقل عامل التصفية number > 5 من عبارة HAVING إلى عبارة WHERE.
SELECT
number % 2 AS n,
sum(number)
FROM numbers(10)
GROUP BY n
HAVING number > 5لإصلاح الاستعلام، ينبغي نقل جميع الشروط التي تخص الأعمدة غير المجمّعة إلى قسم WHERE للامتثال لصياغة SQL القياسية:
SELECT
number % 2 AS n,
sum(number)
FROM numbers(10)
WHERE number > 5
GROUP BY nلتسهيل الترحيل، يمكن لمحلّل الاستعلام تكرار إعادة الكتابة القديمة من HAVING إلى WHERE للمقترنات غير التجميعية المتصلة بـ AND. فعِّل analyzer_compatibility_allow_non_aggregate_in_having = 1 لتمكين هذا السلوك. هذا الإعداد متاح منذ ClickHouse 26.7. يُتجاهل هذا الإعداد مع WITH CUBE وWITH ROLLUP وWITH TOTALS وGROUPING SETS. تبقى المقترنات التي تحتوي على دوال تجميعية أو grouping أو دوال غير حتمية في HAVING؛ وإذا احتوى أي مقترن على دالة نافذة أو دالة ذات حالة (مثل rowNumberInBlock)، فستُعطَّل إعادة الكتابة لعبارة HAVING بالكامل، بما يطابق السلوك القديم.
CREATE VIEW مع استعلام غير صالح
يجري المحلِّل دائمًا التحقّق من الأنواع.
في السابق، كان من الممكن إنشاء VIEW باستعلام SELECT غير صالح.
ثم كان يفشل عند تنفيذ أول SELECT أو INSERT (في حالة MATERIALIZED VIEW).
لم يعد إنشاء VIEW بهذه الطريقة ممكنًا.
مثال
CREATE TABLE source (data String)
ENGINE=MergeTree
ORDER BY tuple();
CREATE VIEW some_view
AS SELECT JSONExtract(data, 'test', 'DateTime64(3)')
FROM source;أوجه عدم التوافق المعروفة لعبارة JOIN
JOIN باستخدام عمود من إسقاط
لا يمكن افتراضيًا استخدام اسم مستعار من قائمة SELECT كمفتاح JOIN USING.
عند تمكين الإعداد الجديد analyzer_compatibility_join_using_top_level_identifier، فإنه يغيّر سلوك JOIN USING بحيث يفضّل تفسير المعرّفات استنادًا إلى التعبيرات الواردة في قائمة الإسقاط ضمن استعلام SELECT، بدلًا من استخدام أعمدة الجدول الأيسر مباشرةً.
على سبيل المثال:
SELECT a + 1 AS b, t2.s
FROM VALUES('a UInt64, b UInt64', (1, 1)) AS t1
JOIN VALUES('b UInt64, s String', (1, 'one'), (2, 'two')) t2
USING (b);عند ضبط analyzer_compatibility_join_using_top_level_identifier على true، يُفسَّر شرط الـ join على أنه t1.a + 1 = t2.b، بما يتوافق مع سلوك الإصدارات السابقة.
ستكون النتيجة 2, 'two'.
وعندما تكون قيمة الإعداد false، يكون شرط الـ join افتراضيًا t1.b = t2.b، وسيُرجع الاستعلام 2, 'one'.
إذا لم يكن b موجودًا في t1، فسيفشل الاستعلام مع ظهور خطأ.
تغيّرات في السلوك مع JOIN USING والأعمدة ALIAS/MATERIALIZED
في المُحلِّل، يؤدي استخدام * في استعلام JOIN USING يتضمن أعمدة ALIAS أو MATERIALIZED إلى إدراج هذه الأعمدة في مجموعة النتائج افتراضيًا.
على سبيل المثال:
CREATE TABLE t1 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t1 VALUES (1), (2);
CREATE TABLE t2 (id UInt64, payload ALIAS sipHash64(id)) ENGINE = MergeTree ORDER BY id;
INSERT INTO t2 VALUES (2), (3);
SELECT * FROM t1
FULL JOIN t2 USING (payload);في المحلل، ستتضمن نتيجة هذا الاستعلام العمود payload إلى جانب id من كلا الجدولين.
في المقابل، كان المحلل السابق لا يتضمن أعمدة ALIAS هذه إلا إذا كانت إعدادات محددة (asterisk_include_alias_columns أو asterisk_include_materialized_columns) مفعّلة،
كما قد تظهر الأعمدة بترتيب مختلف.
ولضمان الحصول على نتائج متسقة ومتوقعة، خاصةً عند ترحيل الاستعلامات القديمة إلى المحلل، يُنصح بتحديد الأعمدة صراحةً في عبارة SELECT بدلًا من استخدام *.
التعامل مع معدِّلات النوع للأعمدة في بند USING
في المحلّل، جرى توحيد قواعد تحديد النوع الأعلى المشترك للأعمدة المحددة في بند USING لإعطاء نتائج أكثر قابلية للتنبؤ،
خصوصًا عند التعامل مع معدِّلات النوع مثل LowCardinality وNullable.
LowCardinality(T)وT: عند ضمّ عمود من النوعLowCardinality(T)إلى عمود من النوعT، سيكون النوع الأعلى المشترك الناتج هوT، ما يعني عمليًا تجاهل معدِّلLowCardinality.Nullable(T)وT: عند ضمّ عمود من النوعNullable(T)إلى عمود من النوعT، سيكون النوع الأعلى المشترك الناتج هوNullable(T)، بما يضمن الحفاظ على خاصية قبول القيمNULL.
على سبيل المثال:
SELECT id, toTypeName(id)
FROM VALUES('id LowCardinality(String)', ('a')) AS t1
FULL OUTER JOIN VALUES('id String', ('b')) AS t2
USING (id);في هذا الاستعلام، يُحدَّد النوع الأعلى المشترك لـ id على أنه String، مع إسقاط المُعدِّل LowCardinality من t1.
تغييرات على أسماء أعمدة الإسقاط
أثناء حساب أسماء الإسقاطات، لا يجري استبدال الأسماء المستعارة.
SELECT
1 + 1 AS x,
x + 1
SETTINGS enable_analyzer = 0
FORMAT PrettyCompact
┌أنواع وسائط الدالة غير المتوافقة
في المحلّل، يحدث استنتاج الأنواع أثناء تحليل الاستعلام الأوّلي.
ويعني هذا التغيير أن عمليات التحقق من الأنواع تُجرى قبل التقييم المختصر؛ لذلك، يجب أن تمتلك وسائط الدالة if دائمًا نوعًا أعلى مشتركًا.
على سبيل المثال، يفشل الاستعلام التالي مع There is no supertype for types Array(UInt8), String because some of them are Array and some of them are not:
SELECT toTypeName(if(0, [2, 3, 4], 'String'))العناقيد غير المتجانسة
يُحدِث المُحلِّل تغييرًا كبيرًا في بروتوكول الاتصال بين الخوادم داخل العنقود. لذلك، يتعذّر تشغيل الاستعلامات الموزعة على خوادم تستخدم قيماً مختلفة للإعداد enable_analyzer.
تُفسَّر عمليات التعديل بواسطة المحلّل السابق
لا تزال عمليات التعديل تستخدم المحلّل القديم.
وهذا يعني أنه لا يمكن استخدام بعض ميزات ClickHouse SQL الجديدة في عمليات التعديل، مثل عبارة QUALIFY.
يمكن الاطلاع على الحالة هنا.
الميزات غير المدعومة
فيما يلي قائمة بالميزات التي لا يدعمها المحلّل حاليًا:
- فهرس Annoy.
- فهرس Hypothesis. العمل جارٍ عليه هنا.
- ميزة Window View غير مدعومة. ولا توجد خطط لدعمها مستقبلًا.
الترحيل إلى Cloud
نعمل على تمكين المحلّل في جميع المثيلات التي يكون معطّلًا فيها حاليًا، وذلك لدعم تحسينات جديدة في الوظائف والأداء. يفرض هذا التغيير قواعد أكثر صرامة لتحديد النطاق في SQL، ما يتطلب من العملاء تحديث الاستعلامات غير المتوافقة يدويًا.
سير عمل الترحيل
- حدِّد الاستعلام بتصفية
system.query_logباستخدامnormalized_query_hash:
SELECT query
FROM clusterAllReplicas(default, system.query_log)
WHERE normalized_query_hash='{hash}'
LIMIT 1
SETTINGS skip_unavailable_shards=1- نفّذ الاستعلام بعد تفعيل المحلّل بإضافة هذه الإعدادات.
SETTINGS
enable_analyzer=1,
analyzer_compatibility_join_using_top_level_identifier=1- أعد صياغة الاستعلام وتحقّق من نتائج الاستعلام للتأكد من أنها تطابق المخرجات الناتجة عند تعطيل المحلِّل.
يُرجى الرجوع إلى أكثر حالات عدم التوافق شيوعًا التي ظهرت أثناء الاختبارات الداخلية.
معرّف تعبير غير معروف
الخطأ: Unknown expression identifier ... in scope ... (UNKNOWN_IDENTIFIER). رمز الاستثناء: 47
السبب: تُعرَّف الآن الاستعلامات التي تعتمد على سلوكيات قديمة متساهلة وغير قياسية — مثل الإشارة إلى الأسماء المستعارة المحسوبة داخل عوامل التصفية، أو عناصر الإسقاط الملتبسة في الاستعلامات الفرعية، أو نطاق تعبير الجدول الشائع "dynamic" — تعريفًا صحيحًا على أنها غير صالحة، وتُرفض فورًا.
الحل: حدّث أنماط SQL لديك كما يلي:
- منطق التصفية: انقل المنطق من WHERE إلى HAVING إذا كنت تطبّق التصفية على النتائج، أو كرّر التعبير في WHERE إذا كنت تطبّق التصفية على البيانات المصدر.
- نطاق الاستعلام الفرعي: حدّد صراحةً جميع الأعمدة التي يحتاجها الاستعلام الخارجي.
- مفاتيح JOIN: استخدم ON مع تعبيرات كاملة بدلًا من USING إذا كان المفتاح اسمًا مستعارًا.
- في الاستعلامات الخارجية، ارجع إلى الاسم المستعار الخاص بالاستعلام الفرعي/تعبير الجدول الشائع نفسه، لا إلى الجداول الموجودة داخله.
الأعمدة غير المجمّعة في GROUP BY
الخطأ: Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE). رمز الاستثناء: 215
السبب: كان المحلّل القديم يسمح بتحديد أعمدة غير موجودة في بند GROUP BY (وغالبًا ما كان يختار قيمة عشوائية). يلتزم المحلّل بمعيار SQL القياسي: يجب أن يكون كل عمود محدد إما تجميعيًا أو مفتاح تجميع.
الحل: غلّف العمود داخل any() أو argMax()، أو أضِفه إلى GROUP BY.
/* ORIGINAL QUERY */
-- device_id is ambiguous
SELECT user_id, device_id FROM table GROUP BY user_id
/* FIXED QUERY */
SELECT user_id, any(device_id) FROM table GROUP BY user_id
-- OR
SELECT user_id, device_id FROM table GROUP BY user_id, device_idالأعمدة غير المجمّعة في HAVING
الخطأ: Column ... is not under aggregate function and not in GROUP BY keys (NOT_AN_AGGREGATE). رمز الاستثناء: 215
السبب: كان المحلّل القديم ينقل بصمت الشروط غير التجميعية من مقترنات AND من HAVING إلى WHERE، ويعاملها كعوامل تصفية قبل التجميع. يلتزم المحلّل بـ SQL القياسية: لا يجوز أن يشير HAVING إلا إلى مفاتيح التجميع والدوال التجميعية.
الحل: انقل الشرط من HAVING إلى WHERE يدويًا، أو فعّل analyzer_compatibility_allow_non_aggregate_in_having = 1 (متاح منذ ClickHouse 26.7) لاستعادة إعادة الكتابة القديمة كمساعدة في الترحيل. يتم تجاهل إعداد compatibility مع WITH CUBE وWITH ROLLUP وWITH TOTALS وGROUPING SETS. وتبقى الشروط التي تحتوي على دوال تجميعية أو grouping أو دوال غير حتمية داخل HAVING؛ وإذا احتوى أي شرط على دالة نافذة أو دالة ذات حالة (مثل rowNumberInBlock)، فسيتم تعطيل إعادة الكتابة لكل HAVING، بما يتوافق مع السلوك القديم.
/* ORIGINAL QUERY */
SELECT category, sum(value) FROM t GROUP BY category HAVING service = 'svc1';
/* FIXED QUERY */
SELECT category, sum(value) FROM t WHERE service = 'svc1' GROUP BY category;أسماء تعبيرات الجدول الشائعة المكررة
الخطأ: CTE with name ... already exists (MULTIPLE_EXPRESSIONS_FOR_ALIAS). رمز الاستثناء: 179
السبب: كان المُحلِّل القديم يسمح بتعريف عدة تعبيرات جدول شائعة (WITH …) بالاسم نفسه، بحيث يطغى التعريف اللاحق على السابق. ويمنع المُحلِّل هذا الالتباس.
الحل: أعد تسمية تعبيرات الجدول الشائعة المكررة لتكون فريدة.
/* ORIGINAL QUERY */
WITH
data AS (SELECT 1 AS id),
data AS (SELECT 2 AS id) -- Redefined
SELECT * FROM data;
/* FIXED QUERY */
WITH
raw_data AS (SELECT 1 AS id),
processed_data AS (SELECT 2 AS id)
SELECT * FROM processed_data;معرّفات الأعمدة الملتبسة
الخطأ: JOIN [JOIN TYPE] ambiguous identifier ... (AMBIGUOUS_IDENTIFIER) رمز الاستثناء: 207
السبب: يشير الاستعلام إلى اسم عمود موجود في عدة جداول ضمن JOIN من دون تحديد الجدول المصدر. وكان المحلّل القديم غالبًا ما يستنتج العمود استنادًا إلى المنطق الداخلي، بينما يتطلب المحلّل تحديد الاسم صراحةً.
الحل: حدّد العمود بالكامل باستخدام table_alias.column_name.
/* ORIGINAL QUERY */
SELECT table1.ID AS ID FROM table1, table2 WHERE ID...
/* FIXED QUERY */
SELECT table1.ID AS ID_RENAMED FROM table1, table2 WHERE ID_RENAMED...استخدام غير صحيح لـ FINAL
الخطأ: Table expression modifiers FINAL are not supported for subquery... أو Storage ... doesn't support FINAL (UNSUPPORTED_METHOD). رموز الاستثناء: 1، 181
السبب: FINAL هو معدِّل لتخزين الجدول (وتحديدًا [Shared]ReplacingMergeTree). يرفض المحلِّل FINAL عند تطبيقه على:
- الاستعلامات الفرعية أو الجداول المشتقة (مثل FROM (SELECT …) FINAL).
- محركات الجداول التي لا تدعمه (مثل SharedMergeTree).
الحل: طبِّق FINAL فقط على الجدول المصدر داخل الاستعلام الفرعي، أو أزِله إذا كان المحرك لا يدعمه.
/* ORIGINAL QUERY */
SELECT * FROM (SELECT * FROM my_table) AS subquery FINAL ...
/* FIXED QUERY */
SELECT * FROM (SELECT * FROM my_table FINAL) AS subquery ...عدم التمييز بين حالات الأحرف في الدالة countDistinct()
الخطأ: Function with name countdistinct does not exist (UNKNOWN_FUNCTION). رمز الاستثناء: 46
السبب: أسماء الدوال تميّز بين حالات الأحرف، أو تُطابَق بشكل صارم ضمن المحلّل. لم يعد countdistinct (بحروف صغيرة بالكامل) يُعرَف تلقائيًا.
الحل: استخدم الصيغة القياسية countDistinct (camelCase) أو الدالة uniq الخاصة بـ ClickHouse.