2010-11-22 4 views
2

La table se compose des colonnes calling_party et called_party et l'enregistrement décrit la connexion entre deux utilisateurs où l'un joue un rôle d'appelant et l'autre est appelé party.Une suggestion pour optimiser la requête suivante qui compte commun et tous les voisins?

Les deux mêmes utilisateurs peuvent avoir deux connexions - dans ce cas, les rôles appelant/appelé sont activés lorsque la direction est modifiée.

Dans la table d'origine (monthly_connections), j'ai ajouté des colonnes supplémentaires common_neighbors et total_neighbors où le nombre de voisins communs et totaux est stocké. Pour clarifier les termes communs et je total_neighbors ajouté l'image suivante:

alt text

Dans ce cas pour la connexion observée il y a 2 communes voisines d'appelant et l'appelé et 6 voisins au total.

Afin d'obtenir ces deux valeurs que j'écrit la procédure stockée suivante:

CREATE PROCEDURE [dbo].[spCountNeighbors] 

AS 

Declare 
@CallingParty varchar(50), 
@CalledParty varchar(50), 
@RecordsUpdated int 

SET @CallingParty ='a' 
SET @RecordsUpdated = 0 
PRINT GETDATE() 
WHILE @CallingParty IS NOT NULL BEGIN 
    SET @CallingParty = NULL 
    SELECT TOP 1 @CallingParty = calling_party, @CalledParty = called_party FROM monthly_connections WHERE common_neighbors IS NULL 
    --PRINT @CallingParty 
    IF @CallingParty IS NOT NULL BEGIN 
    WITH callingPartyNeighbors AS 
    (
     SELECT called_party as neighbor FROM monthly_connections WHERE calling_party = @CallingParty 
     UNION 
     SELECT calling_party as neighbor FROM monthly_connections WHERE called_party = @CallingParty 
    ), 
    calledPartyNeighbors AS 
    (
     SELECT calling_party as neighbor FROM monthly_connections WHERE called_party = @CalledParty 
     UNION 
     SELECT called_party as neighbor FROM monthly_connections WHERE calling_party = @CalledParty 
    ) 

     UPDATE mc SET common_neighbors = (SELECT COUNT (*) FROM 
     (
     SELECT neighbor FROM callingPartyNeighbors 
     INTERSECT 
     SELECT neighbor FROM calledPartyNeighbors 
     ) 
     t1 
     ), 
     total_neighbors = (SELECT COUNT (*) FROM 
     (
     SELECT neighbor FROM callingPartyNeighbors 
     UNION 
     SELECT neighbor FROM calledPartyNeighbors 
     ) 
     t2 
     ) 
     FROM monthly_connections mc WHERE (mc.calling_party = @CallingParty AND mc.called_party = @CalledParty) OR (mc.called_party = @CallingParty AND mc.calling_party = @CalledParty); 
     SET @RecordsUpdated = @RecordsUpdated + @@ROWCOUNT 
     PRINT @RecordsUpdated 
    END 
END 
PRINT @RecordsUpdated 

La procédure ci-dessus est censé passer par la table des connexions qui contient des connexions 23m et mettre à jour les valeurs common_neighbors et total_neighbors pour chaque ligne . Le problème est cependant que la procédure est trop lente - il a fallu 212 s pour mettre à jour 1000 enregistrements.

Je serais vraiment reconnaissant si quelqu'un d'entre vous a suggéré une solution pour la procédure ci-dessus afin d'accélérer le temps d'exécution.

Merci!

Répondre

0

Le script suivant produit la même sortie pour le common_neighbors que votre procédure stockée. Cependant, j'ai le sentiment que ce n'est pas (encore) exactement ce dont vous avez besoin, mais vous pourriez le prendre pour de nouvelles idées.

DECLARE @monthly_connections TABLE (
    calling_party VARCHAR(50) 
    , called_party VARCHAR(50) 
    , common_neighbors INTEGER 
    , total_neighbors INTEGER) 

INSERT INTO @monthly_connections 
      SELECT '1', '3', NULL, NULL 
UNION ALL SELECT '2', '4', NULL, NULL 
UNION ALL SELECT '3', '2', NULL, NULL 
UNION ALL SELECT '3', '4', NULL, NULL 
UNION ALL SELECT '3', '6', NULL, NULL 
UNION ALL SELECT '3', '7', NULL, NULL 
UNION ALL SELECT '4', '5', NULL, NULL 
UNION ALL SELECT '8', '4', NULL, NULL 

;WITH q AS (
    SELECT calling_party, called_party 
    FROM @monthly_connections mc1 
    UNION ALL 
    SELECT called_party, calling_party 
    FROM @monthly_connections mc1 
) 
UPDATE @monthly_connections 
SET  common_neighbors = common_neighbors.cnt 
FROM @monthly_connections mc 
     INNER JOIN (
      SELECT q1.calling_party, q1.called_party, cnt = COUNT(*) 
      FROM q q1 
        INNER JOIN q q2 ON q2.calling_party = q1.called_party       
        INNER JOIN q q3 ON q3.calling_party = q2.called_party 
            AND q3.called_party = q1.calling_party 
      GROUP BY 
        q1.calling_party, q1.called_party 
     ) common_neighbors ON common_neighbors.calling_party = mc.calling_party 
           AND common_neighbors.called_party = mc.called_party 


SELECT * 
FROM @monthly_connections   
0

Dans votre procédure, vous effectuez beaucoup de sous-requêtes, ce qui, je suppose, est la principale source de votre perte de performance. Ne pouvez-vous pas simplement remplacer la requête multiple par une grosse jointure, puis filtrer dessus? Quelque chose comme

SELECT T.calling_party, T.called_party, A.called_party, B.called_party 
from table T 
join table as A 
on T.calling_party = A.calling_party 
join table as B 
on T.calling_party = B.calling_party 
where A.called_party = B.called_party --to get the commong neighbour 

Vous aurez probablement besoin d'une autre se joindre au called_party pour obtenir la liste complète, mais je pense que cela pourrait être plus rapide que itérer dossiers 23m et d'appeler plusieurs requêtes pour tous.

Questions connexes