Хранимая процедура T-SQL не возвращает ноль с помощью COUNT (*) для пустого результата SELECT - PullRequest
1 голос
/ 18 января 2012

Я использую Spring Framework StoredProcedure (я, конечно, расширяю его), чтобы получить набор результатов и выходной параметр (@totalRowsReturned), который является целым числом.Проблема в том, что когда возвращаемый набор результатов должен быть пустым списком, я получаю NullPointerException, когда пытаюсь получить выходной параметр (totalRows, который наивно я ожидал бы, что он будет нулевым).

Хочу отметить, что код работает нормально, когда найденный набор результатов не пустой.

Мои вопросы:

  1. Почему в этом случае @totalRowsReturned не устанавливается на ноль?(Или, если это так, почему я не могу получить его через код Java?)

  2. Как я могу заставить этот код (код Java + код T-SQL) работать таким образомспособ, которым @totalRowsReturned будет установлен в ноль при необходимости, и я мог бы получить его через код Java?

Dao:

List<Book> books = null;
int totalRows = 0;

Map<String, Object> results = storedProcedure.execute(parameters);
books = (List<Book>) results.get("rs");
totalRows = (Integer) results.get("totalRowsReturned"); // NullPointerException on this line if total rows are supposed to be zero!!

хранимая процедура T-SQL:

CREATE PROCEDURE Find_Books

    @authorName Varchar(250),   
    @totalRowsReturned INTEGER OUTPUT

AS

BEGIN

    SET NOCOUNT ON;
    DECLARE @SelectQuery NVARCHAR(2000)

    SET @SelectQuery = 'SELECT @totalRows=COUNT(*) OVER() FROM book b WHERE b.author_name = @authorName'

    Execute sp_Executesql @SelectQuery, N'@authorName VARCHAR(250), @totalRows int OUTPUT', @authorName, @totalRows=@totalRowsReturned OUTPUT

-- Select resultset goes here...

END

ОБНОВЛЕНИЕ:

На самом деле моя хранимая процедура выглядит примерно так (изменениеявляется дополнительным @first_id = b.book_id в SELECT):

CREATE PROCEDURE Find_Books

    @authorName Varchar(250),   
    @totalRowsReturned INTEGER OUTPUT

AS

BEGIN

    SET NOCOUNT ON;
    DECLARE @SelectQuery NVARCHAR(2000)

    DECLARE @first_id int
    DECLARE @first_id_local_returned int

    SET @SelectQuery = 'SELECT @first_id = b.book_id, @totalRows=COUNT(*) OVER() FROM book b WHERE b.author_name = @authorName'

    Execute sp_Executesql @SelectQuery, N'@authorName VARCHAR(250), @first_id int OUTPUT, @totalRows int OUTPUT', @authorName, @first_id=@first_id_local_returned OUTPUT, @totalRows=@totalRowsReturned OUTPUT

    -- Select resultset goes here... I am using the value of @first_id_local_returned in this SELECT...

END

Проблема в том, что когда нет строк, возвращаемых из SELECT, b.book_id не определен, поэтому яполучить org.springframework.dao.TransientDataAccessResourceException ... Column 'book.book_id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

Таким образом, похоже, что если я сохраню OVER(), то @totalRows=COUNT(*) OVER() завершится неудачно, когда будет возвращено ноль строк, и если я удалю OVER(),тогда @first_id = b.book_id терпит неудачу.

Есть идеи, как мне это преодолеть?

Ответы [ 3 ]

2 голосов
/ 19 января 2012

Вам нужно использовать агрегатную функцию на b.book_id.

Вы можете использовать min()

SELECT @first_id = min(b.book_id), @totalRows=COUNT(*) FROM book ...

или max(), если это более уместно в вашем случае

SELECT @first_id = max(b.book_id), @totalRows=COUNT(*) FROM book ...
2 голосов
/ 18 января 2012

COUNT(*) OVER () это не правильная вещь, чтобы использовать здесь.Просто используйте COUNT(*)

COUNT(*) OVER (), чтобы получить набор результатов, содержащий столько строк, сколько COUNT.например, если результат равен 3, результирующий набор будет

3
3
3

Эффект вашего запроса заключается в многократном повторном присвоении значения 3 переменной @totalRows столько раз, сколько строкчто совершенно бессмысленно.

И наоборот, если COUNT(*) = 0, тогда результирующий набор COUNT(*) OVER () пуст, поэтому ваша переменная никогда не назначается вообще.

COUNT(*) всегда даст вам одинСтрока скалярного набора результатов, которую вы можете назначить переменной и которая будет иметь более эффективный план выполнения без лишних общих катушек подвыражения.

Редактировать

В ответ на ваш вопросв комментариях.Это делает то же самое, что и ваша связанная статья.Он может использовать более узкий индекс, чтобы найти (скажем) 10 000-го сотрудника, затем присоединяется к отделу только для 1 последующей страницы записей.Этот метод подкачки работает правильно, потому что у каждого сотрудника есть только один отдел.

WITH E1(RN, EmployeeID)
     AS (SELECT ROW_NUMBER() OVER (ORDER BY EmployeeID),
                EmployeeID
         FROM   Employees)
SELECT TOP (@maximumRows) e.*,
                          d.Name AS DepartmentName
FROM   Employees e
       INNER JOIN Departments d
         ON e.DepartmentID = d.DepartmentID
WHERE  EmployeeID >= (SELECT EmployeeID
                      FROM   E1
                      WHERE  RN = @startRowIndex)
ORDER  BY e.EmployeeID  
1 голос
/ 19 января 2012

Я не на 100% уверен в том, что вы пытаетесь сделать, но я думаю, что-то вроде этого (вам действительно не нужен / не нужен динамический SQL в этом случае):

CREATE PROCEDURE Find_Books
    @authorName Varchar(250),   
    @totalRowsReturned INTEGER OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
    SET @totalRowsReturned = SELECT COUNT(*) 
        FROM book b WHERE b.author_name = @authorName
    SELECT b.book_id 
        FROM book b WHERE b.author_name = @authorName
END
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...