I am using the Spring Framework’s StoredProcedure (I am extending it, of course) to get a result set and an output parameter (@totalRowsReturned) which is an Integer. The problem is that when the resultset being returned is supposed to be an empty list, I am getting a NullPointerException when I try to retrieve the output parameter (totalRows, which naively I would expect it to be zero).
I would like to mention that the code works fine when the result set being found is not empty.
My questions are:
-
Why isn’t
@totalRowsReturnedbeing set to zero in this case? (Or in case it is, why can’t I retrieve it through the Java code?) -
How can I make this code (Java code + T-SQL code) work in such a way that
@totalRowsReturnedwill be set to zero when required, and I could retrieve it through the Java code?
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 stored procedure:
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
UPDATE:
Actually, my stored procedure looks more like this (the change is the additional @first_id = b.book_id in the 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
The problem is, that when there are no rows returned from the SELECT, b.book_id is not defined, so I get an 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.
So it seems like if I keep the OVER(), then @totalRows=COUNT(*) OVER() fails when there are zero rows returned, and if I remove the OVER(), then @first_id = b.book_id fails.
Any idea how I overcome this?
COUNT(*) OVER ()is not the correct thing to use here. Just useCOUNT(*)COUNT(*) OVER ()returns a result set with as many rows as theCOUNT. e.g if the result is 3 the result set will beThe effect of your query is then to repeatedly re-assign the value
3to the@totalRowsvariable as many times as there are rows which is completely pointless.Conversely if
COUNT(*) = 0then theCOUNT(*) OVER ()result set is empty so your variable is never assigned to at all.COUNT(*)will always give you a single row scalar resultset here that you can assign to the variable and will have a more efficient execution plan without unnecessary common subexpression spools too.Edit
In response to your question in the comments. This does the same thing as your linked article. It can use a narrower index to find the (say) 10,000th Employee then joins onto Department only for the 1 subsequent page of records. This paging method only works correctly because each employee has exactly one department.