Precisa de uma contagem de linha após instrução SELECT: qual é a abordagem SQL ideal?

StackOverflow https://stackoverflow.com/questions/243782

  •  04-07-2019
  •  | 
  •  

Pergunta

Eu estou tentando selecionar uma coluna de uma única tabela (sem junções) e eu preciso a contagem do número de linhas, de preferência antes de eu começar a recuperar as linhas. Eu vim para duas abordagens que fornecem a necessidade informações.

Abordagem 1:

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

Em seguida

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

ou Abordagem 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'

Estou fazendo isso porque meu driver SQL (SQL Native Client 9.0) não permite que eu use SQLRowCount em uma instrução SELECT, mas eu preciso saber o número de linhas em meu resultado, a fim de alocar uma matriz antes de atribuir informações para isto. O uso de um recipiente alocada dinamicamente é, infelizmente, não é uma opção nesta área de meu programa.

Estou preocupado que o cenário a seguir pode ocorrer:

  • Selecione para contagem ocorre
  • ocorre Outra instrução, adicionando ou removendo uma linha
  • SELECT para dados ocorre de repente, a matriz é o tamanho errado.
    -No caso pior, este tentará escrever dados para além dos limites matrizes e bater meu programa.

A Abordagem 2 proibir esta questão?

Além disso, Will um dos dois se aproxima de ser mais rápido? Se sim, quais?

Finalmente, há uma abordagem melhor que eu deveria considerar (talvez uma maneira de instruir o driver para retornar o número de linhas em um resultado SELECT usando SQLRowCount?)

Para aqueles que perguntou, eu estou usando Native C ++ com o driver SQL acima mencionada (fornecido pela Microsoft.)

Foi útil?

Solução

Existem apenas duas maneiras de ser 100% certo que o COUNT(*) e a consulta real vai dar resultados consistentes:

  • Combinado a COUNT(*) com a consulta, como na sua abordagem 2. Eu recomendo a forma que você mostra no seu exemplo, não a forma subconsulta correlacionada mostrado no comentário de kogus.
  • Use duas consultas, como na sua abordagem 1, depois de iniciar uma transação em SNAPSHOT ou SERIALIZABLE nível de isolamento.

Usando um desses níveis de isolamento é importante porque qualquer outro nível de isolamento permite novas linhas criadas por outros clientes para se tornar visível na sua transação atual. Leia a documentação do MSDN sobre SET TRANSACTION ISOLATION para mais detalhes.

Outras dicas

Se você estiver usando SQL Server, após a sua consulta você pode selecionar o @@ RowCount função (ou se o seu conjunto de resultados pode ter mais de 2 bilhões de linhas usar o RowCount_Big () função ). Isto irá retornar o número de linhas selecionadas pela declaração anterior ou o número de linhas afetadas por uma atualização instrução de inserção / / delete.

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

SELECT @@Rowcount

Ou se você quiser remar contar incluídos no resultado enviado semelhante à Abordagem # 2, você pode usar o sOBRE cláusula .

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

Usando a cláusula OVER terá um desempenho muito melhor do que usar uma subconsulta para obter a contagem de linhas. Usando o @@ RowCount vai ter o melhor desempenho porque o não haverá qualquer custo de consulta para a instrução select RowCount @@

Atualização em resposta ao comentário: O exemplo que dei daria o # de linhas na partição - definido neste caso a "PARTITION BY my_table.foo". O valor da coluna em cada linha é o # de linhas com o mesmo valor de my_table.foo. Desde o seu exemplo de consulta teve a cláusula "ONDE my_table.foo = 'bar'", todas as linhas no conjunto de resultados terá o mesmo valor do my_table.foo e, portanto, o valor na coluna será o mesmo para todas as linhas e igual (em neste caso) esta o # de linhas na consulta.

Aqui está uma melhor simples exemplo / de como incluir uma coluna em cada linha que é o total # de linhas no conjunto de resultados. Basta remover a partição opcional por cláusula.

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

Abordagem 2 sempre retornará uma contagem que corresponde ao seu conjunto de resultados.

Eu sugiro que você ligar o sub-consulta para a sua consulta externa, porém, a garantia de que a condição em sua contagem corresponde à condição no conjunto de dados.

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';

Se você está em causa o número de linhas que atendam a condição pode mudar em poucos milissegundos desde execução da consulta e recuperação de resultados, você poderia / deveria executar as consultas dentro de uma transação:

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

Este seria devolver os valores corretos, sempre.

Além disso, se você estiver usando SQL Server, você pode usar @@ ROWCOUNT para obter o número de linhas afetadas pela última afirmação, e redirecionar a saída do real consulta a uma tabela temporária ou mesa variável, para que possa devolver tudo por completo, e não há necessidade de uma transação:

DECLARE @dummy INT

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

SET @dummy=@@ROWCOUNT
SELECT @dummy, * FROM #temp_table

Aqui estão algumas idéias:

  • Vá com Método # 1 e redimensionar a matriz para armazenar resultados adicionais ou usar um tipo que automaticamente redimensiona como neccessary (você não mencionar o idioma que você está usando, então não posso ser mais específico).
  • Você pode executar ambas as declarações na abordagem # 1 dentro de uma transação para garantir as contagens são os mesmos nas duas vezes se o seu banco de dados suporta isso.
  • Eu não tenho certeza do que você está fazendo com os dados, mas se é possível processar os resultados sem armazenar todos eles pela primeira vez este pode ser o melhor método.

Se você está realmente preocupado que a sua contagem de linhas vai mudar entre a contagem select e a instrução select, por que não escolher as linhas em uma tabela temporária em primeiro lugar? Dessa forma, você sabe que vai estar em sincronia.

Por que você não colocar seus resultados em um vetor? Dessa forma, você não tem que saber o tamanho antes da mão.

Você pode querer pensar em um melhor padrão para lidar com dados desse tipo.

Nenhum driver SQL auto-prespecting irá dizer-lhe quantas linhas sua consulta irá retornar antes de retornar as linhas, porque a resposta pode mudar (a menos que você use uma transação, que cria seus próprios problemas.)

O número de linhas não vai mudar -. Google para ACID e SQL

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

Apenas para adicionar este porque este é o primeiro resultado no Google para esta pergunta. Em sqlite eu usei isso para obter o número de linhas.

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top