Complicadas consultas SQL - artículos Buscando a emparejan varias claves externas diferentes
-
26-09-2019 - |
Pregunta
Así que imagine que tiene una tabla de Products (ID int, Name nvarchar(200))
, y otras dos mesas, y ProductsCategories (ProductID int, CategoryID int)
InvoiceProducts (InvoiceID int, ProductID int)
.
Tengo que escribir una consulta para producir un conjunto de productos que responden a un determinado conjunto de identificadores de factura y categoría identificadores de tal manera que la lista de productos que coincida con todas las categorías especificadas y todas las facturas especificadas, sin caer de nuevo a SQL dinámico. Imagino que necesito encontrar una lista de productos que se encuentran en ambas categorías 1 y 2 y en las facturas 3 y 4.
Para empezar, he escrito un procedimiento almacenado que acepta identificadores de la categoría y las identificaciones de factura como cadenas, y analizarlos en tablas:
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
Las diferentes soluciones que hemos llegado con aspecto terrible, y obtienen peores resultados. Lo mejor que he encontrado es para generar una vista compuesta de izquierda se une a todos los criterios, pero que parece muy caro y no resuelve el problema de hacer coincidir todas las diferentes claves especificadas.
Actualización: Este es un ejemplo de consulta escribí que los rendimientos de los resultados esperados. Me estoy perdiendo las oportunidades de optimización? Al igual que las operaciones con matrices unicornio mágico por 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)
Solución
Siempre que tenga índices únicos en tanto (ProductID, CategoryID)
y (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
o, si sus valores se transmiten en las cadenas 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
)
Tenga en cuenta que en SQL Server 2008
puede pasar parámetros con valores de tabla en los procedimientos almacenados.
Otros consejos
me gustaría empezar con algo como esto, la utilización de sus valores de ID TableD partir de los parámetros. tablas temporales pueden ayudar con la velocidad subconsulta.
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
CategoriasProductos debe tener un índice agrupado en (CategoryId, ProductId) y InvoiceProducts debe tener uno en (InvoiceID, ProductId) de manera óptima. Esto le permitirá encontrar los identificadores de productos que figuran CategoryId y InvoiceID mediante el uso de los datos en los índices agrupados solamente.
Se podría utilizar una función para devolver una tabla de enteros dados una cadena. Google "CsvToInt" y haga clic en el primer enlace de SqlTeam para ver el código.
A continuación, usted podría:
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)
)
¿Qué tal un CTE recursiva?
Los primeros números de fila se suman a las tablas de criterios, a continuación, algunos seudo SQL si se quiere:
;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.
Esto le dará una forma de llevar a cabo y en múltiples criterios, y debería funcionar bien.
¿Tiene esto algún sentido? He hecho algunas cosas bastante fresco rápido con CTE últimamente, y puede elaborar si es necesario.
código CTE quitado porque estaba mal, y no merece la pena de fijación que tiene una solución mucho mejor que hay.
pasarlos como parámetros XML, almacenarlos en una tabla temporal y unirse.