2013-05-13 8 views
6

Mam tabelę dokumentów i tabelę znaczników. Dokumenty są oznaczone różnymi wartościami.Wybieranie z Tabeli A, w której łączy się ze wszystkimi danymi w Tabeli B

Próbuję utworzyć wyszukiwanie tych znaczników, a większość z nich działa. Jednak otrzymuję dodatkowe wyniki zwracane, gdy pasuje do dowolnego tagu. Chcę tylko wyników, w których pasują do wszystkich tagów.

Stworzyłem to zilustrować problem http://sqlfiddle.com/#!3/8b98e/11

tabele i dane:

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

Kod:

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

(Uwaga Nie jestem DBA, więc mogą być lepsze sposoby robienia rzeczy, mam nadzieję, że jestem na dobrej drodze, ale ja Jestem otwarty na inne sugestie, jeśli moja implementacja może zostać ulepszona.)

Czy każdy może zaproponować jak uzyskać tylko wyniki z wszystkimi tagami?

Wielkie dzięki.

+0

Jeśli masz kod aplikacji, taki jak coldfusion, .net, php itp., Łatwiej będzie z niego skorzystać. –

+0

Ten problem nazywany jest podziałem relacyjnym. –

Odpowiedz

2

Użyj opcji z operatorami [NOT] EXISTS i EXCEPT w ostatnim zapytaniu

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 na SQLFiddle

LUB

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 na SQLFiddle

LUB

Ponadto można skorzystać z prostego rozwiązania z metod XQuery: nodes(), value()) i CTE/Podzapytanie.

-- 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 na SQLFiddle

LUB

-- 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 na SQLFiddle

W celu poprawy wydajności (wymuszone działanie indeksu szukać na tagi i DocumentTags tabelach), indeksy w ruchu i wskazówki do tabeli (wskazówka FORCESEEK została dodana do powyższego zapytania):

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

Idealnie! Dziękuję Ci bardzo. – Yetiish

+1

Nie ma za co;) W odpowiedzi dodano nowe rozwiązanie z wykorzystaniem metod XQuery. Spróbuj też. –

+0

Wydaje się być elegancki! przycina na wielu kodach! Dzięki – Yetiish

0

nie jestem pewien składni SQL Server, ale myślę, że coś jak to powinno działać

0

Dodaj gdzie klauzula sprawdzenie i nie istnieje warunkowy:

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.

Powiązane problemy