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)   
¿Fue útil?

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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top