Domanda

Quindi, immaginate di avere una tabella di Products (ID int, Name nvarchar(200)), e altri due tavoli, ProductsCategories (ProductID int, CategoryID int) e InvoiceProducts (InvoiceID int, ProductID int).

Ho bisogno di scrivere una query per produrre una serie di prodotti che corrispondono a un dato insieme di ID fattura e di categoria ids in modo tale che l'elenco dei prodotti corrispondono tutte le categorie specificate e tutte le fatture specificate, senza cadere di nuovo a SQL dinamico. Immaginate ho bisogno di trovare un elenco di prodotti che sono in entrambe le categorie 1 e 2 e nelle fatture 3 e 4.

Per cominciare, ho scritto a-stored procedure che accetta gli ID di categoria e gli ID delle fatture come stringhe, e li analizza in tabelle:

 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

Le diverse soluzioni mi è venuta in mente un aspetto orribile, ed eseguire peggio. La cosa migliore che ho trovato è quello di generare una vista composta da sinistra si unisce di tutti i criteri, ma che sembra molto costoso e non risolve il problema di abbinare tutte le diverse chiavi specificate.


Aggiornamento: Questo è un esempio di query che ho scritto che produce i risultati attesi. Mi sto perdendo le opportunità di ottimizzazione? Come magiche operazioni di matrice unicorno di ninja?

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)   
È stato utile?

Soluzione

A condizione che si hanno indici univoci su entrambi i (ProductID, CategoryID) e (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, se i valori vengono passati in stringhe 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
        )

Si noti che in SQL Server 2008 è possibile passare i parametri con valori di tabella nelle stored procedure.

Altri suggerimenti

Mi piacerebbe iniziare con qualcosa di simile, utilizzando i valori ID presentati dai parametri. tabelle temporanee possono aiutare con velocità subquery.

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

categorie merceologiche dovrebbe avere un indice cluster (CategoryId, ProductId) e InvoiceProducts dovrebbe avere uno (InvoiceID, ProductId) in modo ottimale. Questo permetterà di trovare ID prodotto data la CategoryId e InvoiceID utilizzando i dati negli indici cluster solo.

Si potrebbe utilizzare una funzione per restituire un tavolo di int dato una stringa. Google "CsvToInt" e fare clic sul primo link da SqlTeam per vedere il codice.

Poi si potrebbe:

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)
    )

Come su una CTE ricorsiva?

I primi numeri di riga aggiungere alle tabelle di criteri, poi alcuni pseudo SQL se si vuole:

;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.

Questo vi darà un modo di eseguire e su più criteri, e dovrebbe funzionare bene.

Questo ha alcun senso a tutti? Ho fatto un po 'di roba abbastanza freddo veloce con CTE ultimamente, e può elaborare, se necessario.

Rimosso codice CTE perché era sbagliato, e non vale la pena di fissaggio avere una soluzione molto migliore là fuori.

passarli come parametro XML, conservarli a una tabella temporanea e unisciti.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top