COUNT är det vanligaste sättet att räkna antalet rader i en tabell vid en viss tidpunkt. Men COUNT kan skapa problem med tabellskanningar och låsningar mm och alla andra som då vill använda tabellen får stå i kö för att få access till den vilket kan skapa problem framför allt om frågan körs frekvent.
Ett vanligt sätt att lösa detta är att använda systemtabellen sys.partitions i stället. I sys.partitions har alla tabeller och nästan alla index (inte t.ex full text och xml) en partition med även om man inte har partitionerat tabellen själv. Kolumnen index_id anger vilket index id partitionen tillhör. Id 0 står för en heap och id 1 för det klustrade indexet. Mer detaljer om vilket id respektive index har finns bland annat i tabellen sys.indexes för de som är intresserade. Antalet rader för partitionen finns i kolumnen rows, tänk på att summera över alla eventuella partitioner för indexet för att få fram rätt värde. Observera att antalet rader i denna tabellen, enligt Microsofts dokumentation, är ungefärligt så om det faktiska antalet rader behövs funkar inte denna metoden. Tänk dock på att COUNT är en ögonblicksbild och om antalet rader förändras hela tiden kan man i många fall använda detta värde.
Tabellen dbo.bigTable innehåller 60000000 (60 miljoner) rader och om vi testkör COUNT(*) mot den med STATISTICS IO påslaget får vi följande:
Table ’bigTable’. Scan count 9, logical reads 274538, physical reads 0, read-ahead reads 32, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Använder vi i stället sys.partitions för att få ut rad antalet enl nedan.
SELECT OBJECT_SCHEMA_NAME(object_id) AS [Schema namn], OBJECT_NAME(object_id) AS [Tabell namn], SUM(p.rows) AS [Rader]
FROM sys.partitions p
WHERE index_id < 2
AND OBJECTPROPERTY(object_id, 'IsUserTable') = 1
AND object_id = OBJECT_ID('[dbo].[bigTable]')
GROUP BY object_id
Får vi följande:
Table 'sysrowsets'. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Detta sätt genererar väldigt mycket mindre läsningar och inte i dbo.bigTable utan i sysrowsets så det finns inte någon risk för låsningar eller annan påverkan för andra användare.
COUNT har fördelen att man kan använda WHERE sats för att specificera vilka värden som ska räknas vilket inte går i andra lösningen. Om det är ett fåtal värden på argumenten är en möjlig lösning att skapa filtrerade ickeklustrade index på tabellen. Om man anger index_id för det nya indexet i frågan ovan i stället för alla heaps och klustrade index (index_id < 2) summeras alla rader för det indexet i stället.
En vanlig användning av COUNT() med en WHERE-sats är att kontrollera om det finns några värden i en tabell och i så fall göra något t ex om vi kontrollera om det i finns ett specifikt värde i en av kolumnerna och i så fall exekvera en lagrad procedur.
IF (SELECT COUNT(*) FROM [dbo].[bigTable] WHERE kolumn2 = 'Ett värde') > 1
BEGIN
EXEC sp_minProcedur
END
Detta kommer att generera samma antal läsningar (274538) som I vårt första test men eftersom vi bara vill kontrollera om det finns någon rad med det värdet kan vi använda oss av exists i stället:
IF EXISTS(SELECT 1 FROM [dbo].[bigTable] WHERE kolumn2 = 'Ett värde') > 1
Table ’bigTable’. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Men detta sätt fungerar bara om vi vill kontrollera att det finns några rader med värdet. Om vi i stället försöker kontrollera om det finns mer än 1 rad fungerar inte exists längre utan vi är tillbaka till COUNT och 274538 läsningar:
IF (SELECT COUNT(*) FROM [dbo].[bigTable] WHERE kolumn2 = 'Ett värde') > 1
Men eftersom vi bara vill veta om det finns fler än en rad med värdet kan vi med lite TSQL magi få ner antalet läsningar markant genom att använda TOP 2 i en inre SQL sats:
IF (SELECT COUNT(*) FROM (SELECT TOP 2 * FROM [dbo].[bigTable] WHERE kolumn2 = 'Ett värde') AS b) > 1
Table ’bigTable’. Scan count 1, logical reads 4, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Kommentera gärna!
/Björn