Compliqué de requêtes SQL - éléments de constatation correspondant à plusieurs différentes clés étrangères

StackOverflow https://stackoverflow.com/questions/4019343

Question

Alors, imaginez que vous avez une table de Products (ID int, Name nvarchar(200)), et deux autres tables, ProductsCategories (ProductID int, CategoryID int) et InvoiceProducts (InvoiceID int, ProductID int).

Je dois écrire une requête pour produire un ensemble de produits qui répondent à un ensemble de ids de facture et catégorie ids tels que la liste des produits correspondant à toutes les catégories spécifiées et toutes les factures spécifiées, sans tomber à SQL dynamique. Imaginez que je dois trouver une liste de produits qui sont dans les deux catégories 1 et 2 et dans les factures 3 et 4.

Pour commencer, j'ai écrit une procédure stockée qui accepte la catégorie ids et ids de la facture sous forme de chaînes, et les analyser dans les tables:

 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

Les différentes solutions que je suis venu avec un regard terrible, et de moins bons résultats. La meilleure chose que j'ai trouvé est de générer une vue composée de gauche rejoint de tous les critères, mais qui semble très cher et ne résout pas la question de faire correspondre toutes les différentes touches spécifiées.


Mise à jour: Ceci est un exemple de requête que j'ai écrit que les rendements les résultats escomptés. Est-ce que je manque des possibilités d'optimisation? Comme les opérations de matrice licorne magique par 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)   
Était-ce utile?

La solution

A condition que vous avez des indices uniques sur les deux (ProductID, CategoryID) et (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

ou, si vos valeurs sont transmises dans les chaînes de 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
        )

Notez que dans SQL Server 2008 vous pouvez passer des paramètres table dans les procédures stockées.

Autres conseils

Je commence par quelque chose comme cela, en utilisant vos valeurs d'ID Dépôt des paramètres. tables temporaires peuvent aider à la vitesse de sous-requête.

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

devrait avoir un catégories de produit index ordonné en clusters sur (CategoryId, ProductId) et InvoiceProducts devrait avoir un sur (InvoiceID, ProductId) de façon optimale. Cela permettra de trouver ids de produits et compte tenu de la CategoryId InvoiceID en utilisant les données dans les index en cluster uniquement.

Vous pouvez utiliser une fonction pour retourner une table de ints d'une chaîne. Google « CsvToInt » et cliquez sur le premier lien de SqlTeam pour voir le code.

Ensuite, vous pouvez:

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

Que diriez-vous d'un CTE récursive?

d'abord ajouter des numéros de ligne aux tableaux de critères, certains SQL pseudo si vous voulez:

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

Cela vous donnera un moyen d'exécution et sur des critères multiples, et devrait fonctionner correctement.

Est-ce aucun sens du tout? Je l'ai fait un peu refroidir assez vite des choses avec CTEs ces derniers temps, et peut élaborer si nécessaire.

Removed code cte parce qu'il était mal, et pas vraiment la peine de fixation ayant une bien meilleure solution là-bas.

les passer en tant que paramètre XML, les stocker dans une table temporaire et rejoindre.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top