2009-04-18 36 views
0

我正在研究一个报告系统,允许用户任意查询一组事实表,限制每个事实表的多维表。我写了一个查询构建器类,它根据约束参数自动组装所有正确的联接和子查询,并且一切按设计工作。报告查询:加入多个事实表的最佳方式?

但是,我有一种感觉,我没有生成最有效的查询。在一组包含几百万条记录的表上,这些查询需要大约10秒钟的时间才能运行,并且我希望在不到一秒的范围内将它们记下来。我有一种感觉,如果我能摆脱子查询,结果会更有效率。

与其向您展示我的实际架构(这更复杂),我将向您展示一个类似的示例,它不需要解释我的整个应用程序和数据模型即可说明这一点。

想象一下,我有一个音乐会信息数据库,包括艺术家和场地。用户可以任意标记艺术家和场地。所以模式看起来像这样:

concert 
    id 
    artist_id 
    venue_id 
    date 

artist 
    id 
    name 

venue 
    id 
    name 

tag 
    id 
    name 

artist_tag 
    artist_id 
    tag_id 

venue_tag 
    venue_id 
    tag_id 

很简单。

现在,让我们来查询数据库,了解所有在今天的一个月内发生的所有音乐会,所有具有'techno'和'trombone'标签的艺术家都会在'cheap-beer'和'great-mosh - 坑'标签。

我已经能够想出这个样子的最佳查询:

SELECT 
    concert.id AS concert_id, 
    concert.date AS concert_date, 
    artist.id AS artist_id, 
    artist.name AS artist_name, 
    venue.id AS venue_id, 
    venue.name AS venue_name, 
FROM 
    concert 
INNER JOIN (
    artist ON artist.id = concert.artist_id 
) INNER JOIN (
    venue ON venue.id = concert.venue_id 
) 
WHERE (
    artist.id IN (
    SELECT artist_id 
    FROM artist_tag 
    INNER JOIN tag AS a on (
     a.id = artist_tag.tag_id 
     AND 
     a.name = 'techno' 
    ) INNER JOIN tag AS b on (
     b.id = artist_tag.tag_id 
     AND 
     b.name = 'trombone' 
    ) 
) 
    AND 
    venue.id IN (
    SELECT venue_id 
    FROM venue_tag 
    INNER JOIN tag AS a on (
     a.id = venue_tag.tag_id 
     AND 
     a.name = 'cheap-beer' 
    ) INNER JOIN tag AS b on (
     b.id = venue_tag.tag_id 
     AND 
     b.name = 'great-mosh-pits' 
    ) 
) 
    AND 
    concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH) 
) 

查询工作,但我真的不喜欢那些多个子查询。如果我完全可以使用JOIN逻辑来完成相同的逻辑,我有一种感觉,性能会大大提高。

在一个完美的世界中,我会使用一个真正的OLAP服务器。但我的客户将部署到MySQL或MSSQL或Postgres,并且我无法保证兼容的OLAP引擎可用。所以我坚持使用一个具有星型模式的普通RDBMS。

不要太担心这个例子的细节(我的真实应用与音乐无关,但它有多个事实表,与我在这里展示的关系类似)。在这个模型中,'artist_tag'和'venue_tag'表充当事实表,其他所有内容都是维度。

在这个例子中,重要的是要注意,如果我只允许用户约束单个artist_tag或venue_tag值,那么查询就更容易编写。当我允许查询包含AND逻辑时,它只会变得非常棘手,需要多个不同的标记。

所以,我的问题是:你知道什么是针对多个事实表编写高效查询的最佳技术?

+0

我觉得这里的问题的关键是真的查询的AND性质,而不是“多个事实表”。 (尽管它们相互复合。)下面给出的答案通过在HAVING子句中执行查询的AND组件来解决这个问题,而不是需要多次连接到相同的事实表。 – MatBailie 2009-04-18 17:26:57

+0

时间来标记为已解决/关闭/ ... :) – 2010-08-11 13:05:04

回答

1

非规范化模型。在场地和艺术家表格中加入标签名称。这样,你避免了多对多的关系,并且你有一个简单的星型模式。

通过应用此非规范化,where子句只能在两个表(艺术家和场地)中检查此额外的tag_name字段。

+0

但是,如果我denormalize,如何让艺术家或场地有多个标签?事情是,我真的不能消除多对多的关系而不会完全瘫痪模型。 – benjismith 2009-04-18 16:22:29

+1

对于同一个艺术家,您将拥有多个记录,但具有不同的标签。数据仓库中的通常做法是拥有非规格化数据,以提高查询性能。这是采用ETL作业(Extract-Transform-Load数据)的原因之一:将标准化关系模型转换为数据仓库特定模型(维度或星型模型)。 – 2009-04-18 17:04:04

2

我的方法更通用一些,将过滤器参数放入表中,然后使用GROUP BY,HAVING和COUNT过滤结果。我已经多次使用这种基本方法进行一些非常复杂的“搜索”,并且它工作得很好(对我来说咧嘴笑)。

我最初也不参加艺术家和场地维度表。我会将结果作为id(只需要artist_tag和venue_tag),然后将结果加入到艺术家和场地表中以获取这些维度值。 (基本上,搜索实体ID在一个子查询,然后在外部查询获得的尺寸值需要。让他们分开应该改善的事情...)

DECLARE @artist_filter TABLE (
    tag_id INT 
) 

DECLARE @venue_filter TABLE (
    tag_id INT 
) 

INSERT INTO @artist_filter 
SELECT id FROM tag 
WHERE name IN ('techno','trombone') 

INSERT INTO @venue_filter 
SELECT id FROM tag 
WHERE name IN ('cheap-beer','great-most-pits') 


SELECT 
    concert.id AS concert_id, 
    concert.date AS concert_date, 
    artist.id AS artist_id, 
    venue.id AS venue_id 
FROM 
    concert 
INNER JOIN 
    artist_tag 
    ON artist_tag.artist_id = concert.artist_id 
INNER JOIN 
    @artist_filter AS [artist_filter] 
    ON [artist_filter].tag_id = artist_tag.id 
INNER JOIN 
    venue_tag 
    ON venue_tag.venue_id = concert.venue_id 
INNER JOIN 
    @venue_filter AS [venue_filter] 
    ON [venue_filter].tag_id = venue_tag.id 
WHERE 
    concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH) 
GROUP BY 
    concert.id, 
    concert.date, 
    artist_tag.artist_id, 
    venue_tag.id 
HAVING 
    COUNT(DISTINCT [artist_filter].id) = (SELECT COUNT(*) FROM @artist_filter) 
    AND 
    COUNT(DISTINCT [venue_filter].id) = (SELECT COUNT(*) FROM @venue_filter) 

(我在上网本和痛苦,所以我就离开了外部查询从艺术家和地点表让艺术家和地点名称笑容

编辑
注:

另一种选择是过滤艺术子查询/派生表中的t_tag和venue_tag表。这是否值得,取决于Concert表上的连接有多大影响力。我的假设是,有很多艺术家和场地,但是一旦在音乐会桌上过滤(本身被日期过滤),艺术家/场馆的数量就会大大减少。

另外,经常需要/希望处理没有指定artist_tags和/或venue_tags的情况。根据经验,最好以编程方式处理。也就是说,使用IF语句和特别适合这些情况的查询。可以编写一个单独的SQL查询来处理它,但比编程替代方法慢得多。同样,多次编写类似的查询可能会看起来杂乱并且降低可维护性,但复杂性的增加需要将其作为单个查询来维护。

编辑

另一个类似的布局可能是...
- 过滤器由艺术家为sub_query演唱会/ derived_table
- 筛选功能作为场地sub_query/derived_table
- 加入对维表到结果获取姓名等

(级联滤波)

SELECT 
    <blah> 
FROM 
    (
    SELECT 
     <blah> 
    FROM 
     (
     SELECT 
      <blah> 
     FROM 
      concert 
     INNER JOIN 
      artist_tag 
     INNER JOIN 
      artist_filter 
     WHERE 
     GROUP BY 
     HAVING 
    ) 
    INNER JOIN 
     venue_tag 
    INNER JOIN 
     venue_filter 
    GROUP BY 
    HAVING 
) 
INNER JOIN 
    artist 
INNER JOIN 
    venue 

通过级联过滤,每个后续过滤都有一个必须处理的减少集。这可以减少查询的GROUP BY - HAVING部分完成的工作。对于两个级别的过滤,我想这不太可能是戏剧性的。

原始的可能仍然更高性能,因为它有利于以不同方式进行附加过滤。在您的例子:
- 可能有许多艺术家在你的日期范围,但很少能满足至少一个标准
- 有可能是在你的日期范围内很多场馆,但很少能满足至少一个标准
- 前在GROUP BY,但是,所有的演唱会被淘汰,其中...
--->艺术家(S)符合标准无
--->和/或场地符合标准无

如果您按许多标准进行搜索,则此过滤会降级。此外,在场地和/或艺术家共享大量标签的情况下,过滤也会降低。

那么,我什么时候会使用原件,或者何时使用Cascaded版本?
- 原始:很少的搜索条件和场地/艺术家DIS相似彼此
- 级联:搜索准则或场地地块/艺术家往往是相似的

0

这种情况是不是技术上的多个事实数据表。你在场馆&标签以及艺术家&标签之间有多对多的关系。

我想MatBailie提供了一些有趣的例子,但是我觉得这可以简单得多,如果你以有用的方式处理你的应用程序中的参数。

除了事实表上的用户生成查询之外,您需要两个静态查询才能首先为用户提供参数选项。其中一个是Venue适当标签的列表,另一个是适合Artist的标签。

地点适当的标签:

SELECT DISTINCT tag_id, tag.name as VenueTagName 
FROM venue_tag 
INNER JOIN tag 
ON venue_tag.tag_id = tag.id 

艺术家适当标签:

SELECT DISTINCT tag_id, tag.name as ArtistTagName 
FROM artist_tag 
INNER JOIN tag 
ON artist_tag.tag_id = tag.id 

这两个查询驱动一些下拉或其他参数选择控制。在报告系统中,您应该尽量避免传递字符串变量。在您的应用程序中,您将变量的字符串名称呈现给用户,但将整数ID传递回数据库。

例如当用户选择的标签,你把tag.id值,并将其提供给您的查询(在那里我有(1,2)和下面的(100,200)位):

SELECT 
    concert.id AS concert_id, 
    concert.date AS concert_date, 
    artist.id AS artist_id, 
    artist.name AS artist_name, 
    venue.id AS venue_id, 
    venue.name AS venue_name, 
FROM 
concert 
INNER JOIN artist 
    ON artist.id = concert.artist_id 
INNER JOIN artist_tag 
    ON artist.id = artist_tag.artist_id 
INNER JOIN venue 
    ON venue.id = concert.venue_id 
INNER JOIN venue_tag 
    ON venue.id = venue_tag.venue_id 
WHERE venue_tag.tag_id in (1,2) -- Assumes that the IDs 1 and 2 map to "cheap-beer" and "great-mosh-pits) 
AND artist_tag.tag_id in (100,200) -- Assumes that the IDs 100 and 200 map to "techno" and "trombone") Sounds like a wild night of drunken moshing to brass band techno! 
AND concert.date BETWEEN NOW() AND (NOW() + INTERVAL 1 MONTH)