0

我正在尝试编写一个 SQL 语句,它允许我根据关键字从表中选择一系列文章。到目前为止,我得到的是一个令牌表、一个文章表和一个用于令牌和文章的多对多表:

tokens
  rowid
  token

token_article
  token_rowid
  article_rowid

articles
  rowid

我正在做的是进行搜索查询,将其按空格分隔,然后选择包含这些关键字的所有文章。到目前为止,我想出了这个:

select * from 
    (select * from tokens 
        inner join token_article on 
             tokens.rowid = token_article.token_rowid and 
             token = 'ABC'
    ) as t1, 

    (select * from tokens 
        inner join token_article on 
            tokens.rowid = token_article.token_rowid and 
            token = 'DEF'
    ) as t2 

where t1.article_rowid = t2.article_rowid and t2.article_rowid = articles.rowid

哪个有效,但当然它对匹配 ABC 的所有文章和 DEF 然后选择它们的所有文章进行选择。

现在我正在尝试找出更好的方法。我认为可行的方法是选择所有与 ABC 匹配的文章,以及从那些与 DEF 匹配的文章中。这是我想象的样子但不起作用(收到错误消息“没有这样的列:tokens.rowid”)

select * from 
    (select * from
        (select * from tokens 
                inner join token_article on 
                    tokens.rowid = token_article.token_rowid and
            token = 'ABC'
        ) 
    inner join token_article on 
            tokens.rowid = token_article.token_rowid and
    token = 'DEF'
)
4

2 回答 2

2

因为有不止一种方法可以做到这一点……此方法使用 GROUP BY 和 HAVING 子句。该查询正在查找具有 ABC 或 DEF 令牌的所有文章,然后按文章 ID 分组,其中文章的令牌数等于被查询的令牌数。

请注意,我在这里使用了 MSSQL 语法,但这个概念应该适用于大多数 SQL 实现。

编辑: 我应该指出,当您向查询添加更多标记时,这具有相当干净的语法。如果您添加更多标记,那么您只需要修改t.token_in条件并HAVING COUNT(*) = x相应地调整子句。

DECLARE @tokens TABLE
(
    rowid INT NOT NULL,
    token VARCHAR(255) NOT NULL
)

DECLARE @articles TABLE
(
    rowid INT NOT NULL,
    title VARCHAR(255) NOT NULL
)

DECLARE @token_article TABLE
(
    token_rowid INT NOT NULL,
    article_rowid INT NOT NULL
)

INSERT INTO @tokens VALUES (1, 'ABC'), (2, 'DEF')
INSERT INTO @articles VALUES (1, 'This is article 1.'), (2, 'This is article 2.'), (3, 'This is article 3.'), (4, 'This is article 4.'), (5, 'This is article 5.'), (6, 'This is article 6.')
INSERT INTO @token_article VALUES (1, 1), (2, 1), (1, 2), (2, 3), (1, 4), (2, 4), (1, 5), (1, 6)

-- Get the article IDs that have all of the tokens
-- Use this if you just want the IDs
SELECT a.rowid FROM @articles a
INNER JOIN @token_article ta ON a.rowid = ta.article_rowid
INNER JOIN @tokens t ON ta.token_rowid = t.rowid
WHERE t.token IN ('ABC', 'DEF')
GROUP BY a.rowid
HAVING COUNT(*) = 2 -- This should match the number of tokens

rowid
-----------
1
4

-- Get the articles themselves
-- Use this if you want the articles
SELECT * FROM @articles WHERE rowid IN (
    SELECT a.rowid FROM @articles a
    INNER JOIN @token_article ta ON a.rowid = ta.article_rowid
    INNER JOIN @tokens t ON ta.token_rowid = t.rowid
    WHERE t.token IN ('ABC', 'DEF')
    GROUP BY a.rowid
    HAVING COUNT(*) = 2 -- This should match the number of tokens
)

rowid       title
----------- ------------------
1           This is article 1.
4           This is article 4.
于 2012-04-28T13:46:30.217 回答
1

这是一种方法。该脚本在 SQL Server 2012 数据库中进行了测试。

脚本

CREATE TABLE dbo.tokens
(
        rowid   INT         NOT NULL IDENTITY
    ,   token   VARCHAR(10) NOT NULL
);

CREATE TABLE dbo.articles
(
        rowid   INT         NOT NULL IDENTITY
    ,   name    VARCHAR(10) NOT NULL
);

CREATE TABLE dbo.token_article
(
        token_rowid     INT NOT NULL
    ,   article_rowid   INT NOT NULL
);

INSERT INTO dbo.tokens (token) VALUES
    ('ABC'),
    ('DEF');

INSERT INTO dbo.articles (name) VALUES
    ('Article 1'),
    ('Article 2'),
    ('Article 3');

INSERT INTO dbo.token_article (token_rowid, article_rowid) VALUES
    (1, 2),
    (2, 3),
    (1, 3),
    (1, 1),
    (2, 2);

SELECT  out1.rowid 
    ,   out1.token
    ,   out1.token_rowid
    ,   out1.article_rowid
    ,   ta2.token_rowid
    ,   ta2.article_rowid
    ,   t2.rowid
    ,   t2.token
FROM
(
    SELECT      t.rowid
            ,   t.token
            ,   ta1.token_rowid
            ,   ta1.article_rowid
    FROM        dbo.tokens          t
    INNER JOIN  dbo.token_article   ta1
    ON          ta1.token_rowid     = t.rowid
    WHERE       t.token             = 'ABC'
)           out1
INNER JOIN  dbo.token_article   ta2
ON          ta2.article_rowid   = out1.article_rowid
INNER JOIN  dbo.tokens          t2
ON          t2.rowid            = ta2.token_rowid
AND         t2.token            = 'DEF';

输出

rowid token token_rowid article_rowid token_rowid article_rowid rowid token
----- ----- ----------- ------------- ----------- ------------- ----- -----
1      ABC       1             2           2             2        2    DEF
1      ABC       1             3           2             3        2    DEF
于 2012-04-28T13:32:49.867 回答