استعلام SQL المعقدة-العناصر التي تتوافق مع مفاتيح أجنبية مختلفة متعددة
-
26-09-2019 - |
سؤال
لذا تخيل أن لديك طاولة 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 ، وقم بتخزينها على جدول مؤقت والانضمام.