Сложный запрос SQL - поиск предметов, соответствующих нескольким различным внешним ключам
-
26-09-2019 - |
Вопрос
Так что представьте, что у вас есть таблица Products (ID int, Name nvarchar(200))
, и две другие таблицы, ProductsCategories (ProductID int, CategoryID int)
а также InvoiceProducts (InvoiceID int, ProductID int)
.
Мне нужно написать запрос для создания набора продуктов, которые соответствуют данному набору идентификаторов счета и идентификаторы категории, такие, что список продуктов соответствует всем указанным категориям и все указанные счета, не падающие назад к динамическому SQL. Представьте себе, что мне нужно найти список продуктов, которые находятся как в категориях 1, так и в счетах 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
Различные решения, которые я придумал выглядеть ужасно и выполнять хуже. Лучшее, что я обнаружил, состоит в том, чтобы создать вид, состоящий из левых соединений всех критериев, но это кажется очень дорогим и не решает проблему соответствия всех указанных клавишных клавиш.
Обновлять: Это пример запроса, который я написал, что дает ожидаемые результаты. Я пропускаю какие-либо возможности оптимизации? Как волшебные матричные операции Unicorn по Ninjas?
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
Вы можете пропустить таблицы параметров в хранимые процедуры.
Другие советы
Я бы начал с чего-то вроде этого, используя ваши таблицы идентификаторов от параметров. Таблицы Temp могут помочь с скоростью подзакомы.
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
ПродуктыКатегории должны иметь кластеризованный индекс на (CoatureID, ProductiD), а информационные продукты должны иметь один на (invoiceiD, productiD) оптимально. Это позволит найти идентификаторы продукта, учитывая ChateiD и invoiceID, используя данные только в кластерных индексах только.
Вы можете использовать функцию для возврата таблицы INT MANTES, причитающей строку. 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, если вы будете:
;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, храните их в таблице TEMP и присоединитесь.