2009-04-18 6 views
0

저는 사용자가 임의로 사실 테이블 집합을 쿼리하고 각 사실 테이블의 여러 차원 테이블을 제약 할 수있는보고 시스템을 개발 중입니다. 필자는 제약 조건 매개 변수를 기반으로 모든 올바른 조인과 하위 쿼리를 자동으로 어셈블하는 쿼리 작성기 클래스를 작성했으며 모든 것이 설계된대로 작동합니다.보고 쿼리 : 여러 가지 사실 테이블을 결합하는 가장 좋은 방법은 무엇입니까?

그러나 나는 가장 효율적인 쿼리를 생성하지 않는다고 생각합니다. 몇 백만 레코드가있는 테이블 집합에서이 쿼리는 실행하는 데 약 10 초가 걸리고, 1 초 미만의 범위에서 쿼리를 내리고 싶습니다. 하위 쿼리를 제거 할 수 있다면 결과가 훨씬 효율적이라는 느낌이 들었습니다.

내 실제 스키마 (훨씬 더 복잡함)를 표시하는 대신, 전체 응용 프로그램 및 데이터 모델을 설명하지 않고도 요점을 설명하는 유사한 예제를 보여 드리겠습니다.

아티스트와 장소가있는 콘서트 정보 데이터베이스가 있다고 가정 해 보겠습니다. 사용자는 임의로 아티스트와 장소에 태그를 지정할 수 있습니다. 따라서 스키마는 다음과 같습니다.

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'콘서트에서 공연 한 오늘부터 1 개월 이내에 발생한 모든 콘서트에 대한 데이터베이스를 쿼리한다고 가정 해 봅시다. - 구덩이 '꼬리표.

이 같은 모습을 마련 할 수있었습니다 가장 좋은 질의 :

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

+0

해결/닫기/...로 표시 할 시간 : –

답변

1

모델을 역 정규화하십시오. 장소 및 아티스트 테이블에 태그 이름을 포함하십시오. 이 방법을 사용하면 다 대다 관계를 피할 수 있으며 간단한 별표 스키마가 있습니다.

이 비정규 화를 적용하면 where 절이 두 테이블 (아티스트와 장소)에서이 추가 tag_name 필드 만 확인할 수 있습니다.

+0

하지만 비정규 화하는 경우 아티스트 나 장소에서 여러 개의 태그를 허용하려면 어떻게해야합니까? 문제는 모델을 완전히 망가 뜨리지 않고 다 대 다 관계를 제거 할 수 없다는 것입니다. – benjismith

+1

동일한 아티스트에 대해 여러 태그가있는 여러 레코드가 있습니다. 데이터웨어 하우징에서 비정규 화 된 데이터를 사용하여 쿼리 성능을 향상시키는 것이 일반적입니다. 이는 정규화 된 관계형 모델을 데이터웨어 하우스 특정 모델 (차원 또는 별 모델)로 변환하는 ETL 작업 (추출 - 변환 -로드 데이터)이 사용되는 이유 중 하나입니다. –

+0

몇 가지 가정에 동의합니다. 이로 인해 데이터 크기가 크게 증가 할 수 있으며 사용 가능한 공간이 있습니까? (드라이브는 싸다 ...) 변화 할 수있는 데이터로, 비정규 화 된 데이터를 리프레시하는 것은 CPU 등의 측면에서 비용이 많이 든다. 데이터는 상대적으로 정적이며 밤에 ETL 될 수 있는가? 그렇다면 그러한 비정규 화 (예 : 플랫 파일 형식)는보고에 매우 유용 할 수 있습니다. – MatBailie

2

내 접근 방식은 필터 매개 변수를 테이블에 넣은 다음 GROUP BY, HAVING 및 COUNT를 사용하여 결과를 필터링하는 방식으로 좀 더 일반적인 방식입니다.나는 매우 정교한 '검색'을 위해이 기본 접근법을 여러 번 사용했으며 매우 잘 작동합니다 (나를 위해 웃음).

처음에는 아티스트 및 Venue 차원 테이블에 참여하지 않습니다. 그 결과를 이드 (art_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) 

(I 넷북에있어 및 그것은 고통, 그래서 나는 작가 및 장소 테이블에서 미소)

편집을 작가와 장소 이름을 받고 외부 쿼리를 떠날거야
참고 :

또 다른 옵션은 필터링하는 것 예술가 하위 쿼리/파생 테이블의 t_tag 및 venue_tag 테이블 이것이 가치가 있는지 여부는 콘서트 테이블에 얼마나 영향을 주는지에 달려 있습니다. 여기 내 가정은 많은 아티스트와 장소가 있지만 콘서트 테이블 (날짜별로 필터링 된)에서 필터링되면 아티스트/장소의 수가 크게 감소합니다.

또한, 종종 artist_tags 및/또는 venue_tags가 지정되지 않은 경우를 처리 할 필요가 있습니다. 경험상 프로그램 적으로이를 처리하는 것이 좋습니다. 즉, 이러한 경우에 특별히 적합한 IF 문과 쿼리를 사용하십시오. 단일 SQL 쿼리는이를 처리하기 위해 작성 될 수 있지만 프로그래밍 방식 대안보다 훨씬 느립니다. 똑같은 방식으로 비슷한 쿼리를 여러 번 작성하면 유지 관리가 어려워 질 수 있지만 복잡성이 증가하면이 쿼리를 단일 쿼리로 유지해야하는 경우가 종종 있습니다.

편집

또 다른 유사한 레이아웃이 될 수 ...
- sub_query 같은 작가의 필터 콘서트/derived_table
- derived_table sub_query/같은 장소별로 필터 결과
-에 차원 테이블에 결과를 가입 , 이름을 얻는 등

(계단식 필터링)

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 섹션에 의해 수행되는 작업을 줄일 수있다. 두 단계의 필터링에 대해 나는 이것이 드라마틱하지 않을 것으로 추측합니다.

원본은 여전히 ​​다른 방식으로 추가 필터링을하는 것이 더 효과적 일 수 있습니다. 귀하의 예 :
- 귀하의 날짜 범위에는 많은 아티스트가있을 수 있지만 하나 이상의 기준을 충족하는 아티스트가 거의 없습니다.
- 날짜 범위가 많지만 하나 이상의 기준을 충족하는 곳이 많습니다.
- 이전 그룹에서 그러나, 모든 공연이 제거되는 곳 ...
---> 작가가 (들)
---> 기준을 전혀 충족되지 및/또는 장소가 기준들이없는

여러 기준으로 검색하면이 필터링이 저하됩니다. 또한 장소 및/또는 아티스트가 많은 태그를 공유하는 경우 필터링도 저하됩니다.

언제 원본을 사용해야합니까? 아니면 계단식 버전은 언제 사용합니까? 검색 기준이나 장소의 제비가/예술가들이 이러한 상황은 기술적으로 여러 팩트 테이블 아니다

+0

"tag_artist_user"테이블을 사용하지 않았습니다. 예제의 결과에 영향을 미치지 않았습니다. – MatBailie

+0

죄송합니다. "tag_artist_user"테이블은 이전 초안 쿼리의 아티팩트였습니다. 원래 게시물을 편집하여 삭제했습니다. – benjismith

+0

나는 필터 테이블을 사용하는 접근법을 좋아하지만 테이블 변수는 사용하지 않는다. 당신은 이것들에 인덱스가 없습니다. 테이블 변수에 인덱스를 가질 수는 있지만, 정당한 이유로 통계는 없습니다. 귀하의 솔루션 역시 SQL Server에만 해당됩니다. 테이블 변수를 사용하면 SQL Server는 테이블 변수에 통계가 없기 때문에 단일 행이 있다고 가정하는 실행 계획을 생성합니다. 테이블 변수에 행이 많지 않은 경우에는 정상적으로 수행 될 수 있지만 더 많은 경우 성능이 저하됩니다. – Davos

0

유사한 경향 :
- : - 원래 계단식 몇 가지 검색 기준 및 장소는/예술가 DIS-유사한 서로
에서이다. 당신은 공연장 &과 아티스트 & 태그 사이에 많은 관계가 있습니다.

MatBailie는 위의 흥미로운 예제를 제공한다고 생각하지만, 유용한 방법으로 어플리케이션의 매개 변수를 처리하면 훨씬 간단해질 수 있습니다.

사실 테이블에 대한 사용자 생성 쿼리 외에도 사용자에게 매개 변수 옵션을 제공하려면 두 가지 정적 쿼리가 필요합니다. 그 중 하나는 Venue에 적합한 태그의 목록이고 다른 하나는 아티스트에 적합한 태그입니다.

장소 적절한 태그 :

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) 
+0

'(1,2)의 venue_tag.tag_id가 OP의 요구 사항을 충족시키지 못합니다. 이는'싼 맥주 '또는'위대한 모쉬핏'을 제공하지만 OP는'싼 맥주 '와'위대한 모쉬핏'을 가진 장소를 원한다. 그것은 여러 행을 검사하는 것입니다. * (싼 맥주를 한 행에, 위대한 모스핏을 한 행에 넣은 다음, 같은 장소에 두 행이 모두 있어야 함) *. 또한 SQL은 매개 변수화 목록에서 잘 알려지지 않았습니다. OP가 '싼 맥주'와 '위대한 모스핏'과 '자유 입국'을 요구하면 어떨까요? 이 대답은 'n'속성을 증명하기위한 일반화 된 접근법을 제공하지 않습니다. – MatBailie

+0

@MatBailie 네, 맞습니다. 태그에 대한 AND 요구 사항을 고려하지 않았습니다. 내 예제는 OR 예제 만 처리합니다. 나는 매개 변수 처리에 관한 나의 요점은 여전히 ​​유효하다고 생각하지만, 첫 번째 예에서 HAVING 절의 태그 수를 비교하는 이유를 알 수 있습니다. 첫 번째 예에서는 사실 +1이 일반화되었습니다. – Davos