web-dev-qa-db-fra.com

Quel est le moyen le plus rapide pour déterminer si une ligne existe à l'aide de Linq to SQL?

Je ne suis pas intéressé par le contenu d'une ligne, je veux juste savoir si une ligne existe. La colonne Name est une clé primaire, il y aura donc 0 ou 1 lignes correspondantes. Actuellement, j'utilise:

if ((from u in dc.Users where u.Name == name select u).Count() > 0)
    // row exists
else
    // row doesn't exist

Bien que ce qui précède fonctionne, il fait beaucoup de travail inutile en sélectionnant tout le contenu de la ligne (s'il existe). Les éléments suivants créent-ils une requête plus rapide:

if (dc.Users.Where(u => u.Name == name).Any())

... ou y a-t-il une requête encore plus rapide?

45
Protagonist

L'approche Count() peut faire un travail supplémentaire, comme (dans TSQL) EXISTS ou TOP 1 sont souvent beaucoup plus rapides; la base de données peut optimiser "y a-t-il au moins une ligne". Personnellement, j'utiliserais la surcharge any/predicate:

if (dc.Users.Any(u => u.Name == name)) {...}

Bien sûr, vous pouvez comparer ce que chacun fait en regardant le TSQL:

dc.Log = Console.Out;
90
Marc Gravell

Bien sûr

if (dc.Users.Where(u => u.Name == name).Any())

c'est mieux et si plusieurs conditions à vérifier, il est très simple d'écrire comme

Dites que vous voulez vérifier l'utilisateur pour l'entreprise, puis

if (dc.Users.Where(u => u.ID== Id && u.Company==company).Any())
11
Raju

Je pense:

if (dc.Users.Any(u => u.Name == name)) {...}

est la meilleure approche.

4
MRFerocius

Pour ceux qui prétendent qu'Any () est la voie à suivre, j'ai fait un test simple dans LinqPad contre une base de données SQL de CommonPasswords, 14 millions donnent ou prennent. Code:

var password = "qwertyuiop123";

var startTime = DateTime.Now;
"From DB:".Dump();
startTime = DateTime.Now;

if (CommonPasswords.Any(c => System.Data.Linq.SqlClient.SqlMethods.Like(c.Word, password)))
{
    $"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
    $"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}

"From DB:".Dump();
startTime = DateTime.Now;
if (CommonPasswords.Where(c => System.Data.Linq.SqlClient.SqlMethods.Like(c.Word, password)).Count() > 0)
{
    $"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
    $"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}

"From DB:".Dump();
startTime = DateTime.Now;
if (CommonPasswords.Where(c => c.Word.ToLower() == password).Take(1).Any())
{
    $"FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}
else
{
    $"NOT FOUND: processing time: {(DateTime.Now - startTime).TotalMilliseconds}\r\n".Dump();
}

Voici le SQL traduit:

-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT 
    (CASE 
        WHEN EXISTS(
            SELECT NULL AS [EMPTY]
            FROM [Security].[CommonPasswords] AS [t0]
            WHERE [t0].[Word] LIKE @p0
            ) THEN 1
        ELSE 0
     END) AS [value]
GO

-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT COUNT(*) AS [value]
FROM [Security].[CommonPasswords] AS [t0]
WHERE [t0].[Word] LIKE @p0
GO

-- Region Parameters
DECLARE @p0 NVarChar(1000) = 'qwertyuiop123'
-- EndRegion
SELECT 
    (CASE 
        WHEN EXISTS(
            SELECT NULL AS [EMPTY]
            FROM (
                SELECT TOP (1) NULL AS [EMPTY]
                FROM [Security].[CommonPasswords] AS [t0]
                WHERE LOWER([t0].[Word]) = @p0
                ) AS [t1]
            ) THEN 1
        ELSE 0
     END) AS [value]

Vous pouvez voir que TOUT encapsule la requête dans une autre couche de code pour faire un CASE Where Exists Then 1 where as Count () ajoute simplement une commande Count. Le problème avec ces deux est que vous ne pouvez pas faire un Top (1) mais je ne vois pas de meilleure façon d'utiliser Top (1)

Résultats:

De DB: TROUVÉ: temps de traitement: 13.3962

De la DB: TROUVE: temps de traitement: 12.0933

De DB: TROUVÉ: temps de traitement: 787.8801

Encore:

De DB: TROUVÉ: temps de traitement: 13.3878

De DB: TROUVÉ: temps de traitement: 12.6881

De DB: TROUVÉ: temps de traitement: 780.2686

Encore:

De DB: TROUVÉ: temps de traitement: 24.7081

De DB: TROUVÉ: temps de traitement: 23.6654

De DB: TROUVÉ: temps de traitement: 699.622

Sans index:

De DB: TROUVÉ: temps de traitement: 2395.1988

De DB: TROUVÉ: temps de traitement: 390.6334

De DB: TROUVÉ: temps de traitement: 664.8581

Maintenant, certains d'entre vous pensent peut-être que ce n'est qu'une milliseconde ou deux. Cependant, la variance était beaucoup plus grande avant de mettre un index dessus; de quelques secondes.

Le dernier calcul est là quand j'ai commencé avec l'idée que ToLower () serait plus rapide que LIKE, et j'avais raison, jusqu'à ce que j'essaie de compter et de mettre un index dessus. Je suppose que le Lower () rend l'indice non pertinent.

1
Tod

Je ne suis pas d'accord que la sélection du top 1 surpassera toujours le nombre de sélections pour toutes les implémentations SQL. Tout dépend de l'implémentation, vous savez. Curieusement, même la nature des données stockées dans une base de données particulière affecte également le résultat global.

Examinons les deux de la façon dont je les implémenterais si je le faisais: pour les deux cas, l'évaluation de la projection (clause WHERE) est une étape courante.

Ensuite, pour sélectionner le top 1, vous devrez lire tous les champs (sauf si vous avez sélectionné le top 1 'x', par exemple: sélectionner le top 1 1). Cela sera fonctionnellement équivalent à IQueryable.Any (...)., Sauf que vous passerez un certain temps à clignoter dans la valeur de chaque colonne du premier enregistrement rencontré si EXISTS. Si SELECT TOP est trouvé dans l'instruction, la projection est tronquée s'il n'y a pas de processus de post-projection (par exemple, clause ORDER BY). Ce prétraitement entraîne un faible coût mais il s'agit d'un coût supplémentaire s'il n'existe aucun enregistrement, auquel cas, un projet complet est toujours en cours.

Pour le décompte sélectionné, le prétraitement n'est pas effectué. Une projection est effectuée et si EXISTS est faux, le résultat est instantané. Si EXISTS est vrai, le décompte est toujours rapide car ce sera simplement dW_Highest_Inclusive - dW_Lowest_Exclusive. Aussi vite que 500 - 26. S'il existe est faux, le résultat est encore plus instantané.

Le cas restant est donc: Quelle est la vitesse de la projection et que perdez-vous en faisant une projection complète? Et la réponse mène au problème le plus crucial ici: le champ [NOM] est-il indexé ou non? Si vous avez un index sur [NOM], les performances de l'une ou l'autre requête seront si proches qu'elles se résumeront aux préférences du développeur.

Dans l'ensemble, j'écrirai simplement deux à quatre requêtes linq et la différence de sortie dans le temps avant et après.

  1. sélectionner le nombre
  2. sélectionnez top 1
  3. sélectionnez top 1 1
  4. sélectionner n'importe quel

Répétez les 4 avec un index non cluster sur [NAME];

0
Pita.O