Нужен счетчик строк после инструкции SELECT: каков оптимальный подход SQL?

Я пытаюсь выбрать столбец из одной таблицы (без соединений), и мне нужно количество строк, в идеале, прежде чем я начну извлекать строки. Я пришел к двум подходам, которые предоставляют необходимую мне информацию.

подход 1:

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'

или подход 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'

Я делаю это, потому что мой драйвер SQL (SQL Native Client 9.0) не позволяет мне использовать SQLRowCount на Выберите оператор, но мне нужно знать количество строк в моем результате, чтобы выделить массив перед назначением ему информации. Использование динамически выделенного контейнера, к сожалению, не является вариантом в этой области моей программы.

Я обеспокоен тем, что может произойти следующий сценарий:

  • выберите для count происходит
  • другая инструкция происходит, добавление или удаление строки
  • выберите для данных происходит и внезапно массив неправильный размер.
    - В худшем случае это будет попытка записать данные за пределы массивов и сбой моей программы.

запрещает ли подход 2 эту проблему?

кроме того, будет ли один из двух подходов быстрее? Если да, то какой?

наконец, есть ли лучший подход, который я должен рассмотреть (возможно, способ поручить драйверу вернуть количество строк в выбранном результате с помощью SQLRowCount?)

для тех, кто спросил, я использую родной C++ с вышеупомянутым драйвером SQL (предоставленным Microsoft.)

10 ответов


есть только два способа быть на 100% уверенным, что COUNT(*) и фактический запрос даст последовательные результаты:

  • в сочетании с COUNT(*) с запросом, как в вашем подходе 2. Я рекомендую форму, которую вы показываете в своем примере, а не коррелированную форму подзапроса, показанную в комментарии от kogus.
  • использовать два запроса, как в вашем подходе 1, после запуска транзакции в SNAPSHOT или SERIALIZABLE уровень изоляции.

используя один из этих уровни изоляции важны, поскольку любой другой уровень изоляции позволяет новым строкам, созданным другими клиентами, стать видимыми в текущей транзакции. Прочитайте документацию MSDN на SET TRANSACTION ISOLATION для получения более подробной информации.


Если вы используете SQL Server, после запроса вы можете выбрать функцию @@RowCount (или если ваш результирующий набор может иметь более 2 миллиардов строк, используйте функцию BIGROW_COUNT ()). Это возвращает количество строк, выбранных предыдущим оператором, или количество строк, затронутых инструкцией insert/update/delete.

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

SELECT @@Rowcount

или если вы хотите, чтобы количество строк, включенных в результат, отправленный аналогично подходу №2, Вы можете использовать предложение OVER (см. 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'

использование предложения OVER будет иметь гораздо лучшую производительность, чем использование подзапроса для получения количества строк. Использование @@RowCount будет иметь лучшую производительность, потому что для оператора select @@RowCount не будет никаких затрат на запрос

Update в ответ на комментарий: пример, который я дал, даст # строк в разделе, определенном в этом случае "PARTITION BY мой стол.foo." Значение столбца в каждой строке-это количество строк с одинаковым значением таблицы my_table.foo. Поскольку в вашем примере запроса было предложение " WHERE my_table.foo = 'bar'", все строки в наборе результатов будут иметь одинаковое значение my_table.foo и, следовательно, значение в столбце будет одинаковым для всех строк и равным (в данном случае) этому # строк в запросе.

вот лучший / простой пример того, как включить столбец в каждую строку, которая является общим # строк в набор результатов. Просто удалите необязательное предложение Partition By.

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

Approach 2 всегда будет возвращать количество, соответствующее вашему набору результатов.

Я предлагаю вам связать подзапрос с вашим внешним запросом, чтобы гарантировать, что условие на вашем счете соответствует условию в наборе данных.

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

Если вас беспокоит количество строк, удовлетворяющих условию, может измениться за несколько миллисекунд с момента выполнения запроса и получения результатов, вы можете/должны выполнить запросы внутри транзакции:

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

это всегда возвращает правильные значения.

кроме того, если вы используете SQL Server, вы можете использовать @@ROWCOUNT, чтобы получить количество строк, затронутых последним оператором, и перенаправить вывод реальные запрос к temp таблица или переменная таблицы, поэтому вы можете вернуть все вместе, и нет необходимости в транзакции:

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

вот некоторые идеи:

  • перейдите к подходу №1 и измените размер массива, чтобы сохранить дополнительные результаты или использовать тип, который автоматически изменяет размер по мере необходимости (вы не упоминаете, какой язык вы используете, поэтому я не могу быть более конкретным).
  • вы можете выполнить Оба оператора в подходе #1 в рамках транзакции, чтобы гарантировать, что счетчики одинаковы оба раза, если ваша база данных поддерживает это.
  • Я не уверен, что вы делаете с данными, а если это возможно обрабатывать результаты, не сохраняя их все сначала, это может быть лучшим методом.

Если вы действительно обеспокоены тем, что количество строк изменится между select count и Select statement, почему бы сначала не выбрать строки во временную таблицу? Таким образом, вы будете синхронизированы.


Почему бы вам не поместить свои результаты в вектор? Таким образом, вы не должны знать размер перед рукой.


возможно, вы захотите подумать о лучшем шаблоне для работы с данными этого типа.

никакой самонадеянный драйвер SQL не скажет вам, сколько строк ваш запрос вернет перед возвращением строк, потому что ответ может измениться (если вы не используете транзакцию, которая создает свои собственные проблемы.)

количество строк не изменится-google для ACID и SQL.


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

просто добавить это, потому что это лучший результат в google для этого вопроса. В sqlite я использовал это, чтобы получить rowcount.

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