2011-01-14 11 views
2

私は実際の人物データ(person)、チーム(team)、エントリ(athlete)を格納する3つのテーブルを持っています。 3つのテーブルのスキーマは次のとおりです。SQLクエリパフォーマンスを向上させる

Database schema

各チームでは、2人の以上の選手があるかもしれません。

私は最も頻繁なペアを生成するためにクエリを作成しようとしています。これは、2つのチームでプレイする人々を意味します。

SELECT p1.surname, p1.name, p2.surname, p2.name, COUNT(*) AS freq 
FROM person p1, athlete a1, person p2, athlete a2 
WHERE 
    p1.id = a1.person_id AND 
    p2.id = a2.person_id AND 
    a1.team_id = a2.team_id AND 
    a1.team_id IN 
      (SELECT team.id 
      FROM team, athlete 
      WHERE team.id = athlete.team_id 
      GROUP BY team.id 
      HAVING COUNT(*) = 2) 
GROUP BY p1.id 
ORDER BY freq DESC 

明らかに、これはリソースを消費するクエリです。それを改善する方法はありますか?

+0

インデックス作成は役に立ちますか? – Sudantha

+0

実際、すべてが適切にインデックスされています。問題は、データベースに数十万行(人:10k、チーム:450k、アスリート:900k)が含まれていることです。 – Anax

+1

サブクエリにジョイン句がない - サブクエリにチームテーブルとアスリートテーブルの両方が必要ですか? –

答えて

4

パフォーマンスヒント1:ここにはathleteテーブルが必要です。

+0

良いキャッチ、+1。 – Anax

2

トリガーを使用してチームと人物のテーブルにカウンターを維持する次の方法を検討すると、2人以上のアスリートと2人以上のチームに所属するチームを簡単に見つけることができます。

(注:私はより良いデータの整合性を強制されます複合キーの賛成であなたのアスリートテーブルから代理IDキーを削除した私はteam_athleteするアスリートをも名前を変更しました。)

drop table if exists person; 
create table person 
(
person_id int unsigned not null auto_increment primary key, 
name varchar(255) not null, 
team_count smallint unsigned not null default 0 
) 
engine=innodb; 

drop table if exists team; 
create table team 
(
team_id int unsigned not null auto_increment primary key, 
name varchar(255) not null, 
athlete_count smallint unsigned not null default 0, 
key (athlete_count) 
) 
engine=innodb; 

drop table if exists team_athlete; 
create table team_athlete 
(
team_id int unsigned not null, 
person_id int unsigned not null, 
primary key (team_id, person_id), -- note clustered composite PK 
key person(person_id) -- added index 
) 
engine=innodb; 

delimiter # 

create trigger team_athlete_after_ins_trig after insert on team_athlete 
for each row 
begin 
    update team set athlete_count = athlete_count+1 where team_id = new.team_id; 
    update person set team_count = team_count+1 where person_id = new.person_id; 
end# 

delimiter ; 

insert into person (name) values ('p1'),('p2'),('p3'),('p4'),('p5'); 
insert into team (name) values ('t1'),('t2'),('t3'),('t4'); 

insert into team_athlete (team_id, person_id) values 
(1,1),(1,2),(1,3), 
(2,3),(2,4), 
(3,1),(3,5); 

select * from team_athlete; 
select * from person; 
select * from team; 

select * from team where athlete_count >= 2; 
select * from person where team_count >= 2; 

EDIT唯一の2名のチームを含むビューを作成します

は次のように最初は誤解質問を追加しました。

drop view if exists teams_with_2_players_view; 

create view teams_with_2_players_view as 
select 
t.team_id, 
ta.person_id, 
p.name as person_name 
from 
team t 
inner join team_athlete ta on t.team_id = ta.team_id 
inner join person p on ta.person_id = p.person_id 
where 
t.athlete_count = 2; 

このビューを使用して、最も頻繁に発生する人のペアを見つけます。

select 
p1.person_id as p1_person_id, 
p1.person_name as p1_person_name, 
p2.person_id as p2_person_id, 
p2.person_name as p2_person_name, 
count(*) as counter 
from 
teams_with_2_players_view p1 
inner join teams_with_2_players_view p2 on 
    p2.team_id = p1.team_id and p2.person_id > p1.person_id 
group by 
p1.person_id, p2.person_id 
order by 
counter desc; 

希望はこのことができます:)

EDIT 2チェックパフォーマンス

select count(*) as counter from person; 

+---------+ 
| counter | 
+---------+ 
| 10000 | 
+---------+ 
1 row in set (0.00 sec) 

select count(*) as counter from team; 

+---------+ 
| counter | 
+---------+ 
| 450000 | 
+---------+ 
1 row in set (0.08 sec) 

select count(*) as counter from team where athlete_count = 2; 

+---------+ 
| counter | 
+---------+ 
| 112644 | 
+---------+ 
1 row in set (0.03 sec) 

select count(*) as counter from team_athlete; 

+---------+ 
| counter | 
+---------+ 
| 1124772 | 
+---------+ 
1 row in set (0.21 sec) 

explain 
select 
p1.person_id as p1_person_id, 
p1.person_name as p1_person_name, 
p2.person_id as p2_person_id, 
p2.person_name as p2_person_name, 
count(*) as counter 
from 
teams_with_2_players_view p1 
inner join teams_with_2_players_view p2 on 
    p2.team_id = p1.team_id and p2.person_id > p1.person_id 
group by 
p1.person_id, p2.person_id 
order by 
counter desc 
limit 10; 

+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+ 
| id | select_type | table | type | possible_keys  | key   | key_len | ref     | rows | Extra          | 
+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+ 
| 1 | SIMPLE  | t  | ref | PRIMARY,t_count_idx | t_count_idx | 2 | const    | 86588 | Using index; Using temporary; Using filesort | 
| 1 | SIMPLE  | t  | eq_ref | PRIMARY,t_count_idx | PRIMARY  | 4 | foo_db.t.team_id |  1 | Using where         | 
| 1 | SIMPLE  | ta | ref | PRIMARY,person  | PRIMARY  | 4 | foo_db.t.team_id |  1 | Using index         | 
| 1 | SIMPLE  | p  | eq_ref | PRIMARY    | PRIMARY  | 4 | foo_db.ta.person_id |  1 |            | 
| 1 | SIMPLE  | ta | ref | PRIMARY,person  | PRIMARY  | 4 | foo_db.t.team_id |  1 | Using where; Using index      | 
| 1 | SIMPLE  | p  | eq_ref | PRIMARY    | PRIMARY  | 4 | foo_db.ta.person_id |  1 |            | 
+----+-------------+-------+--------+---------------------+-------------+---------+---------------------+-------+----------------------------------------------+ 

6 rows in set (0.00 sec) 

select 
p1.person_id as p1_person_id, 
p1.person_name as p1_person_name, 
p2.person_id as p2_person_id, 
p2.person_name as p2_person_name, 
count(*) as counter 
from 
teams_with_2_players_view p1 
inner join teams_with_2_players_view p2 on 
    p2.team_id = p1.team_id and p2.person_id > p1.person_id 
group by 
p1.person_id, p2.person_id 
order by 
counter desc 
limit 10; 

+--------------+----------------+--------------+----------------+---------+ 
| p1_person_id | p1_person_name | p2_person_id | p2_person_name | counter | 
+--------------+----------------+--------------+----------------+---------+ 
|   221 | person 221  |   739 | person 739  |  5 | 
|   129 | person 129  |   249 | person 249  |  5 | 
|   874 | person 874  |   877 | person 877  |  4 | 
|   717 | person 717  |   949 | person 949  |  4 | 
|   395 | person 395  |   976 | person 976  |  4 | 
|   415 | person 415  |   828 | person 828  |  4 | 
|   287 | person 287  |   470 | person 470  |  4 | 
|   455 | person 455  |   860 | person 860  |  4 | 
|   13 | person 13  |   29 | person 29  |  4 | 
|   1 | person 1  |   743 | person 743  |  4 | 
+--------------+----------------+--------------+----------------+---------+ 
10 rows in set (2.02 sec) 
+0

あなたのアプローチは興味深いアイデアを含んでいますが、サブクエリの必要性がなくなるだけで(全体のパフォーマンスが向上します)、元の質問には答えられません。私はプレイヤーXが参加したチーム数を知ることに興味がありませんが、プレイヤーXとプレイヤーYが一緒にプレイして、そのリストからトップペアを取得しました。 – Anax

+0

yupが最初にあなたの疑問を誤解しました - 追加のコードを追加しました。 –

0

追加の制約がa1.person_idがあるはず!= a2.person_idは、とのペアを作成しないようにします同じプレイヤー?これは結果の最終的な順序には影響しませんが、カウントの精度には影響します。

可能であれば、チームテーブルにathlete_count(インデックス付き)という列を追加できます。この列は、チームに追加または削除されるたびに更新され、アスリート全体を通過する必要があるサブクエリを回避できます2人の選手チームを見つけるためのテーブル。

UPDATE1: また、元のクエリを正しく理解している場合、p1.idでグループ化すると、プレイヤーが2人のチームでプレーした回数だけ取得され、ペア自体のカウントは取得されません。あなたは、p1.id、p2.idでグループ化しなければならないかもしれません。まさに二人の最も内側の事前集計することでちょうど二つのPERのTEAM

に基づいて

0

REVISION、私はMIN()とMAXを(使用して、チームごとに単一の行に人格とPersonBで各チームを得ることができます)。このようにして、人のIDは、将来のチームのために比較されるように常にローハイペア設定になります。それから、すべてのチームの共通のMate1とMate2で​​COUNTを照会して、自分の名前を直接取得できます。私は次のようでどうなるのチームメイト

、任意の数のチームのため

SELECT STRAIGHT_JOIN 
     p1.surname, 
     p1.name, 
     p2.surname, 
     p2.name, 
     TeamAggregates.CommonTeams 
    from 
    (select PreQueryTeams.Mate1, 
       PreQueryTeams.Mate2, 
       count(*) CommonTeams 
      from 
       (SELECT team_id, 
         min(person_id) mate1, 
         max(person_id) mate2 
        FROM 
         athlete 
        group by 
         team_id 
        having count(*) = 2) PreQueryTeams 
      group by 
       PreQueryTeams.Mate1, 
       PreQueryTeams.Mate2 ) TeamAggregates, 
     person p1, 
     person p2 
    where 
      TeamAggregates.Mate1 = p1.Person_ID 
     and TeamAggregates.Mate2 = p2.Person_ID 
    order by 
     TeamAggregates.CommonTeams 

ORIGINAL ANSWER。内側のprequeryは、最初に個々のチームの人々のすべての可能な組み合わせに参加しますが、person1を持っている< person2 person1とperson2と同じ人を数えることがなくなります..さらに、より高い番号の人物IDに基づいて逆を防ぐ... ...ここで

athlete person team 
1   1  1 
2   2  1 
3   3  1 
4   4  1 
5   1  2 
6   3  2 
7   4  2 
8   1  3 
9   4  3 

So, from team 1 you would get person pairs of 
1,2 1,3 1,4  2,3  2,4 3,4 
and NOT get reversed duplicates such as 
2,1 3,1 4,1  3,2  4,2 4,3 
nor same person 
1,1 2,2 3,3 4,4 


Then from team 2, you would hav pairs of 
1,3 1,4 3,4 

Finally in team 3 the single pair of 
1,4 

thus teammates 1,4 have occured in 3 common teams. 

SELECT STRAIGHT_JOIN 
     p1.surname, 
     p1.name, 
     p2.surname, 
     p2.name, 
     PreQuery.CommonTeams 
    from 
     (select 
      a1.Person_ID Person_ID1, 
      a2.Person_ID Person_ID2, 
      count(*) CommonTeams 
     from 
      athlete a1, 
      athlete a2 
     where 
       a1.Team_ID = a2.Team_ID 
      and a1.Person_ID < a2.Person_ID 
     group by 
      1, 2 
     having CommonTeams > 1) PreQuery, 
     person p1, 
     person p2 
    where 
      PreQuery.Person_ID1 = p1.id 
     and PreQuery.Person_ID2 = p2.id 
    order by 
     PreQuery.CommonTeams 
0

、のようなSQL SELECTクエリのパフォーマンスを向上させるためにいくつかのヒント:

  • 使用SET NOCOUNT ONは、このように パフォーマンスを向上させるネットワークトラフィックを減少させる助けとなります。代わりに、ユーザーの命名ダイナミッククエリーのexecute
  • IF EXISTS またはSELECT操作のためにselect *使用select column1,column2,..を使用しないでください
  • 避けの
  • 使用完全修飾プロシージャ名(例えば database.schema.objectname
  • 使用sp_executesqlsp_procedureNameのようなストアドプロシージャBecouse、 ストアドプロシージャ名startをsp_とし、SQLを最初に master dbで検索します。クエリのパフォーマンスを低下させる可能性があります。
関連する問題