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

عرض عادي

الصيغة:
CREATE [OR REPLACE] VIEW [IF NOT EXISTS] [db.]table_name [(alias1 [, alias2 ...])] [ON CLUSTER cluster_name]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | INVOKER | NONE }]
AS SELECT ...
[COMMENT 'comment']
لا تخزّن العروض العادية أي بيانات. فهي تقتصر على القراءة من جدول آخر عند كل عملية وصول. وبعبارة أخرى، لا يعدّ العرض العادي سوى استعلام محفوظ. وعند القراءة من عرض، يُستخدم هذا الاستعلام المحفوظ بوصفه استعلامًا فرعيًا في بند FROM. على سبيل المثال، لنفترض أنك أنشأت عرضًا:
CREATE VIEW view AS SELECT ...
وكتبت استعلامًا:
SELECT a, b, c FROM view
هذا الاستعلام مطابق تمامًا لاستخدام الاستعلام الفرعي:
SELECT a, b, c FROM (SELECT ...)

العرض ذو المعلمات

تشبه العروض ذات المعلمات العروض العادية، لكنها يمكن أن تُنشأ بمعلمات لا تُفسَّر فورًا. ويمكن استخدام هذه العروض مع دوال الجداول، بحيث يُستخدَم اسم العرض اسمًا للدالة، وتُمرَّر قيم المعلمات كوسائط لها.
CREATE VIEW view AS SELECT * FROM TABLE WHERE Column1={column1:datatype1} and Column2={column2:datatype2} ...
ينشئ ما سبق عرضًا للجدول يمكن استخدامه كدالة جدولية بعد استبدال المعلمات كما هو موضح أدناه.
SELECT * FROM view(column1=value1, column2=value2 ...)

العرض المادي

CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
CREATE OR REPLACE MATERIALIZED VIEW [db.]table_name [ON CLUSTER cluster_name] [TO[db.]name [(columns)]] [ENGINE = engine] [POPULATE]
[REFRESH ...]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
OR REPLACE وIF NOT EXISTS لا يمكن استخدامهما معًا: فالجمع بينهما يؤدي إلى خطأ نحوي.

CREATE OR REPLACE MATERIALIZED VIEW

تستبدل CREATE OR REPLACE MATERIALIZED VIEW بشكل ذري عرضًا ماديًا موجودًا وجدول التخزين الداخلي الخاص به (إن وُجد). وتتطلب هذه العملية محرك قاعدة بيانات Atomic أو Replicated.
CREATE OR REPLACE MATERIALIZED VIEW [db.]name [ON CLUSTER cluster]
[TO [db.]target_table]
[ENGINE = engine]
[POPULATE]
[REFRESH ...]
AS SELECT ...
السلوكيات الرئيسية:
  • بدون بند TO: يُحذف الجدول الداخلي القديم ويُنشأ جدول جديد. وتُفقد البيانات الموجودة في الجدول الداخلي ما لم يتم تحديد POPULATE.
  • مع بند TO: يُستبدل تعريف الـ view فقط؛ ولا يتأثر الجدول الهدف أو بياناته.
  • متوافق مع REFRESH وON CLUSTER وجميع خيارات المحرك. ولا يكون POPULATE مدعومًا إلا في قواعد البيانات Atomic — ويُرفض في قواعد البيانات Replicated (راجع ملاحظة POPULATE أدناه).
  • يتطلب امتيازي CREATE VIEW وDROP VIEW.
لا يكون CREATE OR REPLACE MATERIALIZED VIEW مدعومًا إلا مع محركات قواعد البيانات Atomic أو Replicated. وهو غير مدعوم مع محرك قاعدة البيانات Ordinary.
أمثلة:
-- Create a materialized view with an inner table
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, sum(y) AS total FROM src GROUP BY x;

-- Replace with a new definition (old inner table data is lost)
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    AS SELECT x, count() AS cnt FROM src GROUP BY x;

-- Replace with POPULATE to backfill from existing source data
CREATE OR REPLACE MATERIALIZED VIEW mv
    ENGINE = MergeTree ORDER BY x
    POPULATE
    AS SELECT x FROM src;

-- Replace an inner-table MV with a TO-table MV (target data is preserved)
CREATE OR REPLACE MATERIALIZED VIEW mv TO target
    AS SELECT x FROM src;
إليك دليلًا خطوة بخطوة لاستخدام العروض المادية.
تخزّن العروض المادية البيانات التي يُحوّلها استعلام SELECT المرتبط بها. عند إنشاء عرض مادي بدون TO [db].[table]، يجب تحديد ENGINE — وهو محرك الجدول المستخدم لتخزين البيانات. عند إنشاء عرض مادي باستخدام TO [db].[table]، لا يمكنك أيضًا استخدام POPULATE. يُنفَّذ العرض المادي على النحو التالي: عند إدراج البيانات في الجدول المحدد في SELECT، يُحوَّل جزء من البيانات المُدرَجة بواسطة استعلام SELECT هذا، ثم تُدرَج النتيجة في العرض.
تستخدم العروض المادية في ClickHouse أسماء الأعمدة بدلًا من ترتيب الأعمدة عند الإدراج في جدول الوجهة. وإذا لم تكن بعض أسماء الأعمدة موجودة في نتيجة استعلام SELECT، فإن ClickHouse يستخدم قيمة افتراضية، حتى إذا لم يكن العمود من النوع Nullable. ومن الممارسات الآمنة إضافة أسماء مستعارة لكل عمود عند استخدام العروض المادية.تُنفَّذ العروض المادية في ClickHouse بطريقة أقرب إلى مشغلات الإدراج. وإذا وُجد أي تجميع في استعلام العرض، فإنه يُطبَّق فقط على دفعة البيانات المُدرجة حديثًا. وأي تغييرات على البيانات الموجودة مسبقًا في الجدول المصدر (مثل update أو delete أو drop partition وما إلى ذلك) لا تغيّر العرض المادي.لا تتمتع العروض المادية في ClickHouse بسلوك حتمي عند حدوث أخطاء. وهذا يعني أن الكتل التي كُتبت بالفعل ستبقى محفوظة في جدول الوجهة، لكن جميع الكتل التي تأتي بعد الخطأ لن تُحفَظ.افتراضيًا، إذا تسببت الكتابة إلى أحد العروض في حدوث استثناء، فإن استعلام INSERT يفشل. وليس هناك ما يضمن ما إذا كانت الكتلة قد وصلت بالفعل إلى الجدول المصدر عند تلك النقطة — إذ يعتمد ذلك على توقيت مسار الإدراج، لا على خطأ العرض. أعد محاولة تنفيذ INSERT الذي فشل باستخدام إزالة تكرار الإدراج (insert_deduplicate, deduplicate_blocks_in_dependent_materialized_views) للحصول على تسليم exactly-once إلى الجدول المصدر وجميع العروض التابعة.إن تعيين materialized_views_ignore_errors=true في استعلام INSERT يغيّر فقط طريقة الإبلاغ عن الأخطاء: إذ يُسجَّل خطأ كل عرض على أنه تحذير وينجح استعلام INSERT. ويكون التسليم إلى وجهة العرض الفاشل جزئيًا — إذ تُحفَظ الكتل التي عولجت قبل الاستثناء، بينما تُسقَط الكتلة الفاشلة وأي كتل لاحقة من ذلك العرض. ولا ترى العروض التابعة لتلك الوجهة إلا الكتل التي وصلت فعلًا، لذا يكون تسليمها جزئيًا أيضًا. أما العروض الشقيقة (وسلاسلها التابعة) التي لم يحدث فيها استثناء فتُكتَب بالكامل، ويُكتَب إلى الجدول المصدر كالمعتاد. ونظرًا لأن INSERT يُبلِغ عن النجاح، فلن يتلقى العميل أي إشارة إلى الفشل ولن تُفعَّل أي إعادة محاولة تلقائية؛ لذا لا تستخدم هذا الإعداد إلا عندما يجب ألا تُحجَب عمليات الكتابة إلى الجدول المصدر بسبب مشكلات في جهة العرض (على سبيل المثال، جداول system.*_log).تكون القيمة الافتراضية لـ materialized_views_ignore_errors هي true بالنسبة إلى جداول system.*_log.
إذا حددت POPULATE، فستُدرَج بيانات الجدول الموجودة مسبقًا في العرض عند إنشائه، كما لو كنت تنفذ CREATE TABLE ... AS SELECT .... وإلا، فسيقتصر الاستعلام على البيانات المُدرجة في الجدول بعد إنشاء العرض. نحن لا نوصي باستخدام POPULATE، لأن البيانات المُدرجة في الجدول أثناء إنشاء العرض لن تُدرج فيه.
نظرًا إلى أن POPULATE يعمل مثل CREATE TABLE ... AS SELECT ...، فله القيود التالية:
  • غير مدعوم مع Replicated database
  • غير مدعوم في ClickHouse Cloud
وبدلًا من ذلك، يمكن استخدام INSERT ... SELECT منفصل.
يمكن أن يحتوي استعلام SELECT على DISTINCT وGROUP BY وORDER BY وLIMIT. لاحظ أن التحويلات المقابلة تُنفَّذ بشكل مستقل على كل كتلة من البيانات المُدرجة. فعلى سبيل المثال، إذا كان GROUP BY مضبوطًا، تُجمَّع البيانات أثناء الإدراج، ولكن فقط ضمن حزمة واحدة من البيانات المُدرجة. ولن تُجمَّع البيانات لاحقًا. والاستثناء هو عند استخدام ENGINE ينفذ تجميع البيانات بشكل مستقل، مثل SummingMergeTree. إذا كان العرض المادي يستخدم البنية TO [db.]name، فيمكنك DETACH العرض، ثم تشغيل ALTER على الجدول الهدف، ثم ATTACH العرض الذي فُصل (DETACH) سابقًا. لاحظ أن العرض المادي يتأثر بإعداد optimize_on_insert. إذ تُدمَج البيانات قبل إدراجها في العرض. تبدو العروض مثل الجداول العادية. فعلى سبيل المثال، تُدرَج في نتيجة استعلام SHOW TABLES. لحذف عرض، استخدم DROP VIEW. مع أن DROP TABLE يعمل مع VIEW أيضًا.

أمان SQL

يتيح لك DEFINER وSQL SECURITY تحديد مستخدم ClickHouse الذي سيُستخدم عند تنفيذ الاستعلام الأساسي للعرض. لـ SQL SECURITY ثلاث قيم مسموح بها: DEFINER أو INVOKER أو NONE. ويمكنك تحديد أي مستخدم موجود أو CURRENT_USER في عبارة DEFINER. يوضح الجدول التالي الصلاحيات المطلوبة لكل مستخدم لكي يتمكن من إجراء SELECT من العرض. لاحظ أنه بغض النظر عن خيار SQL SECURITY، يبقى من الضروري في جميع الحالات امتلاك GRANT SELECT ON <view> للقراءة منه.
خيار أمان SQLالعرضالعرض المادي
DEFINER aliceيجب أن يمتلك alice صلاحية SELECT على جدول المصدر الخاص بالعرض.يجب أن يمتلك alice صلاحية SELECT على جدول المصدر الخاص بالعرض وصلاحية INSERT على الجدول الهدف الخاص بالعرض.
INVOKERيجب أن يمتلك المستخدم صلاحية SELECT على جدول المصدر الخاص بالعرض.لا يمكن تحديد SQL SECURITY INVOKER للعروض المادية.
NONE--
يُعد الخيار SQL SECURITY NONE خيارًا Deprecated. سيتمكن أي مستخدم لديه صلاحية إنشاء عروض باستخدام SQL SECURITY NONE من تنفيذ أي query عشوائي. لذلك، يلزم وجود GRANT ALLOW SQL SECURITY NONE TO <user> لإنشاء عرض باستخدام هذا الخيار.
إذا لم يتم تحديد DEFINER/SQL SECURITY، فستُستخدم القيم الافتراضية: إذا جرى Attach لعرض بدون تحديد DEFINER/SQL SECURITY، فستكون القيمة الافتراضية هي SQL SECURITY NONE للعرض المادي وSQL SECURITY INVOKER للعرض العادي. لتغيير أمان SQL لعرض موجود، استخدم
ALTER TABLE MODIFY SQL SECURITY { DEFINER | INVOKER | NONE } [DEFINER = { user | CURRENT_USER }]

أمثلة

CREATE VIEW test_view
DEFINER = alice SQL SECURITY DEFINER
AS SELECT ...
CREATE VIEW test_view
SQL SECURITY INVOKER
AS SELECT ...

Live View

هذه الميزة مُهمَلة وسيتم إزالتها لاحقًا. لراحتك، يمكنك العثور على الوثائق القديمة هنا

العرض المادي القابل للتحديث

CREATE MATERIALIZED VIEW [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
REFRESH [EVERY|AFTER interval [OFFSET interval]]
[RANDOMIZE FOR interval]
[DEPENDS ON [db.]name [, [db.]name [, ...]]]
[SETTINGS name = value [, name = value [, ...]]]
[APPEND]
[TO[db.]name] [(columns)] [ENGINE = engine]
[EMPTY]
[DEFINER = { user | CURRENT_USER }] [SQL SECURITY { DEFINER | NONE }]
AS SELECT ...
[COMMENT 'comment']
حيث إن interval عبارة عن سلسلة من الفترات البسيطة:
number SECOND|MINUTE|HOUR|DAY|WEEK|MONTH|YEAR
يجب أن يحدِّد بند REFRESH واحدًا على الأقل من EVERY أو AFTER أو DEPENDS ON. ويُرفض استخدام REFRESH المجرّد (من دون أيٍّ من هذه العناصر). كما أن REFRESH DEPENDS ON ... من دون EVERY/AFTER هو اختصار لـ REFRESH AFTER 0 SECOND DEPENDS ON ...؛ راجع تبعيات التحديث أدناه. يشغِّل الاستعلام المقابل دوريًا ويخزّن نتيجته في جدول.
  • إذا تم تحديد APPEND، فإن كل عملية تحديث تُدرِج صفوفًا في الجدول من دون حذف الصفوف الموجودة. ولا تكون عملية الإدراج ذرّية، تمامًا مثل استعلام INSERT INTO ... SELECT العادي.
  • بخلاف ذلك، يستبدل كل تحديث محتويات الجدول السابقة بشكل ذرّي.
الاختلافات عن العروض المادية العادية غير القابلة للتحديث:
  • لا يوجد مشغّل إدراج. عند إدراج بيانات جديدة في الجدول المحدد في SELECT، لا يتم تمريرها تلقائيًا إلى العرض المادي القابل للتحديث. وبدلًا من ذلك، لا يحدث إدراج البيانات إلا أثناء تشغيل عمليات التحديث الدورية أو اليدوية.
  • لا توجد قيود على استعلام SELECT. دوال الجداول (مثل url())، والعروض، وUNION، وJOIN، كلها مسموح بها.
الإعدادات الموجودة في جزء REFRESH ... SETTINGS من الاستعلام هي إعدادات التحديث (مثل refresh_retries)، وهي مختلفة عن الإعدادات العادية (مثل max_threads). ويمكن تحديد الإعدادات العادية باستخدام SETTINGS في نهاية الاستعلام.

جدولة التحديث

أمثلة على جداول التحديث:
REFRESH EVERY 1 DAY -- every day, at midnight (UTC)
REFRESH EVERY 1 MONTH -- on 1st day of every month, at midnight
REFRESH EVERY 1 MONTH OFFSET 5 DAY 2 HOUR -- on 6th day of every month, at 2:00 am
REFRESH EVERY 2 WEEK OFFSET 5 DAY 15 HOUR 10 MINUTE -- every other Saturday, at 3:10 pm
REFRESH EVERY 30 MINUTE -- at 00:00, 00:30, 01:00, 01:30, etc
REFRESH AFTER 30 MINUTE -- 30 minutes after the previous refresh completes, no alignment with time of day
-- REFRESH AFTER 1 HOUR OFFSET 1 MINUTE -- syntax error, OFFSET is not allowed with AFTER
REFRESH EVERY 1 WEEK 2 DAYS -- every 9 days, not on any particular day of the week or month;
                            -- specifically, when day number (since 1969-12-29) is divisible by 9
REFRESH EVERY 5 MONTHS -- every 5 months, different months each year (as 12 is not divisible by 5);
                       -- specifically, when month number (since 1970-01) is divisible by 5
يقوم RANDOMIZE FOR بضبط وقت كل عملية تحديث عشوائيًا، على سبيل المثال:
REFRESH EVERY 1 DAY OFFSET 2 HOUR RANDOMIZE FOR 1 HOUR -- every day at random time between 01:30 and 02:30
يمكن تشغيل عملية تحديث واحدة فقط في كل مرة لعرض معيّن. على سبيل المثال، إذا كان عرض يحتوي على REFRESH EVERY 1 MINUTE يستغرق دقيقتين لإجراء التحديث، فسيُحدَّث كل دقيقتين فقط. وإذا أصبح بعد ذلك أسرع وبدأ التحديث خلال 10 ثوانٍ، فسيعود إلى التحديث كل دقيقة. (وعلى وجه الخصوص، لن يُحدَّث كل 10 ثوانٍ لتعويض تحديثات فائتة متراكمة — إذ لا يوجد أصلًا مثل هذا التراكم.) عادةً ما يبدأ أول تحديث فور إنشاء الـ materialized view: إذ يكون الوقت منذ آخر تحديث لانهائيًا، لذا يشير أي schedule إلى أن وقت التحديث قد حان الآن. إذا تم تحديد EMPTY، فسيتم تخطي هذا التحديث الأولي، وسيحدث أول تحديث في الوقت المجدول التالي؛ فعلى سبيل المثال، مع EVERY 1 HOUR سيحدث أول تحديث عند نهاية الساعة الحالية.

في قاعدة بيانات Replicated

إذا كان العرض المادي القابل للتحديث موجودًا في قاعدة بيانات Replicated، فإن النُسخ المتماثلة تتنسّق فيما بينها بحيث لا تنفّذ التحديث في كل وقت مجدول إلا نسخة متماثلة واحدة فقط. ويُشترط استخدام محرك الجداول ReplicatedMergeTree، حتى تتمكن جميع النُسخ المتماثلة من رؤية البيانات الناتجة عن التحديث. في وضع APPEND، يمكن تعطيل التنسيق باستخدام SETTINGS all_replicas = 1. وهذا يجعل النُسخ المتماثلة تنفّذ عمليات التحديث بشكل مستقل عن بعضها البعض. وفي هذه الحالة، لا يكون ReplicatedMergeTree مطلوبًا. في الوضع غير APPEND، لا يُدعَم إلا التحديث المنسَّق. أما إذا أردت تحديثًا غير منسَّق، فاستخدم قاعدة بيانات Atomic واستعلام CREATE ... ON CLUSTER لإنشاء عروض مادية قابلة للتحديث على جميع النُسخ المتماثلة. يتم التنسيق عبر Keeper. ويُحدَّد مسار znode بواسطة إعداد الخادم default_replica_path.

تبعيات إعادة التحديث

DEPENDS ON يزامن عمليات إعادة التحديث بين الجداول المختلفة:
CREATE MATERIALIZED VIEW dependent REFRESH EVERY 1 HOUR DEPENDS ON dependency [...]
لن يبدأ تحديث العرض التابع إلا بعد اكتمال تحديث جميع العروض التي يعتمد عليها. لإجراء التحديث مباشرةً بعد تحديث عرض آخر:
CREATE MATERIALIZED VIEW dependent REFRESH AFTER 0 SECOND DEPENDS ON dependency [...]
أو بشكلٍ مكافئ:
CREATE MATERIALIZED VIEW dependent REFRESH DEPENDS ON dependency [...]
لا تعمل DEPENDS ON إلا بين العروض المادية القابلة للتحديث. وعلى وجه الخصوص، إذا كان عرض التبعية يستخدم TO <table>، فتأكد من استخدام اسم العرض لا اسم الجدول. وإذا كانت قائمة DEPENDS ON تتضمن جدولًا عاديًا أو عرضًا غير قابل للتحديث أو تحتوي على خطأ مطبعي، فلن يتم تحديث العرض أبدًا، وستظهر حالته MissingDependencies في system.view_refreshes. ويمكن تغيير التبعيات أو إزالتها باستخدام ALTER، راجع تغيير معلمات التحديث.

استخدام DEPENDS ON لتحقيق زمن انتشار متسق

إذا كان كلا العرضين يستخدم REFRESH EVERY بالفترة نفسها، فستُطبَّق التبعية في كل فترة زمنية. على سبيل المثال، افترض أن العرضين X وY يستخدمان كلاهما REFRESH EVERY 1 HOUR، وأن Y يقرأ من جدول المخرجات الخاص بـ X. من دون تبعيات، سيرى Y عادةً بيانات X الناتجة عن عملية refresh الخاصة بالساعة السابقة. ومع DEPENDS ON X، لن تبدأ عملية refresh الخاصة بـ Y عند الساعة 11:00 إلا بعد اكتمال عملية refresh الخاصة بـ X عند الساعة 11:00.
           10:00            11:00            12:00
           │                │                │
  X:        [run]┐           [run]┐           [run]┐
                 │                │                │
  Y:             └►[run]          └►[run]          └►[run]
قد يتخطّى كلٌّ من التبعية والتابع فتراتٍ زمنيةً بصورة مستقلة إذا استغرقت عمليات التحديث وقتًا أطول من فترة التحديث. ولا يوجد ما يضمن أن يُحدَّث التابع مرةً واحدةً بالضبط لكل تحديث للتبعية.
           10:00          11:00          12:00          13:00
           │              │              │              |
  X:        [run]┐         [run]┐         [run]┐         [run]┐
                 │              └────┐    (Y skips 12:00)     └───┐
  Y:             └►[10:00 ru------un]└►[11:00 ru---------------un]└►[13:00 run]

استخدام DEPENDS ON في معالجة التدفق على دفعات

إذا لم يُستخدَم REFRESH EVERY، فسيُحدَّث العرض التابع X إذا كانت جميع تبعياته قد تحدّثت مرة واحدة على الأقل منذ آخر تحديث لـ X. ويضيف REFRESH AFTER T تأخيرًا: إذ سيبدأ العنصر التابع التحديث بعد مرور مدة T على اكتمال تحديث التبعية. التبعيات الدائرية مسموح بها ومفيدة. تأمّل هذا الرسم البياني للعروض المادية القابلة للتحديث:
  1. يأخذ X دفعة من الصفوف من تدفقٍ ما ويضعها في جدول.
  2. ثم يقرأ كلٌّ من Y وZ من ذلك الجدول، ويُجريان عمليات تجميع مختلفة، ويضيفان النتائج إلى جداول أخرى.
  3. بعد اكتمال معالجة الدفعة بالكامل، يأخذ X الدفعة التالية، وتتكرر الدورة.
            source


          ┌─────────┐
     ┌───►│    X    │◄───┐
     │    └──┬───┬──┘    │
  DEPENDS    │   │    DEPENDS
    ON       ▼   ▼      ON
     │      ┌─┐ ┌─┐      │
     └──────┤Y│ │Z├──────┘
            └─┘ └─┘
مثال كامل:
CREATE TABLE current_batch (t UInt64, v Int64) ENGINE ReplicatedMergeTree ORDER BY t;
CREATE TABLE batch_log (max_t UInt64, n Int64, v_sum Int64, processed_at DateTime64) ENGINE ReplicatedMergeTree ORDER BY max_t;
CREATE TABLE stats (h UInt64, n UInt64) ENGINE ReplicatedSummingMergeTree ORDER BY h;

-- (system.numbers stands in for a data source with monotonically increasing timestamps or sequence numbers)
CREATE MATERIALIZED VIEW current_batch_v REFRESH EVERY 10 SECOND DEPENDS ON batch_log_v, stats_v TO current_batch AS SELECT number as t, number * 10 as v FROM system.numbers WHERE number > (SELECT max(max_t) FROM batch_log) LIMIT 100;

CREATE MATERIALIZED VIEW batch_log_v REFRESH DEPENDS ON current_batch_v APPEND TO batch_log AS SELECT max(t) as max_t, count() as n, sum(v) as v_sum, now64() as processed_at FROM current_batch;

CREATE MATERIALIZED VIEW stats_v REFRESH DEPENDS ON current_batch_v APPEND TO stats AS SELECT cityHash64(v) % 20 as h, count() as n FROM current_batch GROUP BY h;

-- Must trigger initial refresh manually.
SYSTEM REFRESH VIEW current_batch_v;
تعمل السلاسل الأطول أيضًا. لا يعمل هذا جيدًا إلا عندما يكون تنسيق التحديث مفعّلًا، أي عندما تكون العروض في قاعدة بيانات Replicated أو Shared. ومن دون التنسيق، تؤدي إعادة تشغيل الخادم إلى كسر الحلقة، مما يتطلب تنفيذ SYSTEM REFRESH VIEW يدويًا بعد كل إعادة تشغيل بدلًا من تنفيذه مرة واحدة فقط بعد إنشاء العروض.

إعدادات التحديث

إعدادات التحديث المتاحة:
  • refresh_retries - عدد مرات إعادة المحاولة إذا فشل استعلام التحديث بسبب استثناء. إذا فشلت جميع محاولات إعادة المحاولة، فانتقل إلى وقت التحديث المجدول التالي. تعني القيمة 0 عدم إعادة المحاولة، وتعني القيمة -1 إعادة المحاولة بلا حدود. القيمة الافتراضية: 2.
  • refresh_retry_initial_backoff_ms - مدة التأخير قبل أول إعادة محاولة، إذا لم تكن قيمة refresh_retries صفراً. تتضاعف مدة التأخير مع كل إعادة محاولة لاحقة، حتى refresh_retry_max_backoff_ms. القيمة الافتراضية: 100 مللي ثانية.
  • refresh_retry_max_backoff_ms - الحد الأقصى للزيادة الأسية في مدة التأخير بين محاولات التحديث. القيمة الافتراضية: 60000 مللي ثانية (دقيقة واحدة).
  • all_replicas - في قاعدة بيانات Replicated مع APPEND، يحدّد ما إذا كانت جميع النسخ المتماثلة تُحدَّث بشكل مستقل، أو ما إذا كانت نسخة متماثلة واحدة فقط تُجري التحديث في كل وقت مجدول. لا يمكن تغيير هذا الإعداد بعد إنشاء العرض. القيمة الافتراضية: false.

تغيير معلمات التحديث

يمكن تغيير معلمات التحديث لعرض مادي قابل للتحديث موجود باستخدام ALTER TABLE ... MODIFY REFRESH:
ALTER TABLE [db.]name MODIFY REFRESH EVERY|AFTER ... [RANDOMIZE FOR ...] [DEPENDS ON ...] [SETTINGS ...]
الجدولة (EVERY أو AFTER) إلزامية: تستبدل التعليمة دائمًا جميع مَعلمات التحديث — الجدولة، وRANDOMIZE FOR، وDEPENDS ON، وإعدادات التحديث — بما هو محدد. وأي عنصر يُغفَل يُعاد تعيينه إلى قيمته الافتراضية (بالنسبة إلى الإعدادات) أو يُزال (بالنسبة إلى التبعيات والعشوائية).
  • لتغيير إعدادات التحديث فقط (مثلًا refresh_retries)، أعِد ذكر الجدولة الحالية:
    ALTER TABLE rmv MODIFY REFRESH EVERY 1 HOUR SETTINGS refresh_retries = 5;
    
  • لا يدعم ALTER TABLE ... MODIFY SETTING refresh_retries = ... العروض المادية؛ ويجب تنفيذ ذلك عبر MODIFY REFRESH.
  • لا تتمّ إضافة APPEND أو إزالته.
  • لا يمكن تغيير الإعداد all_replicas بعد الإنشاء.
أمثلة:
-- Change the schedule, drop existing settings and dependencies.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE;

-- Change the schedule and tune retry behavior.
ALTER TABLE rmv MODIFY REFRESH EVERY 30 MINUTE
SETTINGS refresh_retries = 5,
         refresh_retry_initial_backoff_ms = 500,
         refresh_retry_max_backoff_ms = 60000;

-- Keep the dependency while changing the period.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR DEPENDS ON other_rmv;

-- Drop the dependency by omitting `DEPENDS ON`.
ALTER TABLE rmv MODIFY REFRESH EVERY 6 HOUR;

عمليات أخرى

تتوفر حالة جميع العروض المادية القابلة للتحديث في الجدول system.view_refreshes. ويعرض هذا الجدول تحديدًا تقدّم التحديث (إذا كان جاريًا)، ووقت آخر تحديث ووقت التحديث التالي، ورسالة الاستثناء إذا فشل التحديث. لإيقاف التحديثات أو بدئها أو تشغيلها أو إلغائها يدويًا، استخدم SYSTEM STOP|START|REFRESH|WAIT|CANCEL VIEW. للانتظار حتى يكتمل التحديث، استخدم SYSTEM WAIT VIEW. ويكون هذا مفيدًا بشكل خاص عند انتظار التحديث الأولي بعد إنشاء العرض.
معلومة طريفة: يُسمح لاستعلام التحديث بالقراءة من العرض الذي يجري تحديثه، مع الاطّلاع على نسخة البيانات السابقة للتحديث. وهذا يعني أنه يمكنك تنفيذ لعبة الحياة لكونواي: https://pastila.nl/?00021a4b/d6156ff819c83d490ad2dcec05676865#O0LGWTO7maUQIA4AcGUtlA==

Window View

هذه ميزة تجريبية وقد تتغير في الإصدارات القادمة على نحوٍ غير متوافق مع الإصدارات السابقة. لتمكين استخدام Window View واستعلام WATCH، استخدم الإعداد allow_experimental_window_view. أدخل الأمر set allow_experimental_window_view = 1.
CREATE WINDOW VIEW [IF NOT EXISTS] [db.]table_name [TO [db.]table_name] [INNER ENGINE engine] [ENGINE engine] [WATERMARK strategy] [ALLOWED_LATENESS interval_function] [POPULATE]
AS SELECT ...
GROUP BY time_window_function
[COMMENT 'comment']
يمكن لـ window view تجميع البيانات حسب النافذة الزمنية وإخراج النتائج عندما تصبح النافذة جاهزة للإرسال. كما تخزّن نتائج التجميع الجزئية في جدول داخلي (أو محدد) لتقليل زمن الاستجابة، ويمكنها دفع نتيجة المعالجة إلى جدول محدد أو إرسال إشعارات دفع باستخدام الاستعلام WATCH. يشبه إنشاء window view إنشاء MATERIALIZED VIEW. وتحتاج window view إلى محرك تخزين داخلي لتخزين البيانات الوسيطة. ويمكن تحديد التخزين الداخلي باستخدام العبارة INNER ENGINE، وستستخدم window view المحرك AggregatingMergeTree بوصفه المحرك الداخلي الافتراضي. عند إنشاء window view بدون TO [db].[table]، يجب تحديد ENGINE — أي محرك الجدول المستخدم لتخزين البيانات.

دوال النافذة الزمنية

تُستخدم دوال النافذة الزمنية للحصول على حدَّي النافذة السفلي والعلوي للسجلات. يجب استخدام window view مع إحدى دوال النافذة الزمنية.

سمات الوقت

يدعم Window view نمطَي وقت المعالجة ووقت الحدث. يتيح وقت المعالجة لـ Window view إنتاج النتائج استنادًا إلى وقت الجهاز المحلي، ويُستخدم افتراضيًا. وهو أبسط مفهوم للوقت، لكنه لا يوفّر الحتمية. يمكن تعريف سمة وقت المعالجة من خلال تعيين time_attr في دالة النافذة الزمنية إلى عمود في جدول، أو باستخدام الدالة now(). ينشئ الاستعلام التالي Window view باستخدام وقت المعالجة.
CREATE WINDOW VIEW wv AS SELECT count(number), tumbleStart(w_id) as w_start from date GROUP BY tumble(now(), INTERVAL '5' SECOND) as w_id
وقت الحدث هو الوقت الذي وقع فيه كل حدث على الجهاز الذي أصدره. وعادةً ما يكون هذا الوقت مضمّنًا في السجلات عند إنشائها. وتتيح معالجة وقت الحدث الحصول على نتائج متسقة حتى عند وصول الأحداث بترتيب غير متسق أو متأخر. وتدعم ميزة window view معالجة وقت الحدث باستخدام صياغة WATERMARK. توفّر window view ثلاث استراتيجيات للعلامة المائية:
  • STRICTLY_ASCENDING: تُصدر علامة مائية تساوي أكبر طابع زمني تمّت ملاحظته حتى الآن. ولا تُعد الصفوف التي تحمل طابعًا زمنيًا أصغر من أكبر طابع زمني متأخرة.
  • ASCENDING: تُصدر علامة مائية تساوي أكبر طابع زمني تمّت ملاحظته حتى الآن ناقص 1. ولا تُعد الصفوف التي تحمل طابعًا زمنيًا مساويًا لأكبر طابع زمني أو أصغر منه متأخرة.
  • BOUNDED: WATERMARK=INTERVAL. تُصدر علامات مائية تساوي أكبر طابع زمني تمّت ملاحظته مطروحًا منه التأخير المحدد.
الاستعلامات التالية أمثلة على إنشاء window view باستخدام WATERMARK:
CREATE WINDOW VIEW wv WATERMARK=STRICTLY_ASCENDING AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
CREATE WINDOW VIEW wv WATERMARK=ASCENDING AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
CREATE WINDOW VIEW wv WATERMARK=INTERVAL '3' SECOND AS SELECT count(number) FROM date GROUP BY tumble(timestamp, INTERVAL '5' SECOND);
افتراضيًا، تُطلق النافذة نتيجتها عند وصول العلامة المائية، وتُسقَط العناصر التي تصل متأخرة عنها. يدعم Window View معالجة الأحداث المتأخرة من خلال ضبط ALLOWED_LATENESS=INTERVAL. ومثال على التعامل مع التأخر هو:
CREATE WINDOW VIEW test.wv TO test.dst WATERMARK=ASCENDING ALLOWED_LATENESS=INTERVAL '2' SECOND AS SELECT count(a) AS count, tumbleEnd(wid) AS w_end FROM test.mt GROUP BY tumble(timestamp, INTERVAL '5' SECOND) AS wid;
لاحظ أن العناصر المنبعثة من إطلاق متأخر يجب التعامل معها على أنها نتائج محدَّثة لحساب سابق. وبدلًا من الإطلاق عند نهاية النوافذ، سيُطلِق window view فورًا عند وصول الحدث المتأخر. وبالتالي، سيؤدي ذلك إلى ظهور مخرجات متعددة للنافذة نفسها. يحتاج المستخدمون إلى مراعاة هذه النتائج المكررة أو إزالة تكرارها. يمكنك تعديل استعلام SELECT الذي جرى تحديده في window view باستخدام تعليمة ALTER TABLE ... MODIFY QUERY. يجب أن تكون بنية البيانات الناتجة عن استعلام SELECT الجديد مماثلة لبنية استعلام SELECT الأصلي، سواء وُجد بند TO [db.]name أم لم يوجد. لاحظ أن البيانات في النافذة الحالية ستُفقد لأن الحالة الوسيطة لا يمكن إعادة استخدامها.

مراقبة النوافذ الجديدة

تدعم ميزة window view استعلام WATCH لمراقبة التغييرات، أو يمكنك استخدام صياغة TO لإخراج النتائج إلى جدول.
WATCH [db.]window_view
[EVENTS]
[LIMIT n]
[FORMAT format]
يمكن تحديد LIMIT لتعيين عدد التحديثات التي سيتم تلقيها قبل إنهاء الاستعلام. ويمكن استخدام العبارة EVENTS للحصول على صيغة مختصرة من استعلام WATCH، بحيث ستحصل بدلًا من نتيجة الاستعلام على أحدث علامة مائية للاستعلام فقط.

الإعدادات

  • window_view_clean_interval: فاصل تنظيف Window View بالثواني لتحرير البيانات القديمة. سيحتفظ النظام بالنوافذ التي لم تُطلق بالكامل وفقًا لوقت النظام أو إعداد WATERMARK، وسيُحذف ما عدا ذلك من البيانات.
  • window_view_heartbeat_interval: فاصل نبضات الحياة بالثواني للإشارة إلى أن استعلام watch لا يزال نشطًا.
  • wait_for_window_view_fire_signal_timeout: المهلة الزمنية لانتظار إشارة إطلاق Window View في معالجة وقت الحدث.

مثال

لنفترض أننا نريد حساب عدد سجلات النقرات كل 10 ثوانٍ في جدول سجلات يُسمى data، ويكون هيكل الجدول كما يلي:
CREATE TABLE data ( `id` UInt64, `timestamp` DateTime) ENGINE = Memory;
أولًا، ننشئ window view بنافذة متعاقبة ذات interval قدره 10 ثوانٍ:
CREATE WINDOW VIEW wv as select count(id), tumbleStart(w_id) as window_start from data group by tumble(timestamp, INTERVAL '10' SECOND) as w_id
ثم نستخدم الاستعلام WATCH للحصول على النتائج.
WATCH wv
عند إدراج السجلات في الجدول data،
INSERT INTO data VALUES(1,now())
يجب أن يعرض استعلام WATCH النتائج كما يلي:
┌─count(id)─┬────────window_start─┐
│         1 │ 2020-01-14 16:56:40 │
└───────────┴─────────────────────┘
بدلًا من ذلك، يمكننا توجيه المخرجات إلى جدول آخر باستخدام صيغة TO.
CREATE WINDOW VIEW wv TO dst AS SELECT count(id), tumbleStart(w_id) as window_start FROM data GROUP BY tumble(timestamp, INTERVAL '10' SECOND) as w_id
يمكن العثور على أمثلة إضافية ضمن الاختبارات ذات الحالة في ClickHouse (وهي تحمل هناك الاسم *window_view*).

استخدامات Window View

تكون Window View مفيدة في السيناريوهات التالية:
  • المراقبة: تجميع سجلات المقاييس وحسابها بحسب الوقت، ثم إخراج النتائج إلى جدول الهدف. ويمكن أن تستخدم لوحة المعلومات جدول الهدف بوصفه الجدول المصدر.
  • التحليل: تجميع البيانات تلقائيًا ومعالجتها مسبقًا ضمن النافذة الزمنية. وقد يكون ذلك مفيدًا عند تحليل عدد كبير من السجلات. وتُلغي المعالجة المسبقة العمليات الحسابية المتكررة عبر استعلامات متعددة وتقلل زمن استجابة الاستعلامات.

عروض مؤقتة

يدعم ClickHouse عروض مؤقتة بالخصائص التالية (على غرار الجداول المؤقتة حيثما ينطبق ذلك):
  • بعمر الجلسة لا يوجد عرض مؤقت إلا طوال مدة الجلسة الحالية. وتُحذف تلقائيًا عند انتهاء الجلسة.
  • بدون قاعدة بيانات لا يمكنك إلحاق عرض مؤقت باسم قاعدة بيانات. فهي توجد خارج قواعد البيانات (ضمن نطاق الجلسة).
  • غير مُكرّرة / بدون ON CLUSTER الكائنات المؤقتة محلية بالنسبة إلى الجلسة، ولا يمكن إنشاؤها باستخدام ON CLUSTER.
  • حلّ الأسماء إذا كان لكائن مؤقت (جدول أو طريقة عرض) الاسم نفسه لكائن دائم، وأشار استعلام إلى الاسم من دون قاعدة بيانات، فسيُستخدم الكائن المؤقت.
  • كائن منطقي (بدون تخزين) لا يخزّن عرض مؤقت سوى نص SELECT الخاص بها (وتستخدم داخليًا تخزين View). وهي لا تحتفظ بالبيانات ولا تقبل INSERT.
  • عبارة ENGINE لا تحتاج إلى تحديد ENGINE؛ وإذا تم توفيره على هيئة ENGINE = View، فسيتم تجاهله أو التعامل معه باعتباره طريقة العرض المنطقية نفسها.
  • الأمان / الامتيازات يتطلب إنشاء عرض مؤقت الامتياز CREATE TEMPORARY VIEW، والذي يُمنح ضمنيًا بواسطة CREATE VIEW.
  • SHOW CREATE استخدم SHOW CREATE TEMPORARY VIEW view_name; لطباعة DDL الخاص بعرض مؤقت.

الصيغة

CREATE TEMPORARY VIEW [IF NOT EXISTS] view_name AS <select_query>
OR REPLACE غير مدعوم للعروض المؤقتة (تماشيًا مع الجداول المؤقتة). إذا كنت بحاجة إلى “استبدال” عرض مؤقت، فاحذفها ثم أنشئها من جديد.

أمثلة

أنشئ جدولًا مؤقتًا كمصدر، ثم أنشئ فوقه عرضًا مؤقتًا:
CREATE TEMPORARY TABLE t_src (id UInt32, val String);
INSERT INTO t_src VALUES (1, 'a'), (2, 'b');

CREATE TEMPORARY VIEW tview AS
SELECT id, upper(val) AS u
FROM t_src
WHERE id <= 2;

SELECT * FROM tview ORDER BY id;
اعرض تعريفه (DDL):
SHOW CREATE TEMPORARY VIEW tview;
احذفه:
DROP TEMPORARY VIEW IF EXISTS tview;  -- temporary views are dropped with TEMPORARY TABLE syntax

غير المسموح به / القيود

  • CREATE OR REPLACE TEMPORARY VIEW ...غير مسموح (استخدم DROP + CREATE).
  • CREATE TEMPORARY MATERIALIZED VIEW ... / WINDOW VIEWغير مسموح.
  • CREATE TEMPORARY VIEW db.view AS ...غير مسموح (من دون محدِّد قاعدة بيانات).
  • CREATE TEMPORARY VIEW view ON CLUSTER 'name' AS ...غير مسموح (الكائنات المؤقتة محلية على مستوى الجلسة).
  • POPULATE, REFRESH, TO [db.table], المحركات الداخلية، وجميع البنود الخاصة بـ MV → لا تنطبق على العروض المؤقتة.

ملاحظات حول الاستعلامات الموزعة

إن العرض المؤقت ليس سوى تعريف؛ ولا توجد أي بيانات يمكن تمريرها. وإذا كان العرض المؤقت يشير إلى جداول مؤقتة (مثل Memory)، فيمكن إرسال بياناتها إلى الخوادم البعيدة أثناء تنفيذ الاستعلامات الموزعة، تمامًا كما هو الحال مع الجداول المؤقتة.

مثال

-- A session-scoped, in-memory table
CREATE TEMPORARY TABLE temp_ids (id UInt64) ENGINE = Memory;

INSERT INTO temp_ids VALUES (1), (5), (42);

-- A session-scoped view over the temp table (purely logical)
CREATE TEMPORARY VIEW v_ids AS
SELECT id FROM temp_ids;

-- Replace 'test' with your cluster name.
-- GLOBAL JOIN forces ClickHouse to *ship* the small join-side (temp_ids via v_ids)
-- to every remote server that executes the left side.
SELECT count()
FROM cluster('test', system.numbers) AS n
GLOBAL ANY INNER JOIN v_ids USING (id)
WHERE n.number < 100;

آخر تعديل في ٢٥ يونيو ٢٠٢٦