استعلام SQL المعقدة-العناصر التي تتوافق مع مفاتيح أجنبية مختلفة متعددة

StackOverflow https://stackoverflow.com/questions/4019343

سؤال

لذا تخيل أن لديك طاولة Products (ID int, Name nvarchar(200)), واثنين من الجدولين الآخرين ، ProductsCategories (ProductID int, CategoryID int) و InvoiceProducts (InvoiceID int, ProductID int).

أحتاج إلى كتابة استعلام لإنتاج مجموعة من المنتجات التي تتطابق مع مجموعة معينة من معرفات الفاتورة ومعرفات الفئة بحيث تتطابق قائمة المنتجات مع جميع الفئات المحددة وجميع الفواتير المحددة ، دون العودة إلى SQL الديناميكي. تخيل أنني بحاجة إلى العثور على قائمة بالمنتجات الموجودة في كلتا الفئتين 1 و 2 وفي الفواتير 3 و 4.

كبداية ، لقد كتبت مخزنة مخزنة تقبل معرفات الفئة ومعرفات الفاتورة كقواسل ، وحوضها في الجداول:

 CREATE PROCEDURE dbo.SearchProducts (@categories varchar(max), @invoices varchar(max))
 AS BEGIN
      with catids as (select cast([value] as int) from dbo.split(@categories, ' ')),
           invoiceids as (select cast([value] as int) from dbo.split(@invoices, ' '))
           select * from products --- insert awesomeness here
 END

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


تحديث: هذا مثال على استفسار كتبته ينتج عنه النتائج المتوقعة. هل أفتقد أي فرص تحسين؟ مثل عمليات مصفوفة يونيكورن السحرية من النينجا؟

with catids as (select distinct cast([value] as int) [value] from dbo.split(@categories, ' ')),
  invoiceids as (select distinct cast([value] as int) [value] from dbo.split(@invoices, ' '))

  select pc.ProductID from ProductsCategories pc (nolock)
    inner join catids c on c.value = pc.CategoryID 
    group by pc.ProductID 
    having COUNT(*) = (select COUNT(*) from catids)  
  intersect
  select ip.ProductID from InvoiceProducts ip (nolock)
    inner join invoiceids i on i.value = ip.InvoiceID 
    group by ip.ProductID 
    having COUNT(*) = (select COUNT(*) from invoiceids)   
هل كانت مفيدة؟

المحلول

شريطة أن يكون لديك مؤشرات فريدة من نوعها على حد سواء (ProductID, CategoryID) و (ProductID, InvoiceID):

SELECT  ProductID
FROM    (
        SELECT  ProductID
        FROM    ProductInvoice
        WHERE   InvoiceID IN (1, 2)
        UNION ALL
        SELECT  ProductID
        FROM    ProductCategory pc
        WHERE   CategoryID IN (3, 4)
        ) q
GROUP BY
        ProductID
HAVING  COUNT(*) = 4

أو ، إذا تم تمرير قيمك CSV سلاسل:

WITH    catids(value) AS
        (
        SELECT  DISTINCT CAST([value] AS INT)
        FROM    dbo.split(@categories, ' '))
        ), 
        (
        SELECT  DISTINCT CAST([value] AS INT)
        FROM    dbo.split(@invoices, ' '))
        )
SELECT  ProductID
FROM    (
        SELECT  ProductID
        FROM    ProductInvoice
        WHERE   InvoiceID IN
                (
                SELECT  value
                FROM    invoiceids
                )
        UNION ALL
        SELECT  ProductID
        FROM    ProductCategory pc
        WHERE   CategoryID IN
                (
                SELECT  value
                FROM    catids
                )
        ) q
GROUP BY
        ProductID
HAVING  COUNT(*) = 
        (
        SELECT  COUNT(*)
        FROM    catids
        ) + 
        (
        SELECT  COUNT(*)
        FROM    invoiceids
        )

لاحظ ذلك في SQL Server 2008 يمكنك تمرير المعلمات ذات قيمة الجدول إلى الإجراءات المخزنة.

نصائح أخرى

سأبدأ بشيء من هذا القبيل ، باستخدام قيم المعرف المقدمة من المعلمات. يمكن أن تساعد جداول مؤقتة في السرعة الفرعية.

select p.*
from
(
    select pc.*
    from catids c
    inner join ProductsCategories pc
        on pc.CategoryID = c.value
) catMatch
inner join
(
    select pin.*
    from invoiceids i
    inner join ProductsInvoices pin
        on pin.InvoiceID = i.value
) invMatch
    on invMatch.ProductID = catMatch.ProductID
inner join Products p
    on p.ID = invMatch.ProductID

يجب أن تحتوي فئة ProductCategories على فهرس متجمع على (CategoryId ، ProductId) وينبغي أن يكون للمنتجات الفاتورة واحدة على (فاتورة ، منتج) على النحو الأمثل. سيسمح ذلك بإيجاد معرفات المنتج بالنظر إلى الفئة والفواتير باستخدام البيانات في الفهارس المجمعة فقط.

يمكنك استخدام وظيفة لإرجاع جدول من ints مع إعطاء سلسلة. Google "CSVTOINT" وانقر على الرابط الأول من SQLTeam لمشاهدة الرمز.

ثم يمكنك:

SELECT *
FROM Products
WHERE ID IN (SELECT DISTINCT ProductId 
        FROM ProductCategories
        WHERE CategoryId in dbo.CsvToInt(@categories)
    ) AND ID IN (SELECT DISTINCT ProductId 
        FROM InvoiceProducts
        WHERE InvoiceId in dbo.CsvToInt(@invoices)
    )

ماذا عن CTE العودية؟

أضف أولاً أرقام الصفوف إلى جداول المعايير ، ثم بعض sql pseudo إذا صح التعبير:

;WITH cte AS(
Base case: Select productid, criteria from products left join criteria where row_number = 1 if it matches criteria from both row 1s or one is null.
UNION ALL
Recursive case: Select n+1 criteria row from products left join criteria where row_number = cte.row_number + 1 AND matches criteria from both row_number + 1 or one or the other (but not both) is null
)
SELECT *
WHERE criteria = maximum id from criteria table.

سيعطيك هذا طريقة للأداء وعلى معايير متعددة ، ويجب أن يؤدي أداءً جيدًا.

هل هذا يجعل أي معنى على الإطلاق؟ لقد قمت ببعض الأشياء السريعة الرائعة مع CTEs مؤخرًا ، ويمكن أن توضح إذا لزم الأمر.

تم إزالة رمز CTE لأنه كان خطأ ، ولا يستحق تحديد حل أفضل بكثير هناك.

تمريرها كمعلمة XML ، وقم بتخزينها على جدول مؤقت والانضمام.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top