يوفّر ClickHouse Connect لهجة SQLAlchemy clickhousedb المبنية على المشغّل الأساسي. وهي تدعم SQLAlchemy 1.4.40 والإصدارات الأحدث، بما في ذلك SQLAlchemy 2.x، مع التركيز على استعلامات Core، وClickHouse DDL، واستكشاف البنية، وعمليات insert البسيطة في ORM.
ثبّت تبعيات SQLAlchemy باستخدام الـ extra الخاصة بالحزمة:
pip install "clickhouse-connect[sqlalchemy]"الاتصال عبر SQLAlchemy
أنشئ محركًا باستخدام صيغة URL clickhousedb:// أو clickhousedb+connect://:
from sqlalchemy import create_engine, text
engine = create_engine(
"clickhousedb://user:password@host:8123/mydb?compression=zstd"
)
with engine.connect() as conn:
version = conn.execute(text("SELECT version()")).scalar_one()
print(version)يمكن أن تتضمن معلمات استعلام URL إعدادات ClickHouse، أو خيارات عميل ClickHouse Connect مثل compression وquery_limit ومهل الانتظار، أو خيارات HTTP/TLS مثل ca_cert. أضِف البادئة ch_ إلى إعداد ClickHouse لفرض التعامل معه كإعداد على مستوى الخادم عند الحاجة، على سبيل المثال ch_http_max_field_name_size=99999.
راجع وسيطات الاتصال والإعدادات للاطلاع على خيارات العميل المتاحة.
إعدادات خاصة بكل استعلام
مرِّر إعدادات ClickHouse عبر خيارات التنفيذ في SQLAlchemy. يمكن تعيين الإعدادات على المحرك أو الاتصال أو التعليمة. وتكون قيمة التعليمة لها الأسبقية على قيمة الاتصال أو المحرك عند استخدام المفتاح نفسه.
from sqlalchemy import text
stmt = text("SELECT getSetting('max_threads')").execution_options(
settings={"max_threads": 2}
)
with engine.connect() as conn:
value = conn.execute(stmt).scalar_one()تنسيقات القراءة لكل استعلام
عيّن تنسيقات قراءة ClickHouse على مستوى المحرك أو الاتصال أو التعليمة عبر خيارات تنفيذ SQLAlchemy باستخدام query_formats. تُطبَّق تنسيقات التعليمة أولًا، لذا تتجاوز المفاتيح وأحرف البدل المطابقة على مستوى الاتصال أو المحرك.
from sqlalchemy import text
stmt = text("SELECT user_uuid FROM users").execution_options(
query_formats={"UUID": "string"}
)
with engine.connect() as conn:
rows = conn.execute(stmt).all()معلمات من جهة الخادم
يعرض SQLAlchemy المعلمات عادةً من جهة العميل. فعِّل معلمات ClickHouse من جهة الخادم عند إنشاء المحرّك:
engine = create_engine(
"clickhousedb://user:password@host:8123/mydb",
server_side_params=True,
)في هذا الوضع، يجب أن تكون كل قيمة مقيّدة من نوع SQLAlchemy متوافق مع ClickHouse. وتتحول قوائم IN المدعومة إلى معلمات ClickHouse من النوع Array. ويرفع المصرّف CompileError عندما يتعذر عليه استنتاج نوع متوافق أو معالجة قيمة مقيّدة بأمان.
يجب أن تكون أسماء الربط أسماء ClickHouse من نوع ASCII BareWord. تُرفض الأسماء التي تبدأ وتنتهي بـ $ لأن المشغّل الأساسي يحجزها لمعلمات الاستعلام الثنائية الخام.
استعلامات Core
تدعم هذه اللهجة استعلامات SELECT في SQLAlchemy Core مع عمليات الربط، وعوامل التصفية، والترتيب، والحدود والإزاحات، وDISTINCT، وعمليات select المركبة.
تُترجم union() وintersect() وexcept_() في SQLAlchemy إلى UNION DISTINCT وINTERSECT DISTINCT وEXCEPT DISTINCT في ClickHouse. وتُترجم نظائرها union_all() وintersect_all() وexcept_all() إلى عوامل التشغيل المقابلة مع ALL. يحافظ هذا التعيين الصريح على دلالات التكرارات في SQLAlchemy بغض النظر عن الإعدادات الافتراضية لعمليات المجموعات في ClickHouse.
from sqlalchemy import MetaData, Table, select
metadata = MetaData(schema="mydb")
users = Table("users", metadata, autoload_with=engine)
orders = Table("orders", metadata, autoload_with=engine)
events = Table("events", metadata, autoload_with=engine)
stmt = (
select(users.c.name, orders.c.product)
.select_from(users.join(orders, users.c.id == orders.c.user_id))
.order_by(users.c.name)
.limit(10)
)
with engine.connect() as conn:
rows = conn.execute(stmt).all()يُدعَم DELETE الخفيف ويتطلب عبارة WHERE صريحة:
from sqlalchemy import delete
stmt = delete(users).where(users.c.name.like("%temporary%"))
with engine.connect() as conn:
conn.execute(stmt)عرض القيم الحرفية
عندما يضمّن SQLAlchemy قيمة مقيّدة باستخدام literal_binds أو literal_execute، تستخدم اللهجة أسلوب الاقتباس في ClickHouse لأنواع السلاسل النصية العامة وأنواع ClickHouse. وينطبق ذلك أيضًا عند استخدام أغلفة TypeDecorator واختيارات with_variant(). تحتفظ قيم السلاسل النصية بعلامات النسبة المئوية والشرطات المائلة العكسية، حتى مع وجود مَعلَمات مقيّدة أخرى.
الأعمدة الفرعية لـ JSON
بالنسبة إلى عمود مُعرَّف أو ممثَّل في ClickHouse بصفته JSON، استخدم الأقواس المربعة لاختيار مقطع واحد في كل مرة من مسار عمود فرعي مدعوم بالتخزين:
from sqlalchemy import Column, MetaData, Table, select
from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import JSON, UInt32
events = Table(
"events",
MetaData(),
Column("payload", JSON),
)
request_id = events.c.payload["context"]["request"].subcolumn(
"id",
type_=UInt32,
)
stmt = select(
events.c.payload["severity"].label("severity"),
request_id.label("request_id"),
)يُحوَّل payload["severity"] إلى صياغة المعرّف المنقّط في ClickHouse. يُقتبس كل جزء على حدة، على سبيل المثال `events`.`payload`.`severity`. يقرأ العمود الفرعي JSON المخزّن في ClickHouse ولا يستدعي getSubcolumn. استخدم [] أو .subcolumn() بشكل متسلسل، مرة واحدة لكل مقطع من المسار. يجب أن يكون كل مقطع سلسلة غير فارغة.
يؤدي تمرير type_ إلى .subcolumn() إلى تغليف المسار المنقّط بعملية CAST في SQL وإسناد هذا النوع إلى تعبير SQLAlchemy. من دون type_، تتصرف .subcolumn("segment") مثل ["segment"].
يكون نوع المسار غير المحدد Dynamic في ClickHouse. لا يسمح ClickHouse باستخدام قيم Dynamic مباشرةً في ORDER BY أو GROUP BY. مرّر type_ عند استخدام عمود فرعي في هذه المواضع.
بالنسبة إلى الشيفرة ذات الأنواع الثابتة، استورد json_subcolumn من clickhouse_connect.cc_sqlalchemy. تقبل الدالة المساعدة أيضًا مقطعًا واحدًا في كل مرة وتحافظ على نوع نتيجة بايثون المحدد بواسطة type_:
from clickhouse_connect.cc_sqlalchemy import json_subcolumn
context = json_subcolumn(events.c.payload, "context")
request = json_subcolumn(context, "request")
request_id = json_subcolumn(request, "id", type_=UInt32)في هذا المثال، تتعامل أدوات التحقق من الأنواع مع request_id على أنه ColumnElement[int].
يُقتبس كل مقطع بشكل مستقل، بما في ذلك الأسماء التي تحتوي على مسافات أو علامات اقتباس خلفية. لا تجعل علامات الاقتباس الخلفية النقطة قيمة حرفية في معالجة مسارات JSON في ClickHouse. عند تمكين json_type_escape_dots_in_keys، استخدم ترميز ClickHouse %2E للنقاط الحرفية في المفاتيح. للوصول إلى مفتاح باسم a.b، استخدم payload["a%2Eb"]، وليس payload["a.b"].
امتدادات استعلام ClickHouse
استورد select من clickhouse_connect.cc_sqlalchemy لتمكين أدوات التحقق الساكنة من الأنواع من التعرّف على طرائق ClickHouse المعرّفة الأنواع. كما تتوفر هذه الطرائق أيضًا في sqlalchemy.select القياسي وقت التشغيل.
from clickhouse_connect.cc_sqlalchemy import select
stmt = (
select(events.c.user_id, events.c.event_type)
.final()
.prewhere(events.c.event_date >= "2026-01-01")
.sample(0.1)
.limit_by([events.c.user_id], 3)
)طرق Select في ClickHouse هي:
| الطريقة | ميزة SQL |
|---|---|
.final() |
FINAL لجدول |
.sample(value) |
SAMPLE، باستخدام نسبة أو عدد صفوف أو تعبير |
.prewhere(expression) |
PREWHERE؛ تُجمَع الاستدعاءات المتكررة باستخدام AND |
.limit_by(columns, limit, offset=None) |
LIMIT ... BY |
.array_join(...) |
ARRAY JOIN |
.left_array_join(...) |
LEFT ARRAY JOIN |
.ch_join(...) |
عمليات JOIN في ClickHouse مع خيارات strictness وdistribution وusing وcross |
.cte(name, materialized=True) |
WITH name AS MATERIALIZED (...) |
تُعد Select.with_hint() في SQLAlchemy واجهة برمجة تطبيقات لتلميحات الجداول. لا تُنشئ لهجة ClickHouse تلميحات الجداول. يؤدي استخدام تلميح wildcard أو clickhousedb قابل للتطبيق إلى إصدار SAWarning مع إبقاء SQL المُولَّد دون تغيير. استخدم final() أو sample() أو prewhere() أو limit_by() لبنود ClickHouse هذه.
تُعد Select.with_statement_hint() واجهة برمجة تطبيقات لتوجيه خام يُضاف في النهاية. وهي تُلحق النص المقدَّم بنهاية SELECT دون تحقق خاص بـ ClickHouse. يظل ذلك متاحًا لاستخدامه مع SQL ثابت وموثوق، مثل SETTINGS max_threads=1:
stmt = select(events.c.id).with_statement_hint("SETTINGS max_threads=1")بالنسبة إلى إعدادات ClickHouse، يُفضَّل استخدام خيارات التنفيذ كي يتعامل المشغّل معها بشكل منفصل عن نص SQL:
stmt = select(events.c.id).execution_options(settings={"max_threads": 1})على سبيل المثال، يمكن ربط GLOBAL ANY LEFT JOIN في ClickHouse دون الحاجة إلى تداخل FromClause مخصّص:
stmt = (
select(events.c.id, users.c.name)
.select_from(events)
.ch_join(
users,
events.c.user_id == users.c.id,
isouter=True,
strictness="ANY",
distribution="GLOBAL",
)
)استخدم الصيغة الصريحة Lambda مع الدوال عالية الرتبة في ClickHouse:
from sqlalchemy import column, func
from clickhouse_connect.cc_sqlalchemy import Lambda, select
stmt = select(
func.arrayMap(
Lambda("x", column("x") * 2),
events.c.metrics,
).label("doubled")
)تُحوَّل بنية SQLAlchemy القياسية values() عند الترجمة إلى صياغة دالة الجدول VALUES في ClickHouse، بما في ذلك عند استخدامها في تعبير الجدول الشائع. يتطلب شكل تعبير الجدول الشائع استخدام SQLAlchemy 2.0.42 أو إصدار أحدث، إذ أُضيفت Values.cte().
تعبيرات الجدول الشائعة المُجسَّدة
يُضمّن ClickHouse تعبير الجدول الشائع تلقائيًا، لذا إذا أُشير إلى CTE أكثر من مرة، يُنفَّذ محتواه مرةً لكل مرجع. مرّر materialized=True إلى .cte() لإنتاج WITH <name> AS MATERIALIZED (...)، بحيث يُحسب المحتوى مرةً واحدة:
from sqlalchemy import func
from clickhouse_connect.cc_sqlalchemy import select
ranked = (
select(book.c.book_id, func.row_number().over(order_by=book.c.score.desc()).label("result_rank"))
.where(book.c.genre == "sci-fi")
.order_by(book.c.score.desc())
.limit(100)
.cte("ranked", materialized=True)
)
stmt = (
select(book.c.book_id, ranked.c.result_rank)
.select_from(book)
.ch_join(ranked, book.c.book_id == ranked.c.book_id, strictness="ANY")
.where(book.c.book_id.in_(select(ranked.c.book_id)))
.execution_options(settings={"enable_materialized_cte": 1, "enable_analyzer": 1})
)لا يُجسِّد الخادم تعبير الجدول الشائع إلا عند وجود الكلمة المفتاحية وتعيين enable_materialized_cte=1 وتمكين المحلِّل. عيّن enable_materialized_cte على التعليمة أو الاتصال أو المحرك كما هو موضح في إعدادات خاصة بكل استعلام. يكون المحلِّل مُمكّنًا افتراضيًا على كل خادم يدعم هذه الميزة، لذا يُعد تعيين enable_analyzer=1 صراحةً إجراءً احترازيًا. يُعد enable_materialized_cte إعدادًا تجريبيًا في ClickHouse. عند استخدام enable_materialized_cte=0 أو enable_analyzer=0، ينجح الاستعلام ويُرجع الصفوف نفسها. يتجاهل ClickHouse قيمة MATERIALIZED بصمت ويضمّن تعبير الجدول الشائع مجددًا، لذا فإن نسيان الإعداد يؤثر في الأداء دون ظهور أي تنبيه. تتطلب تعبيرات الجدول الشائع المُجسَّدة ClickHouse 26.3 أو إصدارًا أحدث. ترفض الخوادم الأقدم الكلمة المفتاحية باعتبارها خطأً نحويًا.
بالنسبة إلى تعليمة مُنشأة باستخدام sqlalchemy.select القياسي، استخدم cte() على مستوى الوحدة بدلًا من ذلك. تأخذ التعليمة كوسيط أول، وتكافئ Select.cte() فيما عدا ذلك:
from sqlalchemy import select as sa_select
from clickhouse_connect.cc_sqlalchemy import cte
ranked = cte(sa_select(book.c.book_id), "ranked", materialized=True)لا تظهر الكلمة المحجوزة إلا في لهجة ClickHouse، لذا يُترجم التعليمة المشتركة مع backend آخر فيها دون تغيير.
لا يدعم ClickHouse تعبيرات الجدول الشائع المادية التعاودية. تثير helpers الخاصة بـ SQLAlchemy الخطأ ValueError عند تعيين كلٍّ من recursive=True وmaterialized=True.
DDL واستكشاف البنية
يوفّر ClickHouse Connect أنواع بيانات ClickHouse، ومحركات الجداول، وبُنى القواميس، وDDL لقواعد البيانات، واستكشاف بنية الجداول.
import sqlalchemy as db
from sqlalchemy import MetaData
from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import DateTime64, String, UInt32
from clickhouse_connect.cc_sqlalchemy.ddl.custom import CreateDatabase
from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree
with engine.connect() as conn:
conn.execute(CreateDatabase("example_db", exists_ok=True))
metadata = MetaData(schema="example_db")
events = db.Table(
"events",
metadata,
db.Column("id", UInt32, primary_key=True),
db.Column("user", String),
db.Column("created_at", DateTime64(3)),
MergeTree(order_by="id"),
)
events.create(conn)
reflected = db.Table("events", MetaData(schema="example_db"), autoload_with=conn)
assert reflected.engine is not Noneتحمل الأعمدة المسترجعة server_default لتعبيرات DEFAULT، وسمات خاصة بكل dialect مثل clickhouse_codec وclickhouse_ttl وclickhouse_materialized وclickhouse_alias إن وُجدت.
تستخدم قيم السلاسل النصية في عبارات DEFAULT وMATERIALIZED وALIAS وTTL إفلات السلاسل النصية في ClickHouse. وينطبق الإفلات نفسه على تعليقات الجداول والقواميس والأعمدة، بما في ذلك التعليقات التي يصدرها Alembic.
تقبل وسائط مفاتيح MergeTree مثل order_by وpartition_by وprimary_key وsample_by وttl أعمدة SQLAlchemy وتعبيرات SQL، بالإضافة إلى السلاسل النصية العادية.
عمليات الإدراج واستخدام ORM الأساسي
عمليات إدراج Core ونماذج ORM البسيطة مدعومة. يُفضَّل استخدام عمليات إدراج Core لمسارات البيانات المجمّعة.
with engine.connect() as conn:
conn.execute(
events.insert(),
[
{"id": 13, "user": "user_1"},
{"id": 79, "user": "user_2"},
],
)import sqlalchemy as db
from sqlalchemy import MetaData
from sqlalchemy.orm import Session, declarative_base
from clickhouse_connect.cc_sqlalchemy.datatypes.sqltypes import String, UInt32
from clickhouse_connect.cc_sqlalchemy.ddl.tableengine import MergeTree
Base = declarative_base(metadata=MetaData(schema="example_db"))
class User(Base):
__tablename__ = "users"
__table_args__ = (MergeTree(order_by=["id"]),)
id = db.Column(UInt32, primary_key=True)
name = db.Column(String)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(id=13, name="user_1"))
session.bulk_save_objects([User(id=79, name="user_2")])
session.commit()ترحيلات Alembic
يتضمن ClickHouse Connect تكاملًا مع Alembic لإجراء ترحيلات مخطط ClickHouse. ثبّته باستخدام:
pip install "clickhouse-connect[alembic]"استورد clickhouse_connect.cc_sqlalchemy.alembic في ملف env.py الخاص بـ Alembic لتسجيل تكامل اللهجة. يدعم التوليد التلقائي تغييرات الجداول الشائعة، بما في ذلك إنشاء الجداول وإزالتها، وإضافة الأعمدة وتعديلها وحذفها، والقيم الافتراضية، والتعليقات. استخدم العمليات اليدوية لإعادة تسمية الجداول والأعمدة. راجع كل عملية ترحيل مولَّدة قبل تطبيقها.
تشمل أدوات op.* المساعدة الخاصة بـ ClickHouse ما يلي:
- فهارس تخطي البيانات، بما في ذلك عمليات الإضافة وmaterialize والحذف.
- الإسقاطات، بما في ذلك عمليات الإضافة وmaterialize والحذف.
- تعديل إعدادات جدول MergeTree وإعادة ضبطها.
- إنشاء materialized view وإزالتها.
- إنشاء القواميس وإزالتها وإعادة تحميلها.
فهارس تخطي البيانات في ClickHouse ليست فهارس SQLAlchemy. يتم رفض Index وColumn(index=True) وop.create_index وop.drop_index لتجنّب عبارات DDL الجزئية أو غير الصحيحة. استخدم op.add_clickhouse_index وop.drop_clickhouse_index.
راجع المثال العملي الكامل لـ Alembic. كما ينبغي للمستخدمين الذين يرحّلون من clickhouse-sqlalchemy قراءة دليل الترحيل.
النطاق والقيود
- لا يوفّر ClickHouse المعاملات التقليدية عبر لهجة HTTP هذه. ينظّم
engine.begin()وSession.commit()العمل على جانب بايثون، لكن commit و التراجع لا يُحدثان أي تأثير على الخادوم. - لا تدعم هذه اللهجة
UPDATE، والمعاملات ثنائية الطور، والتسلسلات، وRETURNING، ومستويات العزل المتقدمة. استخدم ClickHouse SQL الصريح لتنفيذ تعديلات الخادوم عند الحاجة. - يوفّر
Column(..., primary_key=True)هوية الكائن في SQLAlchemy، لكنه لا ينشئ قيد تفرد على جانب الخادوم. حدِّد تعبيرات الفرز وتعبيرات المفتاح الأساسي الاختيارية من خلال محرك الجدول. - لا تتوفر البيانات الوصفية التقليدية للمفاتيح الخارجية وقيود التفرد والفهارس القياسية، لأن ClickHouse لا يفرض هذه القيود.
- تخرج إدارة العلاقات في ORM، وتحديثات وحدة العمل، والتتابعات، والتحميل الفوري أو المؤجل للعلاقات، عن نطاق ORM المدعوم.