2009-04-18 67 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)