2008-10-28 10 views
30

Estoy tratando de seleccionar una columna de una sola tabla (sin uniones) y necesito contar el número de filas, idealmente antes de comenzar a recuperar las filas. He llegado a dos enfoques que proporcionan la información que necesito.Necesita un recuento de filas después de la instrucción SELECT: ¿cuál es el enfoque SQL óptimo?

Enfoque 1:

SELECT COUNT(my_table.my_col) AS row_count 
    FROM my_table 
WHERE my_table.foo = 'bar' 

Entonces

SELECT my_table.my_col 
    FROM my_table 
WHERE my_table.foo = 'bar' 

O Enfoque 2

SELECT my_table.my_col, (SELECT COUNT (my_table.my_col) 
          FROM my_table 
          WHERE my_table.foo = 'bar') AS row_count 
    FROM my_table 
WHERE my_table.foo = 'bar' 

que estoy haciendo esto porque mi controlador de SQL (SQL Native Client 9.0) no me permite use SQLRowCount en una instrucción SELECT, pero necesito saber el número de filas en mi resultado para asignar una matriz antes de asignarle información. El uso de un contenedor dinámicamente asignado, desafortunadamente, no es una opción en esta área de mi programa.

Me preocupa que pueda ocurrir el siguiente escenario:

  • SELECT para el recuento se produce
  • ocurre Otra instrucción, adición o eliminación de una fila
  • SELECT produce datos y de repente la matriz es el mal tamaño.
    -En el peor de los casos, esto intentará escribir datos más allá de los límites de las matrices y bloquear mi programa.

¿El enfoque 2 prohíbe este problema?

Además, ¿Será uno de los dos enfoques más rápidos? Si es así, ¿cuál?

Por último, ¿hay un mejor enfoque que debería tener en cuenta (tal vez una manera de indicar al conductor para devolver el número de filas en un resultado de SELECT usando SQLRowCount?)

Para aquellos que pidió, estoy usando nativo C++ con el controlador SQL antes mencionado (provisto por Microsoft.)

+0

¿Qué estás haciendo con estos datos que necesitas tanto los datos sin procesar como el recuento de filas? Si necesita todos los datos brutos (que es lo que está seleccionando), ¿no puede contarlos mientras los lee? Si no necesita todos los datos brutos, no los seleccione. ¿El recuento es solo para fines de paginación? –

Respuesta

15

sólo hay dos maneras de estar 100% seguro de que el COUNT(*) y la consulta real dará resultados consistentes:

  • combinado del COUNT(*) con la consulta, como en su Método 2. Recomiendo el formulario que muestra en su ejemplo, no la forma de subconsulta correlacionada que se muestra en el comentario de kogus.
  • Utilice dos consultas, como en su Método 1, después de iniciar una transacción en el nivel de aislamiento SNAPSHOT o SERIALIZABLE.

Usar uno de esos niveles de aislamiento es importante porque cualquier otro nivel de aislamiento permite que nuevas filas creadas por otros clientes se vuelvan visibles en su transacción actual. Lea la documentación de MSDN en SET TRANSACTION ISOLATION para obtener más detalles.

+0

Sin preguntar, esto abordó otra curiosidad que tenía en su primera viñeta: obviamente, preferiría no tener la consulta de recuento ejecutada repetidamente si se puede optimizar. – antik

+0

Derecha; No soy un experto en el optimizador de MS SQL Server, pero me sorprendería si pudiera optimizar ese tipo de subconsulta correlacionada. –

-1

¿Por qué no pones tus resultados en un vector? De esta forma no tienes que saber el tamaño de antemano.

+0

Debería haber mencionado que se me ocurrió la solución, pero no me gusta la idea de copiar mi información de la base de datos, a un vector, obtener el recuento de filas y luego copiar todo en el vector en una matriz. No puedo cambiar el uso de una matriz simple en este caso. – antik

+0

El conjunto de resultados de una consulta de base de datos puede ser enorme, incluso puede no encajar en la memoria, por lo que no es aconsejable forzar un conjunto de resultados en la memoria antes de saber si encajará. –

+0

Si el conjunto de resultados es tan grande, probablemente debería buscarlo de todos modos. – jonnii

1

Aquí están algunas ideas:

  • Ir con Enfoque # 1 y cambiar el tamaño de la matriz para contener resultados adicionales o utilizar un tipo que cambia de tamaño automáticamente a medida que lo necesite (usted no menciona qué idioma se está utilizando por lo que no puede ser más específico).
  • Puede ejecutar ambas sentencias en el Método n. ° 1 dentro de una transacción para garantizar que los recuentos sean los mismos dos veces si su base de datos lo admite.
  • No estoy seguro de lo que está haciendo con los datos, pero si es posible procesar los resultados sin almacenarlos primero, este podría ser el mejor método.
0

Es posible que desee pensar en un mejor patrón para tratar con datos de este tipo.

Sin controlador de SQL auto-prespecting le dirá cuántas filas su consulta devolverá antes de devolver las filas, ya que la respuesta podría cambiar (a menos que utilice una transacción, lo que crea sus propios problemas.)

El el número de filas no cambiará: google para ACID y SQL.

+0

Buena información sobre el ACID, no exactamente en el comentario de "respeto propio". Muchos controladores SQL ejecutan el lado del servidor de consultas pero no devuelven todo el conjunto de resultados en la misma llamada de rutina (es decir, primero llama a SQLExecute y luego a SQLFetch para obtener los resultados). Esto a menudo está oculto para el usuario final (por ejemplo, .NET Dataset) –

+0

. Creo que el principio de aislamiento dentro del concepto ACID aborda mis preocupaciones sobre el enfoque # 2 de manera suficiente. Si puedo contar con que los resultados no se vean afectados por las consultas en la base de datos por otros usuarios, estoy dispuesto a utilizar ese enfoque. Gracias. – antik

1

Si realmente está preocupado de que su conteo de filas cambiará entre el recuento de selección y la instrucción de selección, ¿por qué no seleccionar primero las filas en una tabla temporal? De esa forma, sabes que estarás sincronizado.

3

El Método 2 siempre devolverá un recuento que coincida con su conjunto de resultados.

Sugiero que vincule la subconsulta con su consulta externa, para garantizar que la condición en su recuento coincida con la condición en el conjunto de datos.

SELECT 
    mt.my_row, 
(SELECT COUNT(mt2.my_row) FROM my_table mt2 WHERE mt2.foo = mt.foo) as cnt 
FROM my_table mt 
WHERE mt.foo = 'bar'; 
+0

Eso podría convertirlo en una subconsulta correlacionada, lo que significa que probablemente ejecutará la subconsulta para cada fila del conjunto de resultados. Una subconsulta no correlacionada se puede optimizar, por lo que solo se debe ejecutar una vez. –

+0

Muy interesante; No lo sabía. En ese caso, sugeriría usar un parámetro compartido por la consulta principal y la subconsulta. – JosephStyons

3

Si le preocupa el número de filas que cumplen la condición puede cambiar en los pocos milisegundos desde la ejecución de la consulta y recuperación de los resultados, se puede/debe ejecutar las consultas dentro de una transacción:

BEGIN TRAN bogus 

SELECT COUNT(my_table.my_col) AS row_count 
FROM my_table 
WHERE my_table.foo = 'bar' 

SELECT my_table.my_col 
FROM my_table 
WHERE my_table.foo = 'bar' 
ROLLBACK TRAN bogus 

Esto devolvería los valores correctos, siempre.

Por otra parte, si usted está utilizando SQL Server, puede utilizar @@ ROWCOUNT para obtener el número de filas afectadas por la última declaración, y redirigir la salida de verdadera consulta a una variable de tabla temporal o una mesa, por lo que puede volver todo a la vez, y no hay necesidad de una transacción:

DECLARE @dummy INT 

SELECT my_table.my_col 
INTO #temp_table 
FROM my_table 
WHERE my_table.foo = 'bar' 

SET @[email protected]@ROWCOUNT 
SELECT @dummy, * FROM #temp_table 
+0

El recuento podría cambiar si está utilizando 'READ COMMITTED', ¿verdad? ¿O el SQL Server sobre el modo ODBC hace las transacciones de manera diferente a T-SQL de alguna manera? – binki

25

Si está utilizando SQL Server, después de su consulta puede seleccionar la función @@ RowCount (o si su conjunto de resultados puede tener más de 2 mil millones de filas use la función BIGROW_COUNT()). Esto devolverá el número de filas seleccionadas por la declaración anterior o el número de filas afectadas por una instrucción de inserción/actualización/eliminación.

SELECT my_table.my_col 
    FROM my_table 
WHERE my_table.foo = 'bar' 

SELECT @@Rowcount 

O si lo desea el número de filas en el resultado de envió similar a Enfoque # 2, puede utilizar la cláusula OVER (ver http://msdn.microsoft.com/en-us/library/ms189461.aspx1).

SELECT my_table.my_col, 
    count(*) OVER(PARTITION BY my_table.foo) AS 'Count' 
    FROM my_table 
WHERE my_table.foo = 'bar' 

El uso de la cláusula OVER tendrá un rendimiento mucho mejor que el uso de una subconsulta para obtener el recuento de filas.Usar @@ RowCount tendrá el mejor rendimiento porque no habrá ningún costo de consulta para la instrucción select @@ RowCount

Actualización en respuesta al comentario: El ejemplo que di daría el número de filas en la partición - definido en este caso por "PARTITION BY my_table.foo". El valor de la columna en cada fila es el número de filas con el mismo valor de my_table.foo. Como su consulta de ejemplo tenía la cláusula "WHERE my_table.foo = 'bar'", todas las filas del conjunto de resultados tendrán el mismo valor de my_table.foo y, por lo tanto, el valor en la columna será el mismo para todas las filas e igual (en este caso) este el # de filas en la consulta.

Aquí hay un ejemplo mejor y más simple de cómo incluir una columna en cada fila que es el número total de filas en el conjunto de resultados. Simplemente elimine la cláusula Partition By opcional.

SELECT my_table.my_col, count(*) OVER() AS 'Count' 
    FROM my_table 
WHERE my_table.foo = 'bar' 
+0

Preferiría tener el resultado en mi conjunto de resultados. Sin embargo, no parece que el uso de OVER como usted ha descrito funcione cuando intento ejecutar su consulta en mi tabla en SQL. – antik

+8

count (*) OVER() COMO 'Count' funcionó para mí. –

0
IF (@@ROWCOUNT > 0) 
BEGIN 
SELECT my_table.my_col 
    FROM my_table 
WHERE my_table.foo = 'bar' 
END 
0

sólo para añadir esto porque este es el primer resultado de Google para esta pregunta. En sqlite lo usé para obtener el recuento de filas.

WITH temptable AS 
    (SELECT one,two 
    FROM 
    (SELECT one, two 
     FROM table3 
     WHERE dimension=0 
     UNION ALL SELECT one, two 
     FROM table2 
     WHERE dimension=0 
     UNION ALL SELECT one, two 
     FROM table1 
     WHERE dimension=0) 
    ORDER BY date DESC) 
SELECT * 
FROM temptable 
LEFT JOIN 
    (SELECT count(*)/7 AS cnt, 
         0 AS bonus 
    FROM temptable) counter 
WHERE 0 = counter.bonus 
Cuestiones relacionadas