2013-05-13 22 views
6

Ho una tabella di documenti e una tabella di tag. I documenti sono contrassegnati con vari valori.Selezionare dalla tabella A dove si unisce a tutti i dati nella tabella B

Sto tentando di creare una ricerca di questi tag e per la maggior parte funziona. Tuttavia, sto ottenendo risultati extra restituiti quando corrisponde a qualsiasi tag. Voglio solo risultati dove corrisponde a tutti i tag.

Ho creato questo per illustrare il problema http://sqlfiddle.com/#!3/8b98e/11

tabelle e dati:

CREATE TABLE Documents 
(
DocId INT, 
DocText VARCHAR(500) 
); 

CREATE TABLE Tags 
(
    TagId INT, 
    TagName VARCHAR(50) 
); 

CREATE TABLE DocumentTags 
(
    DocTagId INT, 
    DocId INT, 
    TagId INT, 
    Value VARCHAR(50) 
); 

INSERT INTO Documents VALUES (1, 'Document 1 Text'); 
INSERT INTO Documents VALUES (2, 'Document 2 Text'); 

INSERT INTO Tags VALUES (1, 'Tag Name 1'); 
INSERT INTO Tags VALUES (2, 'Tag Name 2'); 

INSERT INTO DocumentTags VALUES (1, 1, 1, 'Value 1'); 
INSERT INTO DocumentTags VALUES (1, 1, 2, 'Value 2'); 
INSERT INTO DocumentTags VALUES (1, 2, 1, 'Value 1'); 

Codice:

-- Set up the parameters 
DECLARE @TagXml VARCHAR(max) 
SET @TagXml = '<tags> 
        <tag> 
        <description>Tag Name 1</description> 
        <value>Value 1</value> 
        </tag> 
        <tag> 
        <description>Tag Name 2</description> 
        <value>Value 2</value> 
        </tag> 
       </tags>' 

-- Create a table to store the parsed xml in 
DECLARE @XmlTagData TABLE 
(
    id varchar(20) 
    ,[description] varchar(100) 
    ,value varchar(250) 
) 

-- Populate our XML table 
DECLARE @iTag int 
EXEC sp_xml_preparedocument @iTag OUTPUT, @TagXml 
-- Execute a SELECT statement that uses the OPENXML rowset provider 
-- to produce a table from our xml structure and insert it into our temp table 
INSERT INTO @XmlTagData (id, [description], value) 
SELECT id, [description], value 
FROM OPENXML (@iTag, '/tags/tag',1) 
     WITH (id varchar(20), 
       [description] varchar(100) 'description', 
       value varchar(250) 'value') 

EXECUTE sp_xml_removedocument @iTag 

-- Update the XML table Id's to match existsing Tag Id's 
UPDATE  @XmlTagData 
SET   X.Id = T.TagId 
FROM  @XmlTagData X 
INNER JOIN Tags T ON X.[description] = T.TagName 

-- Check it looks right 
--SELECT * 
--FROM @XmlTagData 

-- This is where things do not quite work. I get both doc 1 & 2 back, 
-- but what I want is just document 1. 
-- i.e. documents that have both tags with matching values 
SELECT DISTINCT D.* 
FROM Documents D 
INNER JOIN DocumentTags T ON T.DocId = D.DocId 
INNER JOIN @XmlTagData X ON X.id = T.TagId AND X.value = T.Value 

(Nota io non sono un DBA, così potrebbero esserci modi migliori per fare le cose. Spero di essere sulla strada giusta, ma io a m aperto ad altri suggerimenti se la mia implementazione può essere migliorata.)

Qualcuno può offrire qualche suggerimento su come ottenere solo i risultati che hanno tutti i tag?

Molte grazie.

+0

Se si dispone di codice dell'applicazione come coldfusion, .net, php, ecc disponibile, sarebbe più semplice utilizzarlo. –

+0

Questo problema è chiamato divisione relazionale. –

risposta

2

Utilizzare l'opzione con gli operatori [NOT] EXISTS e EXCEPT nell'ultima query

SELECT * 
FROM Documents D 
WHERE NOT EXISTS (
        SELECT X.ID , X.Value 
        FROM @XmlTagData X 
        EXCEPT 
        SELECT T.TagId, T.VALUE 
        FROM DocumentTags T 
        WHERE T.DocId = D.DocId 
       ) 

Demo SQLFiddle

O

SELECT * 
FROM Documents D 
WHERE EXISTS (
       SELECT X.ID , X.Value 
       FROM @XmlTagData X 
       EXCEPT 
       SELECT T.TagId, T.VALUE 
       FROM DocumentTags T 
       WHERE T.DocId != D.DocId 
      ) 

Demo SQLFiddle

O

Inoltre è possibile utilizzare una soluzione semplice con i metodi XQuery: nodes(), value()) e CTE/sottoquery.

-- Set up the parameters 
DECLARE @TagXml XML 
SET @TagXml = '<tags> 
        <tag> 
        <description>Tag Name 1</description> 
        <value>Value 1</value> 
        </tag> 
        <tag> 
        <description>Tag Name 2</description> 
        <value>Value 2</value> 
        </tag>    
       </tags>'    


;WITH cte AS 
(
    SELECT TagValue.value('(./value)[1]', 'nvarchar(100)') AS value, 
     TagValue.value('(./description)[1]', 'nvarchar(100)') AS [description]  
    FROM @TagXml.nodes('/tags/tag') AS T(TagValue) 
) 
    SELECT * 
    FROM Documents D 
    WHERE NOT EXISTS (
        SELECT T.TagId, c.value 
        FROM cte c JOIN Tags T WITH(FORCESEEK) 
         ON c.[description] = T.TagName 
        EXCEPT 
        SELECT T.TagId, T.VALUE 
        FROM DocumentTags T WITH(FORCESEEK) 
        WHERE T.DocId = D.DocId       
        ) 

Demo SQLFiddle

O

-- Set up the parameters 
DECLARE @TagXml XML 
SET @TagXml = '<tags> 
        <tag> 
        <description>Tag Name 1</description> 
        <value>Value 1</value> 
        </tag> 
        <tag> 
        <description>Tag Name 2</description> 
        <value>Value 2</value> 
        </tag>    
       </tags>'  

    SELECT * 
    FROM Documents D 
    WHERE NOT EXISTS (
        SELECT T2.TagId, 
          TagValue.value('(./value)[1]', 'nvarchar(100)') AS value       
        FROM @TagXml.nodes('/tags/tag') AS T(TagValue) 
         JOIN Tags T2 WITH(FORCESEEK) 
         ON TagValue.value('(./description)[1]', 'nvarchar(100)') = T2.TagName           
        EXCEPT 
        SELECT T.TagId, T.VALUE 
        FROM DocumentTags T WITH(FORCESEEK) 
        WHERE T.DocId = D.DocId      
        ) 

Demo SQLFiddle

Al fine di migliorare le prestazioni (marcia forzata di indice di ricerca sui Tags e tavoli DocumentTags), gli indici di utilizzo e suggerimenti tabella (suggerimento FORCESEEK aggiunto alla query sopra):

CREATE INDEX x ON Documents(DocId) INCLUDE(DocText) 
CREATE INDEX x ON Tags(TagName) INCLUDE(TagId) 
CREATE INDEX x ON DocumentTags(DocId) INCLUDE(TagID, VALUE) 
+0

Perfetto! Grazie mille. – Yetiish

+1

Prego;) In risposta è stata aggiunta una nuova soluzione con i metodi XQuery. Provalo anche tu –

+0

Sembra elegante! abbassa molto sulla codifica! Grazie – Yetiish

0

io non sono davvero sicuro della sintassi per SQL Server, ma credo che una cosa del genere dovrebbe funzionare

0

aggiungere una clausola WHERE per verificare un non esiste condizionale:

SELECT DISTINCT D.* 
FROM Documents D 
INNER JOIN DocumentTags T ON T.DocId = D.DocId 
INNER JOIN @XmlTagData X ON X.id = T.TagId AND X.value = T.Value 
WHERE NOT EXISTS (SELECT 1 FROM Documents dt2 
        CROSS JOIN Tags t2 
        LEFT JOIN DocumentTags dt3 
        ON t2.TagId = dt3.TagId 
        AND dt2.DocId = dt3.DocId 
        WHERE dt3.DocTagId IS NULL 
        AND dt2.DocId = D.DocId) 

SQL Fiddle.

Problemi correlati