如何编写跨行匹配多个值的更好的多重连接?

时间:2012-04-28 13:06:55

标签: sql

我正在尝试编写一个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'
)

2 个答案:

答案 0 :(得分: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.

答案 1 :(得分: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