الاستعلامات الفرعية في Azure Cosmos DB ل NoSQL

ينطبق على: NoSQL

الاستعلام الفرعي هو استعلام متداخل داخل استعلام آخر داخل Azure Cosmos DB ل NoSQL. يسمى الاستعلام الفرعي أيضا استعلام داخلي أو داخلي SELECT. عادة ما تسمى العبارة التي تحتوي على استعلام فرعي استعلام خارجي.

أنواع الاستعلامات الفرعية

يوجد نوعان رئيسيان من الاستعلامات الفرعية:

  • مترابط: استعلام فرعي يُشير إلى قيم من الاستعلام الخارجي. يتم تقييم الاستعلام الفرعي مرة واحدة لكل صف يعالجه الاستعلام الخارجي.
  • غير مترابط:استعلام فرعي مستقل عن الاستعلام الخارجي. يمكن تشغيله من تلقائيًا دون الاعتماد على الاستعلام الخارجي.

إشعار

تدعم قاعدة بيانات Azure Cosmos الاستعلامات الفرعية المترابطة فقط.

يمكن أيضًا تصنيف الاستعلامات الفرعية استنادًا إلى عدد الصفوف والأعمدة التي ترجعها. توجد ثلاثة أنواع:

  • الجدول: يُرجع صفوف متعددة وأعمدة متعددة.
  • متعدد القيم: يُرجع صفوف متعددة وعمود واحد.
  • عددي: يُرجع صف واحد وعمود واحد.

دائما ما ترجع الاستعلامات في Azure Cosmos DB ل NoSQL عمودا واحدا (إما قيمة بسيطة أو عنصر معقد). لذلك، يمكن تطبيق الاستعلامات الفرعية متعددة القيم والعددية فقط. يمكنك استخدام استعلام فرعي متعدد القيم فقط في العبارة FROM كتعبير علائقي. يمكنك استخدام استعلام فرعي عددي كتعبير عددي في SELECT عبارة أو WHERE ، أو كتعبير علائقي في العبارة FROM .

الاستعلامات الفرعية متعددة القيم

ترجع الاستعلامات الفرعية متعددة القيم مجموعة من العناصر وتستخدم دائما ضمن FROM عبارة . تُستخدم من أجل:

  • JOIN تحسين تعبيرات (الانضمام الذاتي).
  • تقييم التعبيرات باهظة الثمن مرة واحدة، والرجوع عدة مرات.

تحسين تعبيرات الانضمام الذاتي

يمكن أن تحسن JOIN الاستعلامات الفرعية متعددة القيم التعبيرات عن طريق دفع دالات التقييم بعد كل تعبير select-many بدلا من بعد جميع الصلات المتقاطعة في العبارة WHERE .

خذ بعين الاعتبار الاستعلام التالي:

SELECT VALUE
    COUNT(1)
FROM
    products p
JOIN 
    t in p.tags
JOIN 
    q in p.onHandQuantities
JOIN 
    s in p.warehouseStock
WHERE 
    t.name IN ("winter", "fall") AND
    (q.quantity BETWEEN 0 AND 10) AND
    NOT s.backstock

بالنسبة لهذا الاستعلام، يطابق الفهرس أي عنصر يحتوي على علامة إما name "الشتاء" أو "الخريف"، واحد quantity على الأقل بين صفر وعشرة، ومستودع واحد على الأقل حيث backstock يكون .false JOIN يقوم التعبير هنا بتنفيذ المنتج المتقاطع لكافة عناصر tagsالصفائف onHandQuantitiesو و warehouseStock لكل عنصر مطابق قبل تطبيق أي عامل تصفية.

WHERE ثم تطبق العبارة دالة تقييم عامل التصفية على كل <c, t, n, s> مجموعة. على سبيل المثال، إذا كان العنصر المطابق يحتوي على عشرة عناصر في كل صفيف من الصفائف الثلاثة، فإنه يتوسع إلى 1 x 10 x 10 x 10 (أي 1000) مجموعة. يُمكن أن يُساعد استخدام الاستعلامات الفرعية هنا على تصفية عناصر الصفيف المرتبطة قبل الانضمام إلى التعبير التالي.

هذا الاستعلام مكافئ إلى الاستعلام السابق ولكنه يستخدم الاستعلامات الفرعية:

SELECT VALUE
    COUNT(1)
FROM
    products p
JOIN 
    (SELECT VALUE t FROM t IN p.tags WHERE t.name IN ("winter", "fall"))
JOIN 
    (SELECT VALUE q FROM q IN p.onHandQuantities WHERE q.quantity BETWEEN 0 AND 10)
JOIN 
    (SELECT VALUE s FROM s IN p.warehouseStock WHERE NOT s.backstock)

افترض أن عنصرا واحدا فقط في صفيف العلامات يطابق عامل التصفية، وهناك خمسة عناصر لكل من صفائف الكمية والمخزون. ثم يتم JOIN توسيع التعبيرات إلى 1 x 1 x 5 x 5 (25) عنصر، مقابل 1000 عنصر في الاستعلام الأول.

تقييم مرة واحدة، والرجوع عدة مرات

يمكن أن تساعد الاستعلامات الفرعية في تحسين الاستعلامات باستخدام تعبيرات باهظة الثمن؛ مثل: الوظائف المُعرفة من قبل المستخدم (UDFs)، أو سلاسل مُعقدة، أو تعبيرات حسابية. يمكنك استخدام استعلام فرعي مع تعبير JOIN لتقييم التعبير مرة واحدة ولكن الرجوع إليه عدة مرات.

لنفترض أن لديك UDF التالي (getTotalWithTax) معرفا.

function getTotalWithTax(subTotal){
  return subTotal * 1.25;
}

يقوم الاستعلام التالي بتشغيل UDF getTotalWithTax عدة مرات:

SELECT VALUE {
    subtotal: p.price,
    total: udf.getTotalWithTax(p.price)
}
FROM
    products p
WHERE
    udf.getTotalWithTax(p.price) < 22.25

فيما يلي استعلام مكافئ يقوم بتشغيل تناسق القرص الشامل مرة واحدة فقط:

SELECT VALUE {
    subtotal: p.price,
    total: totalPrice
}
FROM
    products p
JOIN
    (SELECT VALUE udf.getTotalWithTax(p.price)) totalPrice
WHERE
    totalPrice < 22.25

تلميح

ضع في اعتبارك سلوك التعبيرات عبر JOIN المنتجات. إذا كان يمكن تقييم تعبير UDF إلى undefined، يجب التأكد من أن JOIN التعبير ينتج دائما صفا واحدا عن طريق إرجاع كائن من الاستعلام الفرعي بدلا من القيمة مباشرة.

تقليد الضم إلى البيانات المرجعية الخارجية

قد تحتاج غالبا إلى الرجوع إلى البيانات الثابتة التي نادرا ما تتغير، مثل وحدات القياس. من المثالي عدم تكرار البيانات الثابتة لكل عنصر في استعلام. يؤدي تجنب هذا التكرار إلى حفظ التخزين وتحسين أداء الكتابة عن طريق الاحتفاظ بحجم العنصر الفردي أصغر. يمكنك استخدام استعلام فرعي لمحاكاة دلالات الصلة الداخلية بمجموعة من البيانات المرجعية الثابتة.

على سبيل المثال، ضع في اعتبارك هذه المجموعة من القياسات:

الاسم مضاعف الوحدة الأساسية
ng نانوغرام 1.00E-09 غرام
µg ميكروغرام 1.00E-06 غرام
mg مليغرام 1.00E-03 غرام
g غرام 1.00E+00 غرام
kg كيلوغرام 1.00E+03 غرام
Mg ميجاغرام 1.00E+06 غرام
Gg جيجاغرام 1.00E+09 غرام

يحاكي الاستعلام التالي الانضمام مع هذه البيانات؛ بحيث يمكنك إضافة اسم الوحدة إلى الإخراج:

SELECT
    s.id,
    (s.weight.quantity * m.multiplier) AS calculatedWeight,
    m.unit AS unitOfWeight
FROM
    shipments s
JOIN m IN (
    SELECT VALUE [
        {unit: 'ng', name: 'nanogram', multiplier: 0.000000001, baseUnit: 'gram'},
        {unit: 'µg', name: 'microgram', multiplier: 0.000001, baseUnit: 'gram'},
        {unit: 'mg', name: 'milligram', multiplier: 0.001, baseUnit: 'gram'},
        {unit: 'g', name: 'gram', multiplier: 1, baseUnit: 'gram'},
        {unit: 'kg', name: 'kilogram', multiplier: 1000, baseUnit: 'gram'},
        {unit: 'Mg', name: 'megagram', multiplier: 1000000, baseUnit: 'gram'},
        {unit: 'Gg', name: 'gigagram', multiplier: 1000000000, baseUnit: 'gram'}
    ]
)
WHERE
    s.weight.units = m.unit

الاستعلام الفرعي الرقمي

تعبير الاستعلام الفرعي العددي هو استعلام فرعي الذي يقيم قيمة مفردة. قيمة تعبير الاستعلام الفرعي العددي هي قيمة الإسقاط (SELECT عبارة) الاستعلام الفرعي. يمكنك استخدام تعبير الاستعلام الفرعي التحجيمي في العديد من الأماكن؛ حيث يكون التعبير المقياسي صالحًا. على سبيل المثال، يمكنك استخدام استعلام فرعي عددي في أي تعبير في كل من SELECT العبارتين و WHERE .

لا يساعد استخدام استعلام فرعي عددي دائما في تحسين استعلامك. على سبيل المثال، تمرير استعلام فرعي عددي كوسيطة إما إلى نظام أو وظائف معرفة من قبل المستخدم لا يوفر أي فائدة في تقليل استهلاك وحدة الموارد (RU) أو زمن الانتقال.

يمكن أيضًا تصنيف الاستعلامات الفرعية العددية على النحو:

  • الاستعلامات الفرعية العددية بسيطة التعبير
  • الاستعلامات الفرعية العددية المجمعة

الاستعلامات الفرعية العددية بسيطة التعبير

الاستعلام الفرعي العددي للتعبير البسيط هو استعلام فرعي مرتبط يحتوي SELECT على عبارة لا تحتوي على أي تعبيرات تجميعية. لا توفر تلك الاستعلامات الفرعية أي فوائد التحسين؛ لأن المحول البرمجي يحولها إلى تعبير واحد بسيط وأكبر. لا يوجد سياق مترابط بين الاستعلامات الداخلية والخارجية.

كمثال أول، ضع في اعتبارك هذا الاستعلام التافه.

SELECT
    1 AS a,
    2 AS b

يمكنك إعادة كتابة هذا الاستعلام، باستخدام استعلام فرعي عددي بسيط التعبير.

SELECT
    (SELECT VALUE 1) AS a, 
    (SELECT VALUE 2) AS b

ينتج عن كلا الاستعلامين نفس الإخراج.

[
  {
    "a": 1,
    "b": 2
  }
]

يقوم استعلام المثال التالي هذا بتسلسل المعرف الفريد ببادئة كتعليق فرعي عددي بسيط التعبير.

SELECT 
    (SELECT VALUE Concat('ID-', p.id)) AS internalId
FROM
    products p

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

SELECT
    p.id,
    (SELECT p.name WHERE CONTAINS(p.name, "glove")).name
FROM
    products p
[
  {
    "id": "aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb",
    "name": "Winter glove"
  },
  {
    "id": "bbbbbbbb-1111-2222-3333-cccccccccccc"
  },
  {
    "id": "cccccccc-2222-3333-4444-dddddddddddd"
  }
]

الاستعلامات الفرعية العددية المجمعة

يعد الطلب الفرعي للتحجيم الكلي هو استعلام فرعي له دالة تجميعية في الإسقاط، أو عامل التصفية الذي يقيم إلى قيمة واحدة.

كمثال أول، ضع في اعتبارك عنصرا يحتوي على الحقول التالية.

{
  "name": "Snow coat",
  "inventory": [
    {
      "location": "Redmond, WA",
      "quantity": 50
    },
    {
      "location": "Seattle, WA",
      "quantity": 30
    },
    {
      "location": "Washington, DC",
      "quantity": 25
    }
  ]
}

فيما يلي استعلام فرعي مع تعبير دالة تجميعية واحدة في إسقاطه. يحسب هذا الاستعلام كافة العلامات لكل عنصر.

SELECT
    p.name,
    (SELECT VALUE COUNT(1) FROM i IN p.inventory) AS locationCount
FROM
    products p
[
  {
    "name": "Snow coat",
    "locationCount": 3
  }
]

فيما يلي نفس الاستعلام الفرعي مع عامل تصفية.

SELECT
    p.name,
    (SELECT VALUE COUNT(1) FROM i IN p.inventory WHERE ENDSWITH(i.location, "WA")) AS washingtonLocationCount
FROM
    products p
[
  {
    "name": "Snow coat",
    "washingtonLocationCount": 2
  }
]

فيما يلي استعلام فرعي آخر مع تعبيرات دالة تجميعية متعددة:

SELECT
    p.name,
    (SELECT
        COUNT(1) AS locationCount,
        SUM(i.quantity) AS totalQuantity
    FROM i IN p.inventory) AS inventoryData
FROM
    products p
[
  {
    "name": "Snow coat",
    "inventoryData": {
      "locationCount": 2,
      "totalQuantity": 75
    }
  }
]

وأخيرا، إليك استعلام مع استعلام فرعي مجمع في كل من الإسقاط وعامل التصفية:

SELECT
    p.name,
    (SELECT VALUE AVG(q.quantity) FROM q IN p.inventory WHERE q.quantity > 10) AS averageInventory
FROM
    products p
WHERE
    (SELECT VALUE COUNT(1) FROM i IN p.inventory WHERE i.quantity > 10) >= 1
[
  {
    "name": "Snow coat",
    "averageInventory": 35
  }
]

طريقة أكثر مثالية لكتابة هذا الاستعلام هو الانضمام إلى الاستعلام الفرعي والإشارة إلى الاسم المستعار للاستعلام الفرعي في كل من جمل SELECT و WHERE. هذا الاستعلام أكثر فعالية؛ لأنك تحتاج إلى تنفيذ الاستعلام الفرعي فقط ضمن عبارة الضم، وليس في كل من الإسقاط والتصفية.

SELECT
    p.name,
    inventoryData.inventoryAverage
FROM
    products p
JOIN
    (SELECT 
        COUNT(1) AS inventoryCount, 
        AVG(i.quantity) as inventoryAverage 
    FROM i IN p.inventory 
    WHERE i.quantity > 10) AS inventoryData
WHERE
    inventoryData.inventoryCount >= 1

تعبير EXISTS

يدعم EXISTS Azure Cosmos DB لمحرك استعلام NoSQL التعبيرات. هذا التعبير هو استعلام فرعي عددي مجمع مضمن في Azure Cosmos DB ل NoSQL. EXISTS يأخذ تعبير الاستعلام الفرعي ويرجع true إذا كان الاستعلام الفرعي يرجع أي صفوف. وإلا، فإنه يرجع false.

نظرا لأن محرك الاستعلام لا يميز بين التعبيرات المنطقية وأي تعبيرات رقمية أخرى، يمكنك استخدام EXISTS في كل من العبارتين SELECT و WHERE . يختلف هذا السلوك عن T-SQL، حيث يقتصر التعبير المنطقي على عوامل التصفية فقط.

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

SELECT VALUE
    undefined

إذا كنت تستخدم EXISTS التعبير والاستعلام السابق كتعلام فرعي، فإن التعبير يرجع false.

SELECT
    EXISTS (SELECT VALUE undefined)
[
  {
    "$1": false
  }
]

إذا تم حذف الكلمة الأساسية VALUE في الاستعلام الفرعي السابق، يتم تقييم الاستعلام الفرعي إلى صفيف مع كائن فارغ واحد.

SELECT
    undefined
[
  {}
]

عند هذه النقطة، EXISTS يتم تقييم التعبير إلى true منذ خروج الكائن ({}) تقنيا.

SELECT 
    EXISTS (SELECT undefined) 
[
  {
    "$1": true
  }
]

حالة الاستخدام الشائعة ل ARRAY_CONTAINS هي تصفية عنصر حسب وجود عنصر في صفيف. في هذه الحالة، نتحقق لمعرفة ما إذا كان tags الصفيف يحتوي على عنصر يسمى "ملابس خارجية".

SELECT
    p.name,
    p.tags
FROM
    products p
WHERE
    ARRAY_CONTAINS(p.tags, "outerwear")

يمكن استخدام EXISTS نفس الاستعلام كخيار بديل.

SELECT
    p.name,
    p.tags
FROM
    products p
WHERE
    EXISTS (SELECT VALUE t FROM t IN p.tags WHERE t = "outerwear")

بالإضافة إلى ذلك، ARRAY_CONTAINS يمكن التحقق فقط مما إذا كانت القيمة مساوية لأي عنصر داخل صفيف. إذا كنت بحاجة إلى عوامل تصفية أكثر تعقيدا على خصائص الصفيف، فاستخدم JOIN بدلا من ذلك.

ضع في اعتبارك هذا المثال العنصر في مجموعة مع عناصر متعددة يحتوي كل منها على accessories صفيف.

{
  "name": "Unobtani road bike",
  "accessories": [
    {
      "name": "Front/rear tire",
      "type": "tire",
      "quantityOnHand": 5
    },
    {
      "name": "9-speed chain",
      "type": "chains",
      "quantityOnHand": 25
    },
    {
      "name": "Clip-in pedals",
      "type": "pedals",
      "quantityOnHand": 15
    }
  ]
}

الآن، ضع في اعتبارك الاستعلام التالي الذي يقوم بالتصفية استنادا type إلى خصائص و quantityOnHand في الصفيف داخل كل عنصر.

SELECT
    p.name,
    a.name AS accessoryName
FROM
    products p
JOIN
    a IN p.accessories
WHERE
    a.type = "chains" AND
    a.quantityOnHand >= 10
[
  {
    "name": "Unobtani road bike",
    "accessoryName": "9-speed chain"
  }
]

لكل عنصر من العناصر في المجموعة، يتم تنفيذ منتج متقاطع مع عناصر الصفيف الخاصة به. تجعل هذه JOIN العملية من الممكن التصفية على الخصائص داخل الصفيف. ومع ذلك، فإن استهلاك RU لهذا الاستعلام مهم. على سبيل المثال، إذا كان هناك 1000 عنصر يحتوي على 100 عنصر في كل صفيف، فإنه يتوسع إلى 1,000 x 100 (أي 100,000) مجموعة.

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

SELECT VALUE
    p.name
FROM
    products p
WHERE
    EXISTS (SELECT VALUE 
        a 
    FROM 
        a IN p.accessories
    WHERE
        a.type = "chains" AND
        a.quantityOnHand >= 10)
[
  "Unobtani road bike"
]

يمكن للاستعلامات أيضا الاسم المستعار EXISTS والإشارة إلى الاسم المستعار في الإسقاط:

SELECT
    p.name,
    EXISTS (SELECT VALUE
        a 
    FROM 
        a IN p.accessories
    WHERE
        a.type = "chains" AND
        a.quantityOnHand >= 10) AS chainAccessoryAvailable
FROM
    products p
[
  {
    "name": "Unobtani road bike",
    "chainAccessoryAvailable": true
  }
]

تعبير ARRAY

يمكنك استخدام ARRAY التعبير لعرض نتائج استعلام كصفيف. يمكنك استخدام هذا التعبير فقط ضمن SELECT عبارة الاستعلام.

لهذه الأمثلة، لنفترض أن هناك حاوية مع هذا العنصر على الأقل.

{
  "name": "Radimer mountain bike",
  "tags": [
    {
      "name": "road"
    },
    {
      "name": "bike"
    },
    {
      "name": "competitive"
    }
  ]
}

في هذا المثال الأول، يتم استخدام التعبير داخل SELECT عبارة .

SELECT
    p.name,
    ARRAY (SELECT VALUE t.name FROM t in p.tags) AS tagNames
FROM
    products p
[
  {
    "name": "Radimer mountain bike",
    "tagNames": [
      "road",
      "bike",
      "competitive"
    ]
  }
]

كما هو الحال مع الاستعلامات الفرعية الأخرى، تكون عوامل التصفية مع ARRAY التعبير ممكنة.

SELECT
    p.name,
    ARRAY (SELECT VALUE t.name FROM t in p.tags) AS tagNames,
    ARRAY (SELECT VALUE t.name FROM t in p.tags WHERE CONTAINS(t.name, "bike")) AS bikeTagNames
FROM
    products p
[
  {
    "name": "Radimer mountain bike",
    "tagNames": [
      "road",
      "bike",
      "competitive"
    ],
    "bikeTagNames": [
      "bike"
    ]
  }
]

يمكن أن تأتي تعبيرات الصفيف أيضا بعد العبارة FROM في الاستعلامات الفرعية.

SELECT
    p.name,
    n.t.name AS nonBikeTagName
FROM
    products p
JOIN
    n IN (SELECT VALUE ARRAY(SELECT t FROM t in p.tags WHERE t.name NOT LIKE "%bike%"))
[
  {
    "name": "Radimer mountain bike",
    "nonBikeTagName": "road"
  },
  {
    "name": "Radimer mountain bike",
    "nonBikeTagName": "competitive"
  }
]