Hai bisogno di un conteggio delle righe dopo l'istruzione SELECT: qual è l'approccio SQL ottimale?

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

  •  04-07-2019
  •  | 
  •  

Domanda

Sto provando a selezionare una colonna da una singola tabella (nessun join) e ho bisogno del conteggio del numero di righe, idealmente prima di iniziare a recuperare le righe. Sono giunto a due approcci che forniscono le informazioni di cui ho bisogno.

Approccio 1:

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

Poi

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

O Approccio 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'

Lo sto facendo perché il mio driver SQL (SQL Native Client 9.0) non mi consente di utilizzare SQLRowCount su un'istruzione SELECT ma devo conoscere il numero di righe nel mio risultato per allocare un array prima di assegnare informazioni a esso. Sfortunatamente, l'uso di un contenitore allocato dinamicamente non è un'opzione in quest'area del mio programma.

Temo che potrebbe verificarsi il seguente scenario:

  • SELEZIONA per il conteggio si verifica
  • Si verifica un'altra istruzione, aggiungendo o rimuovendo una riga
  • SELEZIONA per i dati si verifica e improvvisamente l'array ha dimensioni errate.
      -Nel caso peggiore, questo tenterà di scrivere dati oltre i limiti degli array e di interrompere il mio programma.

L'approccio 2 proibisce questo problema?

Inoltre, uno dei due approcci sarà più veloce? In tal caso, quale?

Infine, c'è un approccio migliore che dovrei prendere in considerazione (forse un modo per istruire il driver a restituire il numero di righe in un risultato SELECT usando SQLRowCount?)

Per quelli che hanno chiesto, sto usando Native C ++ con il suddetto driver SQL (fornito da Microsoft.)

È stato utile?

Soluzione

Esistono solo due modi per essere sicuri al 100% che COUNT (*) e la query effettiva forniranno risultati coerenti:

  • Combina COUNT (*) con la query, come nel tuo Approccio 2. Raccomando il modulo che mostri nel tuo esempio, non il modulo di subquery correlato mostrato nel commento da kogus.
  • Usa due query, come nel tuo Approccio 1, dopo aver avviato una transazione nel livello di isolamento SNAPSHOT o SERIALISABLE .

L'uso di uno di questi livelli di isolamento è importante perché qualsiasi altro livello di isolamento consente alle nuove righe create da altri client di diventare visibili nella transazione corrente. Leggi la documentazione MSDN su SET ISOLATION TRANSACTION per maggiori dettagli.

Altri suggerimenti

Se si utilizza SQL Server, dopo la query è possibile selezionare @@ RowCount (o se il tuo set di risultati potrebbe avere più di 2 miliardi di righe usa la funzione RowCount_Big () ). Ciò restituirà il numero di righe selezionate dall'istruzione precedente o il numero di righe interessate da un'istruzione insert / update / delete.

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

SELECT @@Rowcount

Oppure, se si desidera conteggiare le righe incluse nel risultato inviato simile all'approccio n. 2, è possibile utilizzare clausola OVER .

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

L'uso della clausola OVER avrà prestazioni molto migliori rispetto all'utilizzo di una subquery per ottenere il conteggio delle righe. L'uso di @@ RowCount avrà le migliori prestazioni perché non ci saranno costi di query per l'istruzione select @@ RowCount

Aggiornamento in risposta al commento: l'esempio che ho dato darebbe il numero di righe nella partizione - definito in questo caso da " PARTITION BY my_table.foo " ;. Il valore della colonna in ogni riga è il numero di righe con lo stesso valore di my_table.foo. Poiché la tua query di esempio aveva la clausola " WHERE my_table.foo = 'bar' " ;, tutte le righe nel set di risultati avranno lo stesso valore di my_table.foo e quindi il valore nella colonna sarà lo stesso per tutte le righe e uguale (in questo caso) questo è il numero di righe nella query.

Ecco un esempio migliore / più semplice di come includere una colonna in ogni riga che è il numero totale di righe nel gruppo di risultati. Rimuovi semplicemente la clausola opzionale Partition By.

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

L'approccio 2 restituirà sempre un conteggio corrispondente al set di risultati.

Ti suggerisco tuttavia di collegare la query secondaria alla query esterna, per garantire che la condizione sul tuo conteggio corrisponda alla condizione sul set di dati.

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 sei preoccupato che il numero di righe che soddisfano la condizione possa cambiare in pochi millisecondi dall'esecuzione della query e il recupero dei risultati, potresti / dovresti eseguire le query all'interno di una transazione:

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

Questo restituirebbe sempre i valori corretti.

Inoltre, se si utilizza SQL Server, è possibile utilizzare @@ ROWCOUNT per ottenere il numero di righe interessate dall'ultima istruzione e reindirizzare l'output della query reale a una tabella o tabella temporanea variabile, in modo da poter restituire tutto del tutto e non è necessaria alcuna transazione:

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

Ecco alcune idee:

  • Vai con l'approccio n. 1 e ridimensiona l'array per contenere risultati aggiuntivi o usa un tipo che si ridimensiona automaticamente come necessario (non menzioni la lingua che stai usando, quindi non posso essere più specifico).
  • È possibile eseguire entrambe le istruzioni nell'approccio n. 1 all'interno di una transazione per garantire che i conteggi siano gli stessi entrambe le volte se il database lo supporta.
  • Non sono sicuro di cosa stai facendo con i dati, ma se è possibile elaborare i risultati senza prima memorizzarli tutti, questo potrebbe essere il metodo migliore.

Se sei davvero preoccupato che il conteggio delle righe cambi tra il conteggio selezionato e l'istruzione select, perché non selezionare prima le righe in una tabella temporanea? In questo modo, sai che sarai sincronizzato.

Perché non metti i tuoi risultati in un vettore? In questo modo non devi conoscere prima le dimensioni.

Potresti voler pensare a un modello migliore per gestire i dati di questo tipo.

Nessun driver SQL con auto-presunzione ti dirà quante righe restituirà la tua query prima di restituire le righe, perché la risposta potrebbe cambiare (a meno che tu non usi una Transazione, che crea problemi a sé stanti).

Il numero di righe non cambierà - google per ACID e SQL.

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

Solo per aggiungere questo perché questo è il miglior risultato in Google per questa domanda. In sqlite l'ho usato per ottenere il conteggio delle righe.

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top