2010-06-30 2 views
1

J'ai une table contact_relationship qui stocke la force rapportée d'une relation entre un contact et un autre à un moment donné.aide à optimiser la requête (montre la force des relations bidirectionnelles entre les contacts)

mysql> desc contact_relationship; 
+------------------+-----------+------+-----+-------------------+-----------------------------+ 
| Field   | Type  | Null | Key | Default   | Extra      | 
+------------------+-----------+------+-----+-------------------+-----------------------------+ 
| relationship_id | int(11) | YES |  | NULL    |        | 
| contact_id  | int(11) | YES | MUL | NULL    |        | 
| other_contact_id | int(11) | YES |  | NULL    |        | 
| strength   | int(11) | YES |  | NULL    |        | 
| recorded   | timestamp | NO |  | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP | 
+------------------+-----------+------+-----+-------------------+-----------------------------+ 

maintenant je veux obtenir une liste des relations dans les deux sens entre les contacts (ce qui signifie qu'il ya deux lignes, l'une avec le contact d'une spécification d'une force de la relation avec le contact b et un autre avec un contact b spécifiant une force au contact d'un - la force de la relation bidirectionnelle est la plus petite de ces deux valeurs de force).

cela est la requête que je suis venu avec, mais il est assez lent:

select 
    mrcr1.contact_id, 
    mrcr1.other_contact_id, 
    case when (mrcr1.strength < mrcr2.strength) then 
     mrcr1.strength 
    else 
     mrcr2.strength 
    end strength 
from ( 
    select 
     cr1.* 
    from ( 
     select 
      contact_id, 
      other_contact_id, 
      max(recorded) as max_recorded 
     from 
      contact_relationship 
     group by 
      contact_id, 
      other_contact_id 
    ) as cr2 
    inner join contact_relationship cr1 on 
     cr1.contact_id = cr2.contact_id 
     and cr1.other_contact_id = cr2.other_contact_id 
     and cr1.recorded = cr2.max_recorded 
) as mrcr1, 
( 
    select 
     cr3.* 
    from ( 
     select 
      contact_id, 
      other_contact_id, 
      max(recorded) as max_recorded 
     from 
      contact_relationship 
     group by 
      contact_id, 
      other_contact_id 
    ) as cr4 
    inner join contact_relationship cr3 on 
     cr3.contact_id = cr4.contact_id 
     and cr3.other_contact_id = cr4.other_contact_id 
     and cr3.recorded = cr4.max_recorded 
) as mrcr2 
where 
    mrcr1.contact_id = mrcr2.other_contact_id 
    and mrcr1.other_contact_id = mrcr2.contact_id 
    and mrcr1.contact_id != mrcr1.other_contact_id 
    and mrcr2.contact_id != mrcr2.other_contact_id 
    and mrcr1.contact_id <= mrcr1.other_contact_id; 

quelqu'un a des recommandations sur la façon d'accélérer?

Notez que, parce qu'un utilisateur peut spécifier la force de sa relation avec un utilisateur particulier plus d'une fois, vous devez seulement saisir l'enregistrement le plus récent pour chaque paire de contacts.

mise à jour: voici le résultat d'expliquer la requête ...

+----+-------------+----------------------+-------+----------------------------------------------------------------------------------------+------------------------------+---------+-------------------------------------+-------+--------------------------------+ 
| id | select_type | table    | type | possible_keys                   | key       | key_len | ref         | rows | Extra       | 
+----+-------------+----------------------+-------+----------------------------------------------------------------------------------------+------------------------------+---------+-------------------------------------+-------+--------------------------------+ 
| 1 | PRIMARY  | <derived2>   | ALL | NULL                     | NULL       | NULL | NULL        | 36029 | Using where     | 
| 1 | PRIMARY  | <derived4>   | ALL | NULL                     | NULL       | NULL | NULL        | 36029 | Using where; Using join buffer | 
| 4 | DERIVED  | <derived5>   | ALL | NULL                     | NULL       | NULL | NULL        | 36021 |        | 
| 4 | DERIVED  | cr3     | ref | contact_relationship_index_1,contact_relationship_index_2,contact_relationship_index_3 | contact_relationship_index_2 | 10  | cr4.contact_id,cr4.other_contact_id |  1 | Using where     | 
| 5 | DERIVED  | contact_relationship | index | NULL                     | contact_relationship_index_3 | 14  | NULL        | 37973 | Using index     | 
| 2 | DERIVED  | <derived3>   | ALL | NULL                     | NULL       | NULL | NULL        | 36021 |        | 
| 2 | DERIVED  | cr1     | ref | contact_relationship_index_1,contact_relationship_index_2,contact_relationship_index_3 | contact_relationship_index_2 | 10  | cr2.contact_id,cr2.other_contact_id |  1 | Using where     | 
| 3 | DERIVED  | contact_relationship | index | NULL                     | contact_relationship_index_3 | 14  | NULL        | 37973 | Using index     | 
+----+-------------+----------------------+-------+----------------------------------------------------------------------------------------+------------------------------+---------+-------------------------------------+-------+--------------------------------+ 
+0

Pouvez-vous poster le plan d'exécution? –

+0

Pouvez-vous publier le SGBD que vous utilisez? –

+0

J'utilise mysql – emh

Répondre

0

Vous perdez beaucoup de beaucoup de beaucoup de temps en sélectionnant le plus récent. 2 options:

1- Modifiez la façon dont vous stockez les données et disposez d'une table contenant uniquement un enregistrement récent, et une autre table comme un enregistrement historique.

2- Utilisez la requête analytique pour sélectionner l'enregistrement le plus récent, si votre SGBD vous le permet. Quelque chose comme

Select first_value(strength) over(partition by contact_id, other_contact_id order by recorded desc) 
from contact_relationship 

Une fois que vous avez la bonne ligne d'enregistrement, je pense que votre requête sera beaucoup plus rapide.

+0

vous m'avez fait penser que je pourrais utiliser une table temporaire pour sortir toutes les nouvelles lignes de relation (voir ci-dessous). merci! – emh

0

réponse de Scorpi0 m'a fait penser que je pourrais utiliser une table temporaire ...

create temporary table mrcr1 (
    contact_id int, 
    other_contact_id int, 
    strength int, 
    index mrcr1_index_1 (
     contact_id, 
     other_contact_id 
    ) 
) replace as 
    select 
     cr1.contact_id, 
     cr1.other_contact_id, 
     cr1.strength from ( 
      select 
       contact_id, 
       other_contact_id, 
       max(recorded) as max_recorded 
      from 
       contact_relationship 
      group by 
       contact_id, other_contact_id 
     ) as cr2 
     inner join 
      contact_relationship cr1 on 
       cr1.contact_id = cr2.contact_id 
       and cr1.other_contact_id = cr2.other_contact_id 
       and cr1.recorded = cr2.max_recorded; 

que je devais faire deux fois (deuxième fois dans une table temporaire mrcr2 du nom) parce que MySQL a une limitation où vous ne peut pas alias deux fois la même table temporaire dans une requête.

avec mes deux tables temporaires créés ma requête devient alors:

select 
    mrcr1.contact_id, 
    mrcr1.other_contact_id, 
    case when (mrcr1.strength < mrcr2.strength) then 
     mrcr1.strength 
    else 
     mrcr2.strength 
    end strength 
from 
    mrcr1, 
    mrcr2 
where 
    mrcr1.contact_id = mrcr2.other_contact_id 
    and mrcr1.other_contact_id = mrcr2.contact_id 
    and mrcr1.contact_id != mrcr1.other_contact_id 
    and mrcr2.contact_id != mrcr2.other_contact_id 
    and mrcr1.contact_id <= mrcr1.other_contact_id; 
+0

malheureusement, en production, je n'ai pas accès à créer des tables temporaires :( – emh

+0

la table temporaire ne sont pas vraiment une bonne idée.Regardez avec votre supérieur que vous devez changer votre structure.Un tableau pour la production et la requête en cours, et un –

+0

pouvez-vous expliquer pourquoi les tables temporaires sont une mauvaise idée? – emh