6

我有一个文档表和一个标签表。文档标记有各种值。

我正在尝试创建这些标签的搜索,并且在大多数情况下它正在工作。但是,当它匹配任何标签时,我会返回额外的结果。我只想要匹配所有标签的结果。

我创建了这个来说明问题http://sqlfiddle.com/#!3/8b98e/11

表格和数据:

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

代码:

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

(注意我不是 DBA,所以可能有更好的做事方式。希望我走在正确的轨道上,但如果我的实现可以改进,我愿意接受其他建议。)

任何人都可以就如何仅获得具有所有标签的结果提供任何建议吗?

非常感谢。

4

3 回答 3

2

在最后一个查询中使用带有[NOT] EXISTSEXCEPT运算符的选项

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
                  )

SQLFiddle上的演示

或者

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
              )   

SQLFiddle上的演示

或者

您还可以使用带有 XQuery 方法的简单解决方案:nodes()value() ) 和 CTE/Subquery。

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

SQLFiddle上的演示

或者

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

SQLFiddle上的演示

为了提高性能(在 Tags 和 DocumentTags 表上强制操作索引查找),使用索引和表提示(在上面的查询中添加了 FORCESEEK 提示):

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)
于 2013-05-13T16:29:42.430 回答
0

我不太确定 的语法SQL Server,但我想这样的东西应该可以

SELECT d.docId
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
group by 
documents.docid
having count(*) = 2 --[total of tags to be searched]
于 2013-05-13T16:22:29.663 回答
0

添加 where 子句来检查不存在的条件:

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小提琴。

于 2013-05-13T16:28:13.313 回答