2009-03-15 35 views
39

No me interesan los contenidos de una fila, solo quiero saber si existe una fila. La columna Name es una clave principal, por lo que habrá 0 o 1 filas coincidentes. Actualmente, estoy usando:¿Cuál es la manera más rápida de determinar si existe una fila usando Linq a SQL?

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

Si bien lo anterior funciona, lo hace una gran cantidad de trabajo innecesario mediante la selección de todos los contenidos de la fila (si existe). ¿Lo siguiente crea una consulta más rápida:

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

... ¿o hay una consulta aún más rápida?

Respuesta

73

El enfoque Count() puede hacer un trabajo extra, ya que (en TSQL) EXISTS o TOP 1 son a menudo mucho más rápidos; el DB puede optimizar "¿hay al menos una fila?" En lo personal, me gustaría utilizar la sobrecarga de cualquier/predicado:

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

Por supuesto, se puede comparar lo que cada uno hace observando el TSQL:

dc.Log = Console.Out; 
+2

Combinatum extra solo porque nunca me di cuenta de que había una característica de registro de SQL para Sql. He tenido que ejecutar SQL Profiler todo este tiempo. – David

+7

@David - de hecho. Sigo pidiéndole al equipo de datos de MS que lo agregue a EF ;-p –

+0

¿y si quiere ** utilizar a los Usuarios después ** en lugar de escribir una segunda consulta en la base de datos? compruebe [esto] (http://stackoverflow.com/a/1071063/2218697), la esperanza ayuda a alguien. – stom

2

pienso:

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

es el mejor enfoque.

0

No estoy de acuerdo con que la selección de la parte superior 1 siempre supere el recuento de selecciones para todas las implementaciones de SQL. Todo depende de la implementación, ya sabes. Curiosamente, incluso la naturaleza de los datos almacenados en una base de datos particular también afecta el resultado general.

Examinemos ambos de la manera en que los implementaría si lo hiciera: en ambos casos, la evaluación de proyección (cláusula WHERE) es un paso común.

Luego, para seleccionar la parte superior 1, deberá leer todos los campos (a menos que haya seleccionado la parte superior 1 'x' por ejemplo: seleccione la parte superior 1 1). Esto será funcionalmente equivalente a IQueryable.Any (...)., Excepto que pasará un tiempo parpadeando en el valor de cada columna del primer registro encontrado si EXISTE. Si SELECCIONAR ARRIBA se encuentra en la declaración, la proyección es truncamiento si no hay un proceso de post proyección (p. Ej. Cláusula ORDER BY). Este preproceso implica un pequeño costo, pero este es un costo adicional si no existe ningún registro, en cuyo caso, todavía se realiza un proyecto completo.

Para la cuenta de selección, el preproceso no está hecho. Se realiza una proyección y si EXISTS es falso, el resultado es instantáneo. Si EXISTS es verdadero, el conteo aún es rápido porque será un mero dW_Highest_Inclusive - dW_Lowest_Exclusive. Tan rápido como 500 - 26. Si existe es falso, el resultado es aún más instantáneo.

Por lo tanto, el caso restante es: ¿Qué tan rápido es la proyección y qué pierde al hacer la proyección completa? Y la respuesta lleva al problema más crucial aquí, que es: ¡está el campo [NAME] indexado o no! Si tiene un índice en [NAME], el rendimiento de cualquiera de las consultas será tan cercano que se reducirá a las preferencias del desarrollador.

En general, simplemente voy a escribir de dos a cuatro consultas de linq y la diferencia de salida en el tiempo antes y después.

  1. select count
  2. seleccione la parte superior 1
  3. seleccione la parte superior 1 1
  4. seleccione cualquiera

repetir todo 4 con un índice no agrupado en [NOMBRE];

8

Por supuesto

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

este es el mejor y si hay varias condiciones para comprobar entonces es muy sencillo escribir como

decir que quiere comprobar el usuario para la empresa, entonces

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

Para aquellas personas que afirman que Any() es el camino a seguir, hice una prueba simple en LinqPad contra una base de datos SQL de CommonPasswords, 14 millones de dar o recibir. Código:

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(); 
} 

Aquí está el SQL traducida:

-- 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] 

Se puede ver que cualquier envuelve la consulta en otra capa de código para hacer un caso en el que existe, entonces 1 donde como Count() solo agrega un comando de Cuenta. El problema con estos dos es que no puede hacer una parte superior (1), pero no puede ver una mejor manera el uso de Arriba (1)

Resultados:

De DB: encontrado: el tiempo de procesamiento: 13.3962

De DB: Encontrado: tiempo de procesamiento: 12.0933

De DB: Encontrado: tiempo de procesamiento: 787,8801

Una vez más:

De DB: Encontrado: Tiempo de procesamiento: 13.3878

De DB: Encontrado: Tiempo de procesamiento: 12.6881

De DB: Encontrado: Tiempo de procesamiento: 780,2686

Una vez más:

De base de datos: ENCONTRADO: tiempo de procesamiento: 24.7081

De base: encontrado: el tiempo de procesamiento: 23.6654

De DB: encontrado: el tiempo de procesamiento: 699.622

sin índice:

De DB: encontrado: Tiempo de ejecución: 2.395.1988

De DB: encontrado: el tiempo de procesamiento: 390,6334

De DB: encontrado: el tiempo de procesamiento: 664,8581

Ahora algunos de ustedes pueden estar pensando que es sólo una milésima de segundo o dos. Sin embargo, la variedad era mucho mayor antes de poner un índice sobre ella; por unos segundos.

El último cálculo está allí ya que comencé con la noción de que ToLower() sería más rápido que LIKE, y tenía razón, hasta que intenté contar y poner un índice en él. Supongo que Lower() hace que el índice sea relajante.

Cuestiones relacionadas