2011-01-21 3 views
0

Предположим, что я реализую статьи с тегами статьи. Я использую SQL Server 2008.Наиболее эффективное много-много запросов

TABLE Articles 
ArtID INT 
... 

TABLE Tags 
TagID INT 
TagText VARCHAR(10) 

TABLE ArticleTags 
ArtID INT 
TagID INT 

Я пытаюсь выяснить наиболее эффективный способ запроса всех статей с помощью определенных тегов. Вот два варианта, оба из которых я прочитал, наиболее эффективны.

Метод A:

SELECT a.* FROM Articles 
WHERE EXISTS (
    SELECT * FROM ArticleTags at 
    INNER JOIN Tags t ON at.TagID = t.TagID 
    WHERE at.ArtID = a.ID 
    AND t.TagText IN ('abc', 'def') 
) 

Метод B:

SELECT a.* FROM Articles a 
INNER JOIN ArticleTags at ON a.ArtID = at.ArtID 
INNER JOIN Tags t ON at.TagID = t.TagID 
WHERE t.TagText IN ('abc', 'def') 
GROUP BY a.ArtID 

Могут ли специалисты SQL предложить, который является более эффективным и почему? Или, может быть, я ошибаюсь.

+0

Взгляните на планы запросов для обоих. Это должно сказать вам, какой из них лучше для вашей конкретной ситуации. – Oded

+0

Установили ли вы полный текстовый поиск по tagText? – Patrick

+0

Вы должны перейти к этому вопросу SO, http://stackoverflow.com/questions/1200295/sql-join-vs-in-performance, и взглянуть на ответ @Quassnoi.И затем вы можете перейти к ссылке, которую он предоставил для очень подробного и подробного объяснения и примеров этого поведения. Http: //explainextended.com/2009/06/16/in-vs-join-vs-exists/ – Lamak

ответ

2

Как почти все вопросы производительности SQL, ответ не запрос, ответ схема данных. Какие индексы у вас есть, это то, что влияет на производительность ваших запросов.

Обычно отношения «многие ко многим» требуют двух дополнительных индексов: один - (ID1, ID2), а другой - (ID2, ID1). Один из них сгруппирован, на самом деле не имеет значения, какой из них. Так позволяет создать тестовую БД (100k статьи, 1K теги, 1-10 теги на статью):

:setvar dbname testdb 
:setvar articles 1000000 
:setvar tags 1000 
:setvar articletags 10 
:on error exit 

set xact_abort on; 
go 

use master; 
go 

if db_id('$(dbname)') is not null 
begin 
    alter database [$(dbname)] set single_user with rollback immediate; 
    drop database [$(dbname)]; 
end 
go 

create database [$(dbname)]; 
go 

use [$(dbname)]; 
go 

create TABLE Articles (
    ArtID INT not null identity(1,1), 
    name varchar(100) not null, 
    filler char(500) not null default replicate('X', 500), 
    constraint pk_Articles primary key clustered (ArtID)); 
go 

create table Tags (
    TagID INT not null identity(1,1), 
    TagText VARCHAR(10) not null, 
    constraint pk_Tags primary key clustered (TagID), 
    constraint unq_Tags_Text unique (TagText)); 
go 

create TABLE ArticleTags (
    ArtID INT not null, 
    TagID INT not null, 
    constraint fk_Articles 
     foreign key (ArtID) 
     references Articles (ArtID), 
    constraint fk_Tags 
     foreign key (TagID) 
     references Tags (TagID), 
    constraint pk_ArticleTags 
     primary key clustered (ArtID, TagID)); 
go 

create nonclustered index ndxArticleTags_TagID 
    on ArticleTags (TagID, ArtID); 
go   

-- populate articles 
set nocount on; 
declare @i int =0, @name varchar(100); 
begin transaction 
while @i < $(articles) 
begin 
    set @name = 'Name ' + cast(@i as varchar(10)); 
    insert into Articles (name) values (@name); 
    set @i += 1; 
    if @i %1000 = 0 
    begin 
     commit; 
     raiserror (N'Inserted %d articles', 0, 1, @i); 
     begin transaction; 
    end 
end 
commit 
go 


-- populate tags 
set nocount on; 
declare @i int =0, @text varchar(100); 
begin transaction 
while @i < $(tags) 
begin 
    set @text = 'Tag ' + cast(@i as varchar(10)); 
    insert into Tags (TagText) values (@text); 
    set @i += 1; 
    if @i %1000 = 0 
    begin 
     commit; 
     raiserror (N'Inserted %d tags', 0, 1, @i); 
     begin transaction; 
    end 
end 
commit 
go 

-- populate article-tags 
set nocount on; 
declare @i int =0, @a int = 1, @cnt int, @tag int; 
set @cnt = rand() * $(articletags) + 1; 
set @tag = rand() * $(tags) + 1; 
begin transaction 
while @a < $(articles) 
begin 
    insert into ArticleTags (ArtID, TagID) values (@a, @tag); 
    set @cnt -= 1; 
    set @tag += rand()*10+1; 
    if $(tags)<[email protected] 
    begin 
     set @tag = 1; 
    end 
    if @cnt = 0 
    begin 
     set @cnt = rand() * $(articletags) + 1; 
     set @tag = rand() * $(tags) + 1; 
     set @a += 1; 
    end 
    set @i += 1; 
    if @i %1000 = 0 
    begin 
     commit; 
     raiserror (N'Inserted %d article-tags', 0, 1, @i); 
     begin transaction; 
    end 
end 
commit 
raiserror (N'Final: %d article-tags', 0, 1, @i); 
go 

Теперь давайте сравним два запроса:

set statistics io on; 
set statistics time on; 

select a.ArtID 
from Articles a 
where exists (
    select * 
    from ArticleTags at 
    join Tags t on at.TagID = t.TagID 
    where at.ArtID = a.ArtID 
    and t.TagText in ('Tag 10', 'Tag 12')); 

SELECT a.ArtID FROM Articles a 
INNER JOIN ArticleTags at ON a.ArtID = at.ArtID 
INNER JOIN Tags t ON at.TagID = t.TagID 
WHERE t.TagText IN ('Tag 10', 'Tag 12') 
GROUP BY a.ArtID  

Результат:

Table 'Articles'. Scan count 0, logical reads 3561, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'ArticleTags'. Scan count 2, logical reads 13, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Tags'. Scan count 2, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Table 'Articles'. Scan count 0, logical reads 3561, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'ArticleTags'. Scan count 2, logical reads 13, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 
Table 'Tags'. Scan count 2, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. 

Сюрприз! (Ну не совсем). Это IDENTICAL. Фактически, они имеют точно такой же план выполнения.

+0

Спасибо Ремусу. Один вопрос: какую СУБД вы использовали выше. –

+0

SQL Server 2008 R2 на моем ноутбуке –

+0

Я предполагаю, что причиной этого может быть проблема из-за [SQLCMD] (http://www.simple-talk.com/sql/sql-tools/the-sqlcmd-workbench /) синтаксис? –

0

Вскоре: без разницы. Оба будут переведены в один и тот же план выполнения.

Редактировать: не заметили GROUP BY. Таким образом, запрос скорее всего не скомпилируется. Удалите предложение GROUP BY или перечислите все поля таблицы, такие как GROUP BY Id, Name, ...

+0

GROUP BY скомпилирует _if_ ArtID определяет уникальную строку. – ThomasMcLeod

+3

@ThomasMcLeod Только в MySQL не SQL Server! –

+0

@Martin: хорошая точка, +1 – ThomasMcLeod

1

Ваш метод B имеет предложение GROUP BY, но вы возвращаете все столбцы из статей, даже предположительно неагрегативных столбцов. Это вызовет ошибку. GROUP BY, вероятно, не нужно.

Без GROUP BY запросы имеют примерно одинаковый план выполнения. Однако метод B является более стандартным запросом SQL-запроса.

Edit: DISTINCT, как правило, предпочтительнее GROUP BY в этом случае, и имеет ту же функцию

SELECT DISTINCT 
    a.* 
FROM 
    Articles a 
INNER JOIN 
    ArticleTags at 
ON 
    a.ArtID = at.ArtID 
INNER JOIN 
    Tags t 
ON 
    at.TagID = t.TagID 
WHERE 
    t.TagText IN ('abc', 'def') 
+0

Без GROUP BY он будет возвращать одну и ту же статью несколько раз, если в статье содержится более одного тега. Это не будет то, что я хочу, и будет также использовать полосу пропускания. –

+0

Я вижу, это прекрасно, пока вы можете гарантировать, что ArtID уникален. Я бы поставил ограничение PRIMARY KEY. – ThomasMcLeod

+0

ArtID определенно будет PK. –

1

Я хотел бы создать индексированное представление на основе 3-х таблиц на artID столбцов и TagText. Таким образом, вы можете использовать:

SELECT * 
FROM Articles 
WHERE artID IN 
(SELECT artID 
FROM ArticleTagTextView 
WHERE TagText IN ('abc', 'def')) 
Смежные вопросы