> ## Documentation Index
> Fetch the complete documentation index at: https://clickhouse.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> دليل حول الإجراءات المخزنة والعبارات المُحضّرة ومعلمات الاستعلام في ClickHouse

# الإجراءات المخزنة ومعلمات الاستعلام في ClickHouse

إذا كنت تستخدم قاعدة بيانات علائقية تقليدية، فقد تبحث في ClickHouse عن الإجراءات المخزنة والعبارات المُحضّرة.
يشرح هذا الدليل نهج ClickHouse تجاه هذه المفاهيم ويقدّم بدائل موصى بها.

<div id="alternatives-to-stored-procedures">
  ## بدائل الإجراءات المخزنة في ClickHouse
</div>

لا يدعم ClickHouse الإجراءات المخزنة التقليدية التي تتضمن منطق التحكم في التدفق (`IF`/`ELSE`، والحلقات، وما إلى ذلك).
وهذا قرار تصميمي مقصود يستند إلى معمارية ClickHouse باعتباره قاعدة بيانات تحليلية.
ولا يُنصح باستخدام الحلقات في قواعد البيانات التحليلية، لأن معالجة O(n) من الاستعلامات البسيطة تكون عادةً أبطأ من معالجة عدد أقل من الاستعلامات المعقدة.

تم تحسين ClickHouse من أجل:

* **أحمال العمل التحليلية** - عمليات تجميع معقدة عبر مجموعات بيانات كبيرة
* **المعالجة على دفعات** - التعامل بكفاءة مع كميات كبيرة من البيانات
* **الاستعلامات التصريحية** - استعلامات SQL التي تصف البيانات المطلوب استرجاعها، لا كيفية معالجتها

تتعارض الإجراءات المخزنة ذات المنطق الإجرائي مع أوجه التحسين هذه. وبدلًا من ذلك، يوفّر ClickHouse بدائل تتوافق مع نقاط قوته.

<div id="user-defined-functions">
  ### الدوال المعرّفة من قبل المستخدم (UDFs)
</div>

تتيح لك الدوال المعرّفة من قبل المستخدم تغليف منطق قابل لإعادة الاستخدام من دون التحكم في التدفق. يدعم ClickHouse نوعين:

<div id="lambda-based-udfs">
  #### UDFs المستندة إلى Lambda
</div>

أنشئ الدوال باستخدام تعبيرات SQL وبنية Lambda:

<Accordion title="بيانات نموذجية للأمثلة">
  ```sql theme={null}
  -- إنشاء جدول المنتجات
  CREATE TABLE products (
      product_id UInt32,
      product_name String,
      price Decimal(10, 2)
  )
  ENGINE = MergeTree()
  ORDER BY product_id;

  -- إدراج بيانات نموذجية
  INSERT INTO products (product_id, product_name, price) VALUES
  (1, 'Laptop', 899.99),
  (2, 'Wireless Mouse', 24.99),
  (3, 'USB-C Cable', 12.50),
  (4, 'Monitor', 299.00),
  (5, 'Keyboard', 79.99),
  (6, 'Webcam', 54.95),
  (7, 'Desk Lamp', 34.99),
  (8, 'External Hard Drive', 119.99),
  (9, 'Headphones', 149.00),
  (10, 'Phone Stand', 15.99);
  ```
</Accordion>

```sql theme={null}
-- Simple calculation function
CREATE FUNCTION calculate_tax AS (price, rate) -> price * rate;

SELECT
    product_name,
    price,
    calculate_tax(price, 0.08) AS tax
FROM products;
```

```sql theme={null}
-- Conditional logic using if()
CREATE FUNCTION price_tier AS (price) ->
    if(price < 100, 'Budget',
       if(price < 500, 'Mid-range', 'Premium'));

SELECT
    product_name,
    price,
    price_tier(price) AS tier
FROM products;
```

```sql theme={null}
-- String manipulation
CREATE FUNCTION format_phone AS (phone) ->
    concat('(', substring(phone, 1, 3), ') ',
           substring(phone, 4, 3), '-',
           substring(phone, 7, 4));

SELECT format_phone('5551234567');
-- Result: (555) 123-4567
```

**القيود:**

* لا توجد حلقات أو بنى تحكم معقدة في التدفق
* لا يمكن تعديل البيانات (`INSERT`/`UPDATE`/`DELETE`)
* لا يُسمح بالدوال العودية

راجع [`CREATE FUNCTION`](/docs/ar/reference/statements/create/function) للاطلاع على البنية الكاملة.

<div id="executable-udfs">
  #### دوال UDF القابلة للتنفيذ
</div>

للمنطق الأكثر تعقيدًا، استخدم دوال UDF القابلة للتنفيذ التي تستدعي برامج خارجية:

```xml theme={null}
<!-- /etc/clickhouse-server/sentiment_analysis_function.xml -->
<functions>
    <function>
        <type>executable</type>
        <name>sentiment_score</name>
        <return_type>Float32</return_type>
        <argument>
            <type>String</type>
        </argument>
        <format>TabSeparated</format>
        <command>python3 /opt/scripts/sentiment.py</command>
    </function>
</functions>
```

```sql theme={null}
-- Use the executable UDF
SELECT
    review_text,
    sentiment_score(review_text) AS score
FROM customer_reviews;
```

يمكن لـ Executable UDFs تنفيذ أي منطق مخصّص بأي لغة (بايثون، Node.js، Go، إلخ).

راجع [Executable UDFs](/docs/ar/reference/functions/regular-functions/udf) لمزيد من التفاصيل.

<div id="parameterized-views">
  ### العروض ذات المعلمات
</div>

تعمل العروض ذات المعلمات كأنها دوال تُرجِع مجموعات بيانات.
وهي مثالية للاستعلامات القابلة لإعادة الاستخدام مع التصفية الديناميكية:

<Accordion title="بيانات نموذجية لهذا المثال">
  ```sql theme={null}
  -- إنشاء جدول المبيعات
  CREATE TABLE sales (
    date Date,
    product_id UInt32,
    product_name String,
    category String,
    quantity UInt32,
    revenue Decimal(10, 2),
    sales_amount Decimal(10, 2)
  )
  ENGINE = MergeTree()
  ORDER BY (date, product_id);

  -- إدراج بيانات نموذجية
  INSERT INTO sales VALUES
  ('2024-01-05', 12345, 'Laptop Pro', 'Electronics', 2, 1799.98, 1799.98),
  ('2024-01-06', 12345, 'Laptop Pro', 'Electronics', 1, 899.99, 899.99),
  ('2024-01-10', 12346, 'Wireless Mouse', 'Electronics', 5, 124.95, 124.95),
  ('2024-01-15', 12347, 'USB-C Cable', 'Accessories', 10, 125.00, 125.00),
  ('2024-01-20', 12345, 'Laptop Pro', 'Electronics', 3, 2699.97, 2699.97),
  ('2024-01-25', 12348, 'Monitor 4K', 'Electronics', 2, 598.00, 598.00),
  ('2024-02-01', 12345, 'Laptop Pro', 'Electronics', 1, 899.99, 899.99),
  ('2024-02-05', 12349, 'Keyboard Mechanical', 'Accessories', 4, 319.96, 319.96),
  ('2024-02-10', 12346, 'Wireless Mouse', 'Electronics', 8, 199.92, 199.92),
  ('2024-02-15', 12350, 'Webcam HD', 'Electronics', 3, 164.85, 164.85);
  ```
</Accordion>

```sql theme={null}
-- Create a parameterized view
CREATE VIEW sales_by_date AS
SELECT
    date,
    product_id,
    sum(quantity) AS total_quantity,
    sum(revenue) AS total_revenue
FROM sales
WHERE date BETWEEN {start_date:Date} AND {end_date:Date}
GROUP BY date, product_id;
```

```sql theme={null}
-- Query the view with parameters
SELECT *
FROM sales_by_date(start_date='2024-01-01', end_date='2024-01-31')
WHERE product_id = 12345;
```

<div id="common-use-cases">
  #### حالات الاستخدام الشائعة
</div>

* تصفية نطاق التاريخ ديناميكيًا
* تقسيم البيانات حسب المستخدم
* [الوصول إلى البيانات في البيئات متعددة المستأجرين](/docs/ar/products/cloud/guides/best-practices/multitenancy)
* قوالب التقارير
* [إخفاء البيانات](/docs/ar/products/cloud/guides/security/data-masking)

```sql theme={null}
-- More complex parameterized view
CREATE VIEW top_products_by_category AS
SELECT
    category,
    product_name,
    revenue,
    rank
FROM (
    SELECT
        category,
        product_name,
        revenue,
        rank() OVER (PARTITION BY category ORDER BY revenue DESC) AS rank
    FROM (
        SELECT
            category,
            product_name,
            sum(sales_amount) AS revenue
        FROM sales
        WHERE category = {category:String}
            AND date >= {min_date:Date}
        GROUP BY category, product_name
    )
)
WHERE rank <= {top_n:UInt32};

-- Use it
SELECT * FROM top_products_by_category(
    category='Electronics',
    min_date='2024-01-01',
    top_n=10
);
```

راجع قسم [العروض ذات المعلمات](/docs/ar/reference/statements/create/view#parameterized-view) لمزيد من المعلومات.

<div id="materialized-views">
  ### العروض المادية
</div>

تُعد العروض المادية مثالية للحساب المسبق لعمليات التجميع المكلفة التي كانت تُنفَّذ تقليديًا داخل الإجراءات المخزنة. إذا كنت معتادًا على قواعد البيانات التقليدية، فاعتبر العرض المادي **مُشغِّل INSERT** يحوِّل البيانات ويجمعها تلقائيًا عند إدراجها في جدول المصدر:

```sql theme={null}
-- Source table
CREATE TABLE page_views (
    user_id UInt64,
    page String,
    timestamp DateTime,
    session_id String
)
ENGINE = MergeTree()
ORDER BY (user_id, timestamp);

-- Materialized view that maintains aggregated statistics
CREATE MATERIALIZED VIEW daily_user_stats
ENGINE = SummingMergeTree()
ORDER BY (date, user_id)
AS SELECT
    toDate(timestamp) AS date,
    user_id,
    count() AS page_views,
    uniq(session_id) AS sessions,
    uniq(page) AS unique_pages
FROM page_views
GROUP BY date, user_id;

-- Insert sample data into source table
INSERT INTO page_views VALUES
(101, '/home', '2024-01-15 10:00:00', 'session_a1'),
(101, '/products', '2024-01-15 10:05:00', 'session_a1'),
(101, '/checkout', '2024-01-15 10:10:00', 'session_a1'),
(102, '/home', '2024-01-15 11:00:00', 'session_b1'),
(102, '/about', '2024-01-15 11:05:00', 'session_b1'),
(101, '/home', '2024-01-16 09:00:00', 'session_a2'),
(101, '/products', '2024-01-16 09:15:00', 'session_a2'),
(103, '/home', '2024-01-16 14:00:00', 'session_c1'),
(103, '/products', '2024-01-16 14:05:00', 'session_c1'),
(103, '/products', '2024-01-16 14:10:00', 'session_c1'),
(102, '/home', '2024-01-17 10:30:00', 'session_b2'),
(102, '/contact', '2024-01-17 10:35:00', 'session_b2');

-- Query pre-aggregated data
SELECT
    user_id,
    sum(page_views) AS total_views,
    sum(sessions) AS total_sessions
FROM daily_user_stats
WHERE date BETWEEN '2024-01-01' AND '2024-01-31'
GROUP BY user_id;
```

<div id="refreshable-materialized-views">
  #### العروض المادية القابلة للتحديث
</div>

للمعالجة الدفعية المجدولة (مثل الإجراءات المخزنة الليلية):

```sql theme={null}
-- Automatically refresh every day at 2 AM
CREATE MATERIALIZED VIEW monthly_sales_report
REFRESH EVERY 1 DAY OFFSET 2 HOUR
AS SELECT
    toStartOfMonth(order_date) AS month,
    region,
    product_category,
    count() AS order_count,
    sum(amount) AS total_revenue,
    avg(amount) AS avg_order_value
FROM orders
WHERE order_date >= today() - INTERVAL 13 MONTH
GROUP BY month, region, product_category;

-- Query always has fresh data
SELECT * FROM monthly_sales_report
WHERE month = toStartOfMonth(today());
```

راجع [العروض المادية المتتالية](/docs/ar/concepts/features/materialized-views/cascading-materialized-views) للاطلاع على الأنماط المتقدمة.

<div id="external-orchestration">
  ### تنسيق العمليات خارجيًا
</div>

عند التعامل مع منطق أعمال معقّد، أو سير عمل ETL، أو عمليات متعددة الخطوات، يمكن دائمًا تنفيذ هذا المنطق خارج ClickHouse
باستخدام مكتبات العملاء للغات البرمجة.

<div id="using-application-code">
  #### استخدام شيفرة التطبيق
</div>

فيما يلي مقارنة جنبًا إلى جنب توضّح كيفية تحويل إجراء مخزّن في MySQL إلى شيفرة تطبيق عند استخدام ClickHouse:

<Tabs>
  <Tab title="الإجراء المخزن في MySQL">
    ```sql theme={null}
    DELIMITER $$

    CREATE PROCEDURE process_order(
        IN p_order_id INT,
        IN p_customer_id INT,
        IN p_order_total DECIMAL(10,2),
        OUT p_status VARCHAR(50),
        OUT p_loyalty_points INT
    )
    BEGIN
        DECLARE v_customer_tier VARCHAR(20);
        DECLARE v_previous_orders INT;
        DECLARE v_discount DECIMAL(10,2);

        -- Start transaction
        START TRANSACTION;

        -- Get customer information
        SELECT tier, total_orders
        INTO v_customer_tier, v_previous_orders
        FROM customers
        WHERE customer_id = p_customer_id;

        -- Calculate discount based on tier
        IF v_customer_tier = 'gold' THEN
            SET v_discount = p_order_total * 0.15;
        ELSEIF v_customer_tier = 'silver' THEN
            SET v_discount = p_order_total * 0.10;
        ELSE
            SET v_discount = 0;
        END IF;

        -- Insert order record
        INSERT INTO orders (order_id, customer_id, order_total, discount, final_amount)
        VALUES (p_order_id, p_customer_id, p_order_total, v_discount,
                p_order_total - v_discount);

        -- Update customer statistics
        UPDATE customers
        SET total_orders = total_orders + 1,
            lifetime_value = lifetime_value + (p_order_total - v_discount),
            last_order_date = NOW()
        WHERE customer_id = p_customer_id;

        -- Calculate loyalty points (1 point per dollar)
        SET p_loyalty_points = FLOOR(p_order_total - v_discount);

        -- Insert loyalty points transaction
        INSERT INTO loyalty_points (customer_id, points, transaction_date, description)
        VALUES (p_customer_id, p_loyalty_points, NOW(),
                CONCAT('Order #', p_order_id));

        -- Check if customer should be upgraded
        IF v_previous_orders + 1 >= 10 AND v_customer_tier = 'bronze' THEN
            UPDATE customers SET tier = 'silver' WHERE customer_id = p_customer_id;
            SET p_status = 'ORDER_COMPLETE_TIER_UPGRADED_SILVER';
        ELSEIF v_previous_orders + 1 >= 50 AND v_customer_tier = 'silver' THEN
            UPDATE customers SET tier = 'gold' WHERE customer_id = p_customer_id;
            SET p_status = 'ORDER_COMPLETE_TIER_UPGRADED_GOLD';
        ELSE
            SET p_status = 'ORDER_COMPLETE';
        END IF;

        COMMIT;
    END$$

    DELIMITER ;

    -- Call the stored procedure
    CALL process_order(12345, 5678, 250.00, @status, @points);
    SELECT @status, @points;
    ```
  </Tab>

  <Tab title="شيفرة التطبيق في ClickHouse">
    <Info>
      **معلمات الاستعلام**

      يستخدم المثال أدناه معلمات الاستعلام في ClickHouse.
      انتقل مباشرة إلى ["بدائل العبارات المُحضّرة في ClickHouse"](/docs/ar/guides/clickhouse/data-modelling/stored-procedures-and-prepared-statements#alternatives-to-prepared-statements-in-clickhouse)
      إذا لم تكن معتادًا بعد على معلمات الاستعلام في ClickHouse.
    </Info>

    ```python theme={null}
    # Python example using clickhouse-connect
    import clickhouse_connect
    from datetime import datetime
    from decimal import Decimal

    client = clickhouse_connect.get_client(host='localhost')

    def process_order(order_id: int, customer_id: int, order_total: Decimal) -> tuple[str, int]:
        """
        Processes an order with business logic that would be in a stored procedure.
        Returns: (status_message, loyalty_points)

        Note: ClickHouse is optimized for analytics, not OLTP transactions.
        For transactional workloads, use an OLTP database (PostgreSQL, MySQL)
        and sync analytics data to ClickHouse for reporting.
        """

        # Step 1: Get customer information
        result = client.query(
            """
            SELECT tier, total_orders
            FROM customers
            WHERE customer_id = {cid: UInt32}
            """,
            parameters={'cid': customer_id}
        )

        if not result.result_rows:
            raise ValueError(f"Customer {customer_id} not found")

        customer_tier, previous_orders = result.result_rows[0]

        # Step 2: Calculate discount based on tier (business logic in Python)
        discount_rates = {'gold': 0.15, 'silver': 0.10, 'bronze': 0.0}
        discount = order_total * Decimal(str(discount_rates.get(customer_tier, 0.0)))
        final_amount = order_total - discount

        # Step 3: Insert order record
        client.command(
            """
            INSERT INTO orders (order_id, customer_id, order_total, discount,
                               final_amount, order_date)
            VALUES ({oid: UInt32}, {cid: UInt32}, {total: Decimal64(2)},
                    {disc: Decimal64(2)}, {final: Decimal64(2)}, now())
            """,
            parameters={
                'oid': order_id,
                'cid': customer_id,
                'total': float(order_total),
                'disc': float(discount),
                'final': float(final_amount)
            }
        )

        # Step 4: Calculate new customer statistics
        new_order_count = previous_orders + 1

        # For analytics databases, prefer INSERT over UPDATE
        # This uses a ReplacingMergeTree pattern
        client.command(
            """
            INSERT INTO customers (customer_id, tier, total_orders, last_order_date,
                                  update_time)
            SELECT
                customer_id,
                tier,
                {new_count: UInt32} AS total_orders,
                now() AS last_order_date,
                now() AS update_time
            FROM customers
            WHERE customer_id = {cid: UInt32}
            """,
            parameters={'cid': customer_id, 'new_count': new_order_count}
        )

        # Step 5: Calculate and record loyalty points
        loyalty_points = int(final_amount)

        client.command(
            """
            INSERT INTO loyalty_points (customer_id, points, transaction_date, description)
            VALUES ({cid: UInt32}, {pts: Int32}, now(),
                    {desc: String})
            """,
            parameters={
                'cid': customer_id,
                'pts': loyalty_points,
                'desc': f'Order #{order_id}'
            }
        )

        # Step 6: Check for tier upgrade (business logic in Python)
        status = 'ORDER_COMPLETE'

        if new_order_count >= 10 and customer_tier == 'bronze':
            # Upgrade to silver
            client.command(
                """
                INSERT INTO customers (customer_id, tier, total_orders, last_order_date,
                                      update_time)
                SELECT
                    customer_id, 'silver' AS tier, total_orders, last_order_date,
                    now() AS update_time
                FROM customers
                WHERE customer_id = {cid: UInt32}
                """,
                parameters={'cid': customer_id}
            )
            status = 'ORDER_COMPLETE_TIER_UPGRADED_SILVER'

        elif new_order_count >= 50 and customer_tier == 'silver':
            # Upgrade to gold
            client.command(
                """
                INSERT INTO customers (customer_id, tier, total_orders, last_order_date,
                                      update_time)
                SELECT
                    customer_id, 'gold' AS tier, total_orders, last_order_date,
                    now() AS update_time
                FROM customers
                WHERE customer_id = {cid: UInt32}
                """,
                parameters={'cid': customer_id}
            )
            status = 'ORDER_COMPLETE_TIER_UPGRADED_GOLD'

        return status, loyalty_points

    # Use the function
    status, points = process_order(
        order_id=12345,
        customer_id=5678,
        order_total=Decimal('250.00')
    )

    print(f"Status: {status}, Loyalty Points: {points}")
    ```
  </Tab>
</Tabs>

<br />

<div id="key-differences">
  #### الاختلافات الرئيسية
</div>

1. **التحكم في التدفق** - تستخدم الإجراءات المخزنة في MySQL تعليمات `IF/ELSE` وحلقات `WHILE`. في ClickHouse، نفّذ هذا المنطق في شيفرة التطبيق لديك (بايثون، Java، إلخ.)
2. **المعاملات** - يدعم MySQL أوامر `BEGIN/COMMIT/ROLLBACK` لمعاملات ACID. أما ClickHouse فهي قاعدة بيانات تحليلية مُحسّنة لأعباء العمل المعتمدة على الإلحاق فقط، وليست مخصّصة للتحديثات المعاملاتية
3. **التحديثات** - يستخدم MySQL عبارات `UPDATE`. بينما يفضّل ClickHouse استخدام `INSERT` مع [ReplacingMergeTree](/docs/ar/reference/engines/table-engines/mergetree-family/replacingmergetree) أو [CollapsingMergeTree](/docs/ar/reference/engines/table-engines/mergetree-family/collapsingmergetree) للبيانات القابلة للتغيير
4. **المتغيرات والحالة** - يمكن للإجراءات المخزنة في MySQL تعريف متغيرات (`DECLARE v_discount`). في ClickHouse، أدر الحالة في شيفرة التطبيق لديك
5. **معالجة الأخطاء** - يدعم MySQL `SIGNAL` ومعالجات الاستثناءات. وفي شيفرة التطبيق، استخدم آليات معالجة الأخطاء الأصلية في لغتك (try/catch)

<Tip>
  **متى تستخدم كل نهج:**

  * **أعباء عمل OLTP** (الطلبات، المدفوعات، حسابات المستخدمين) → استخدم MySQL/PostgreSQL مع الإجراءات المخزنة
  * **أعباء عمل التحليلات** (التقارير، التجميعات، السلاسل الزمنية) → استخدم ClickHouse مع التنسيق على مستوى التطبيق
  * **المعمارية الهجينة** → استخدم كليهما! ابثّ البيانات المعاملاتية من OLTP إلى ClickHouse لأغراض التحليلات
</Tip>

<div id="using-workflow-orchestration-tools">
  #### استخدام أدوات تنسيق سير العمل
</div>

* **Apache Airflow** - جدولة مخططات DAG المعقدة لاستعلامات ClickHouse ومراقبتها
* **dbt** - تحويل البيانات باستخدام مهام سير عمل قائمة على SQL
* **Prefect/Dagster** - تنسيق حديث قائم على بايثون
* **Custom schedulers** - مهام Cron وCronJobs في Kubernetes وما إلى ذلك.

**فوائد التنسيق الخارجي:**

* إمكانات كاملة للغات البرمجة
* معالجة أفضل للأخطاء ومنطق أفضل لإعادة المحاولة
* التكامل مع الأنظمة الخارجية (واجهات برمجة التطبيقات، وقواعد البيانات الأخرى)
* التحكم في الإصدارات والاختبار
* المراقبة والتنبيهات
* جدولة أكثر مرونة

<div id="alternatives-to-prepared-statements-in-clickhouse">
  ## بدائل العبارات المُحضَّرة في ClickHouse
</div>

مع أن ClickHouse لا يدعم "العبارات المُحضَّرة" التقليدية بالمفهوم المتعارف عليه في أنظمة إدارة قواعد البيانات العلائقية، فإنه يوفّر **معلمات الاستعلام** التي تؤدي الغرض نفسه: استعلامات آمنة مُعلَّمة بمعلمات وتمنع حقن SQL.

<div id="query-parameters-syntax">
  ### الصيغة
</div>

هناك طريقتان لتحديد معلمات الاستعلام:

<div id="method-1-using-set">
  #### الطريقة 1: استخدام `SET`
</div>

<Accordion title="مثال على الجدول والبيانات">
  ```sql theme={null}
  -- إنشاء جدول user_events (بناء جملة ClickHouse)
  CREATE TABLE user_events (
      event_id UInt32,
      user_id UInt64,
      event_name String,
      event_date Date,
      event_timestamp DateTime
  ) ENGINE = MergeTree()
  ORDER BY (user_id, event_date);

  -- إدراج بيانات نموذجية لعدة مستخدمين وأحداث
  INSERT INTO user_events (event_id, user_id, event_name, event_date, event_timestamp) VALUES
  (1, 12345, 'page_view', '2024-01-05', '2024-01-05 10:30:00'),
  (2, 12345, 'page_view', '2024-01-05', '2024-01-05 10:35:00'),
  (3, 12345, 'add_to_cart', '2024-01-05', '2024-01-05 10:40:00'),
  (4, 12345, 'page_view', '2024-01-10', '2024-01-10 14:20:00'),
  (5, 12345, 'add_to_cart', '2024-01-10', '2024-01-10 14:25:00'),
  (6, 12345, 'purchase', '2024-01-10', '2024-01-10 14:30:00'),
  (7, 12345, 'page_view', '2024-01-15', '2024-01-15 09:15:00'),
  (8, 12345, 'page_view', '2024-01-15', '2024-01-15 09:20:00'),
  (9, 12345, 'page_view', '2024-01-20', '2024-01-20 16:45:00'),
  (10, 12345, 'add_to_cart', '2024-01-20', '2024-01-20 16:50:00'),
  (11, 12345, 'purchase', '2024-01-25', '2024-01-25 11:10:00'),
  (12, 12345, 'page_view', '2024-01-28', '2024-01-28 13:30:00'),
  (13, 67890, 'page_view', '2024-01-05', '2024-01-05 11:00:00'),
  (14, 67890, 'add_to_cart', '2024-01-05', '2024-01-05 11:05:00'),
  (15, 67890, 'purchase', '2024-01-05', '2024-01-05 11:10:00'),
  (16, 12345, 'page_view', '2024-02-01', '2024-02-01 10:00:00'),
  (17, 12345, 'add_to_cart', '2024-02-01', '2024-02-01 10:05:00');
  ```
</Accordion>

```sql theme={null}
SET param_user_id = 12345;
SET param_start_date = '2024-01-01';
SET param_end_date = '2024-01-31';

SELECT
    event_name,
    count() AS event_count
FROM user_events
WHERE user_id = {user_id: UInt64}
    AND event_date BETWEEN {start_date: Date} AND {end_date: Date}
GROUP BY event_name;
```

<div id="method-2-using-cli-parameters">
  #### الطريقة 2: استخدام معلَمات CLI
</div>

```bash theme={null}
clickhouse-client \
    --param_user_id=12345 \
    --param_start_date='2024-01-01' \
    --param_end_date='2024-01-31' \
    --query="SELECT count() FROM user_events
             WHERE user_id = {user_id: UInt64}
             AND event_date BETWEEN {start_date: Date} AND {end_date: Date}"
```

<div id="parameter-syntax">
  ### بنية المعاملات
</div>

يُشار إلى المعاملات باستخدام: `{parameter_name: DataType}`

* `parameter_name` - اسم المعامل (من دون البادئة `param_`)
* `DataType` - نوع بيانات ClickHouse الذي يُحوَّل المعامل إليه

<div id="data-type-examples">
  ### أمثلة على أنواع البيانات
</div>

<Accordion title="الجداول وبيانات العينة لهذا المثال">
  ```sql theme={null}
  -- 1. أنشئ جدولًا لاختبارات السلاسل النصية والأرقام
  CREATE TABLE IF NOT EXISTS users (
      name String,
      age UInt8,
      salary Float64
  ) ENGINE = Memory;

  INSERT INTO users VALUES
      ('John Doe', 25, 75000.50),
      ('Jane Smith', 30, 85000.75),
      ('Peter Jones', 20, 50000.00);

  -- 2. أنشئ جدولًا لاختبارات التاريخ والطابع الزمني
  CREATE TABLE IF NOT EXISTS events (
      event_date Date,
      event_timestamp DateTime
  ) ENGINE = Memory;

  INSERT INTO events VALUES
      ('2024-01-15', '2024-01-15 14:30:00'),
      ('2024-01-15', '2024-01-15 15:00:00'),
      ('2024-01-16', '2024-01-16 10:00:00');

  -- 3. أنشئ جدولًا لاختبارات المصفوفات
  CREATE TABLE IF NOT EXISTS products (
      id UInt32,
      name String
  ) ENGINE = Memory;

  INSERT INTO products VALUES (1, 'Laptop'), (2, 'Monitor'), (3, 'Mouse'), (4, 'Keyboard');

  -- 4. أنشئ جدولًا لاختبارات Map (الشبيهة بـ struct)
  CREATE TABLE IF NOT EXISTS accounts (
      user_id UInt32,
      status String,
      type String
  ) ENGINE = Memory;

  INSERT INTO accounts VALUES
      (101, 'active', 'premium'),
      (102, 'inactive', 'basic'),
      (103, 'active', 'basic');

  -- 5. أنشئ جدولًا لاختبارات Identifier
  CREATE TABLE IF NOT EXISTS sales_2024 (
      value UInt32
  ) ENGINE = Memory;

  INSERT INTO sales_2024 VALUES (100), (200), (300);
  ```
</Accordion>

<Tabs>
  <Tab title="السلاسل النصية والأرقام">
    ```sql theme={null}
    SET param_name = 'John Doe';
    SET param_age = 25;
    SET param_salary = 75000.50;

    SELECT name, age, salary FROM users
    WHERE name = {name: String}
      AND age >= {age: UInt8}
      AND salary <= {salary: Float64};
    ```
  </Tab>

  <Tab title="التواريخ والأوقات">
    ```sql theme={null}
    SET param_date = '2024-01-15';
    SET param_timestamp = '2024-01-15 14:30:00';

    SELECT * FROM events
    WHERE event_date = {date: Date}
       OR event_timestamp > {timestamp: DateTime};
    ```
  </Tab>

  <Tab title="المصفوفات">
    ```sql theme={null}
    SET param_ids = [1, 2, 3, 4, 5];

    SELECT * FROM products WHERE id IN {ids: Array(UInt32)};
    ```
  </Tab>

  <Tab title="الخرائط">
    ```sql theme={null}
    SET param_filters = {'target_status': 'active'};

    SELECT user_id, status, type FROM accounts
    WHERE status = arrayElement(
        mapValues({filters: Map(String, String)}),
        indexOf(mapKeys({filters: Map(String, String)}), 'target_status')
    );
    ```
  </Tab>

  <Tab title="المعرّفات">
    ```sql theme={null}
    SET param_table = 'sales_2024';

    SELECT count() FROM {table: Identifier};
    ```
  </Tab>
</Tabs>

<br />

لاستخدام معلَمات الاستعلام في [مكتبات العملاء للغات البرمجة](/docs/ar/integrations/language-clients/index)، راجع وثائق
مكتبة العميل الخاصة بلغة البرمجة التي تهمك.

<div id="limitations-of-query-parameters">
  ### قيود معلمات الاستعلام
</div>

معلمات الاستعلام **ليست بدائل نصية عامة**، بل لها قيود محددة:

1. هي **مخصصة أساسًا لعبارات SELECT** - وأفضل دعم لها يكون في استعلامات SELECT
2. هي **تعمل كمُعرّفات أو قيم حرفية** - ولا يمكنها أن تحل محل أجزاء SQL عشوائية
3. لديها **دعم محدود لـ DDL** - فهي مدعومة في `CREATE TABLE`، ولكن ليس في `ALTER TABLE`

**ما ينجح:**

```sql theme={null}
-- ✓ Values in WHERE clause
SELECT * FROM users WHERE id = {user_id: UInt64};

-- ✓ Table/database names
SELECT * FROM {db: Identifier}.{table: Identifier};

-- ✓ Values in IN clause
SELECT * FROM products WHERE id IN {ids: Array(UInt32)};

-- ✓ CREATE TABLE
CREATE TABLE {table_name: Identifier} (id UInt64, name String) ENGINE = MergeTree() ORDER BY id;
```

**ما الذي لا يعمل:**

```sql theme={null}
-- ✗ Column names in SELECT (use Identifier carefully)
SELECT {column: Identifier} FROM users;  -- Limited support

-- ✗ Arbitrary SQL fragments
SELECT * FROM users {where_clause: String};  -- NOT SUPPORTED

-- ✗ ALTER TABLE statements
ALTER TABLE {table: Identifier} ADD COLUMN new_col String;  -- NOT SUPPORTED

-- ✗ Multiple statements
{statements: String};  -- NOT SUPPORTED
```

<div id="security-best-practices">
  ### أفضل الممارسات الأمنية
</div>

**استخدم دائمًا معلمات الاستعلام لأي إدخال من المستخدم:**

```python theme={null}
# ✓ SAFE - Uses parameters
user_input = request.get('user_id')
result = client.query(
    "SELECT * FROM orders WHERE user_id = {uid: UInt64}",
    parameters={'uid': user_input}
)

# ✗ DANGEROUS - SQL injection risk!
user_input = request.get('user_id')
result = client.query(f"SELECT * FROM orders WHERE user_id = {user_input}")
```

**تحقّق من أنواع الإدخال:**

```python theme={null}
def get_user_orders(user_id: int, start_date: str):
    # Validate types before querying
    if not isinstance(user_id, int) or user_id <= 0:
        raise ValueError("Invalid user_id")

    # Parameters enforce type safety
    return client.query(
        """
        SELECT * FROM orders
        WHERE user_id = {uid: UInt64}
            AND order_date >= {start: Date}
        """,
        parameters={'uid': user_id, 'start': start_date}
    )
```

<div id="mysql-protocol-prepared-statements">
  ### العبارات المُحضَّرة في بروتوكول MySQL
</div>

تتضمن [واجهة MySQL](/docs/ar/concepts/features/interfaces/mysql) في ClickHouse حدًا أدنى من دعم العبارات المُحضَّرة (`COM_STMT_PREPARE`, `COM_STMT_EXECUTE`, `COM_STMT_CLOSE`)، وذلك أساسًا لإتاحة الاتصال بأدوات مثل Tableau Online التي تُغلّف الاستعلامات داخل عبارات مُحضَّرة.

**القيود الأساسية:**

* **ربط المَعلمات غير مدعوم** - لا يمكنك استخدام العناصر النائبة `?` مع مَعلمات مرتبطة
* تُخزَّن الاستعلامات، لكنها لا تُحلَّل أثناء `PREPARE`
* التنفيذ محدود جدًا ومصمم للتوافق مع أدوات BI محددة

**مثال على ما لا يعمل:**

```sql theme={null}
-- This MySQL-style prepared statement with parameters does NOT work in ClickHouse
PREPARE stmt FROM 'SELECT * FROM users WHERE id = ?';
EXECUTE stmt USING @user_id;  -- Parameter binding not supported
```

<Tip>
  **استخدم معلمات الاستعلام الأصلية في ClickHouse بدلًا من ذلك.** فهي توفّر دعمًا كاملًا لربط المعلمات، وأمانًا للأنواع، وحماية من حقن SQL عبر جميع واجهات ClickHouse:

  ```sql theme={null}
  -- معلمات الاستعلام الأصلية في ClickHouse (موصى بها)
  SET param_user_id = 12345;
  SELECT * FROM users WHERE id = {user_id: UInt64};
  ```
</Tip>

لمزيد من التفاصيل، راجع [وثائق واجهة MySQL](/docs/ar/concepts/features/interfaces/mysql) و[منشور المدونة حول دعم MySQL](https://clickhouse.com/blog/mysql-support-in-clickhouse-the-journey).

<div id="summary">
  ## الخلاصة
</div>

<div id="summary-stored-procedures">
  ### بدائل ClickHouse للإجراءات المخزنة
</div>

| نمط الإجراءات المخزنة التقليدي                    | بديل ClickHouse                                                    |
| ------------------------------------------------- | ------------------------------------------------------------------ |
| الحسابات والتحويلات البسيطة                       | الدوال المعرّفة من قبل المستخدم (UDFs)                             |
| الاستعلامات ذات المعلمات القابلة لإعادة الاستخدام | العروض ذات المعلمات                                                |
| التجميعات المحسوبة مسبقًا                         | العروض المادية                                                     |
| المعالجة الدفعية المجدولة                         | العروض المادية القابلة للتحديث                                     |
| مهام ETL المعقدة متعددة الخطوات                   | العروض المادية المتسلسلة أو التنسيق الخارجي (بايثون, Airflow, dbt) |
| منطق الأعمال مع التحكم في التدفق                  | شيفرة التطبيق                                                      |

<div id="summary-query-parameters">
  ### استخدام معلمات الاستعلام
</div>

يمكن استخدام معلمات الاستعلام للأغراض التالية:

* منع حقن SQL
* الاستعلامات المُعلَّمة مع ضمان سلامة الأنواع
* التصفية الديناميكية في التطبيقات
* قوالب استعلام قابلة لإعادة الاستخدام

<div id="related-documentation">
  ## الوثائق ذات الصلة
</div>

* [`CREATE FUNCTION`](/docs/ar/reference/statements/create/function) - الدوال المعرّفة من قبل المستخدم
* [`CREATE VIEW`](/docs/ar/reference/statements/create/view) - العروض، بما في ذلك العروض ذات المعلمات والعروض المادية
* [بنية SQL - معلمات الاستعلام](/docs/ar/reference/syntax#defining-and-using-query-parameters) - البنية الكاملة للمعلمات
* [العروض المادية المتتالية](/docs/ar/concepts/features/materialized-views/cascading-materialized-views) - أنماط متقدمة للعروض المادية
* [دوال UDF قابلة للتنفيذ](/docs/ar/reference/functions/regular-functions/udf) - تنفيذ الدوال الخارجية
