Send the total rows/last row, included with the resultset - sql

I have this procedure used for getting items on the current page. I would also like the include an OUT var having the total number of items so I can calculate the total number of pages.
USE [some_name]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[GetFilteredProductDetails]
#start int,
#end int
AS
BEGIN
SET NOCOUNT ON;
SELECT *
FROM
(
SELECT *, (ROW_NUMBER() OVER (ORDER BY itemid)) AS row
/* the rest of a big complex query that, so far, works.*/
) AS q
WHERE
(
row BETWEEN #start AND #end
)
END
This is my current (stripped)query, how would I be able to get either get the last rownumber/total rowcount of the inner select, or include the last row alongside of the rows between #start and #end.

COUNT(*) with an empty OVER() clause will give you the total row count. You could then add that into the WHERE clause if you need the last row returned.
SELECT *
FROM
(
SELECT *,
(ROW_NUMBER() OVER (ORDER BY itemid)) AS row,
COUNT(*) OVER() AS row_count
/* the rest of a big complex query that, so far, works.*/
) AS q
WHERE
(
row BETWEEN #start AND #end or row=row_count
)

Related

Is it possible to get the last number generated from ROW_NUMBER in SQL

I currently have such a query inside my stored procedure:
INSERT INTO YTDTRNI (TRCDE, PROD, WH, DESCR, UCOST, TCOST, DRAC, CRAC, REM, QTY, UM, ORDNO, TRDATE, SYSDATE, PERIOD, USERID)
SELECT
'AJ', PROD, WH, DESCR, 0, -TCOST, STKGL, COSGL,
'MASS ADJUSTMENT', 0, UM,
CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS nvarchar(255)),
GETDATE(), GETDATE(), #inputPeriod, #inputUserId
FROM
INV
WHERE
H = 0
I am making use of row_number() to get a number that is incrementing itself while executing the query.
For example the query above INSERT 2018 records in YTDTRNI table. So the last number generated by this row_number() function is 2018. My question now is whether is it possible to get hold of this last number generated by row_number().
In another table, I have a value stored as I1000 for example. So after performing the above operation. I need to update this table with the new value of I3018 (1000+2018).
I am stuck on how to move on. Open to any advice if whatever I am doing is incorrect or not following conventions/standards.
just do a ##rowcount after your query
DECLARE #rc INT
INSERT INTO YTDTRNI ( ... )
SELECT #rc = ##rowcount
after that you can use this #rc to update the other table
##ROWCOUNT is not reliable if there are triggers in the database. I would strongly discourage you from using it.
Instead, use OUTPUT:
declare #t table (rn int);
insert into . . .
output (inserted.ordno) into #t
select . . .;
Then you can simply do:
select max(ordno) from #t;
This captures exactly what is input into the table.

SQL - Variable assignment with other columns selection

I am trying to get the records as well as records count using common table expressions, but I want to store the records count in a variable and it will ofcourse give me the error
A SELECT statement that assigns a value to a variable must not be
combined with data-retrieval operations.
I am trying something like this
declare #count int
;with allRecords as
(
-- query fetching all the records with many joins
),
recordsCount as
(
select count(*) as Total from allRecords
)
select allRecords.*, #count=recordsCount.Total from allRecords, recordsCount
where -- multiple conditions
Is there any work around for this?
Actually the #count variable is an output variable of my stored procedure so I want to return the result as well as fill this #count variable
You can't do it like this. If you want to get the number of rows the select statement returned into a variable you should use the built-in global variable ##ROWCOUNT:
DECLARE #count int
;WITH allRecords as
(
-- query fetching all the records with many joins
)
SELECT allRecords.*
FROM allRecords
SELECT #Count = ##ROWCOUNT
Update:
Well, in that case you have no choise that I'm aware of other then using a temporary table:
SELECT /* columns */ INTO #tempTableName
-- rest of the select statement
SELECT #Count = COUNT(*)
FROM #tempTableName
SELECT *
FROM #tempTableName
WHERE <conditions>
DROP TABLE #tempTableName
declare #count int
;with allRecords as
(
-- query fetching all the records with many joins
)
select #count = count(*) as Total from allRecords
I'd use a temp table or table variable here. You can then do separate statements for select #count = count(*) from #allrecords and select * from #allrecords

SQL stored procedure SET output param using COUNT(*) ON a CTE

I'm using a stored procedure with a CTE and doing some paging. I also want to return an output parameter with the total count of the returned query before my paging.
My problem is that I get an error that "OrderedSet" is not a valid object name.
#ft INT,
#page INT,
#pagesize INT,
#count INT OUTPUT
AS
BEGIN
DECLARE #offset INT
SET #offset = #page * #pagesize
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
WITH OrderedSet AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Id DESC) AS 'Index'
FROM tbl_BulkUploadFiles buf
WHERE
buf.FileType = #ft )
SELECT * FROM OrderedSet WHERE [Index] BETWEEN #offset AND (#offset + #pagesize)
SET #count = (SELECT COUNT(*) FROM OrderedSet)
END
So my issue is on the last line, error is that last OrderedSet is not a valid object name.
Thanks in advance for any help!
Here are 2 approaches that avoid copying and pasting all the CTEs multiple times.
Return total rows as column of result set
Benefit here is that you can calculate total rows without multiple queries and temp tables, but you have to add logic to your front end to get the total row count from the first row of the result set before iterating over it to display the paged set. Another consideration is that you must account for no rows being returned, so set your total row count to 0 if no rows returned.
;WITH OrderedSet AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id DESC) AS Seq,
ROW_NUMBER() OVER (ORDER BY Id) AS SeqRev
FROM tbl_BulkUploadFiles buf
WHERE buf.FileType = #ft
)
SELECT *, Seq + SeqRev - 1 AS [TotalCount]
FROM OrderedSet
WHERE Seq BETWEEN #offset AND (#offset + #pagesize)
Utilize a temp table
While there is a cost of a temp table, if your database instance follows best practices for tempdb (multiple files for multi-cores, reasonable initial size, etc), 200k rows may not be a big deal since the context is lost after the stored proc completes, so the 200k rows don't exist for too long. However, it does present challenges if these stored procs are called quite often concurrently - doesn't scale too well. However, you are not keeping the entire table - just the paged rows, so hopefully your page sizes are much smaller than 200k rows.
The approach below tries to minimize the tempdb cost being able to calculate the row count by getting only the first row due to the method of ASC and DESC ROW_NUMBERs.
;WITH OrderedSet AS (
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id DESC) AS Seq,
ROW_NUMBER() OVER (ORDER BY Id) AS SeqRev
FROM #buf buf --tbl_BulkUploadFiles buf
WHERE buf.FileType = #ft
)
SELECT * INTO #T
FROM OrderedSet
WHERE Seq BETWEEN #offset AND (#offset + #pagesize)
SET #count = COALESCE((SELECT TOP 1 SeqRev + Seq - 1 FROM #T), 0)
SELECT * FROM #T
Note: The method used above for calculating row counts was adapted from How to reference one CTE twice? and http://www.sqlservercentral.com/articles/T-SQL/66030/.
You can't use the CTE in more than one select statement. From the MSDN docs (talking about the CTE itself).
This is derived from a simple query and defined within the execution
scope of a single SELECT, INSERT, UPDATE, or DELETE statement.
You either need to run the CTE twice (probably a bad idea) or select the results of the CTE into a temp table and then select the paged data from that along with the total count.
Only alternative I see is repeating the query as inline view
select #count = numrows FROM
(
SELECT count(*) as numrows,
ROW_NUMBER() OVER (ORDER BY Id DESC) AS 'Index'
FROM tbl_BulkUploadFiles buf
WHERE
buf.FileType = #ft
) XXX WHERE [Index] BETWEEN #offset AND (#offset + #pagesize)

SQL server udf not working

I'm now all day on a fairly simple udf. It's below. When I paste the select statement into a query, it runs as expected... when I execute the entire function, I get "0" every time. As you know there aren't a ton of debugging options, so it's hard to see what value are/ aren't being set as it executes. The basic purpose of it is to make sure stock data exists in a daily pricing table. So I can check by how many days' data I'm checking for, the ticker, and the latest trading date to check. A subquery gets me the correct trading dates, and I use "IN" to pull data out of the pricing and vol table... if the count of what comes back is less than the number of days I'm checking, no good. If it does, we're in business. Any help would be great, I'm a newb that is punting at this point:
ALTER FUNCTION dbo.PricingVolDataAvailableToDateProvided
(#Ticker char,
#StartDate DATE,
#NumberOfDaysBack int)
RETURNS bit
AS
BEGIN
DECLARE #Result bit
DECLARE #RecordCount int
SET #RecordCount = (
SELECT COUNT(TradeDate) AS Expr1
FROM (SELECT TOP (100) PERCENT TradeDate
FROM tblDailyPricingAndVol
WHERE ( Symbol = #Ticker )
AND ( TradeDate IN (SELECT TOP (#NumberOfDaysBack)
CAST(TradingDate AS DATE) AS Expr1
FROM tblTradingDays
WHERE ( TradingDate <= #StartDate )
ORDER BY TradingDate DESC) )
ORDER BY TradeDate DESC) AS TempTable )
IF #RecordCount = #NumberOfDaysBack
SET #Result = 1
ELSE
SET #Result = 0
RETURN #Result
END
#Ticker char seems suspect.
If you don't declare a length in the parameter definition it defaults to char(1) so quite likely your passed in tickers are being silently truncated - hence no matches.
SELECT TOP (100) PERCENT TradeDate ... ORDER BY TradeDate DESC
in the derived table is pointless but won't affect the result.

SQL Server 2008 paging methods?

I have to work with a potentially large list of records and I've been Googling for ways to avoid selecting the whole list, instead I want to let users select a page (like from 1 to 10) and display the records accordingly.
Say, for 1000 records I will have 100 pages of 10 records each and the most recent 10 records will be displayed first then if the user click on page 5, it will show records from 41 to 50.
Is it a good idea to add a row number to each record then query based on row number? Is there a better way of achieving the paging result without too much overhead?
So far those methods as described here look the most promising:
http://developer.berlios.de/docman/display_doc.php?docid=739&group_id=2899
http://www.codeproject.com/KB/aspnet/PagingLarge.aspx
The following T-SQL stored procedure is a very efficient implementation of paging. THE SQL optimiser can find the first ID very fast. Combine this with the use of ROWCOUNT, and you have an approach that is both CPU-efficient and read-efficient. For a table with a large number of rows, it certainly beats any approach that I've seen using a temporary table or table variable.
NB: I'm using a sequential identity column in this example, but the code works on any column suitable for page sorting. Also, sequence breaks in the column being used don't affect the result as the code selects a number of rows rather than a column value.
EDIT: If you're sorting on a column with potentially non-unique values (eg LastName), then add a second column to the Order By clause to make the sort values unique again.
CREATE PROCEDURE dbo.PagingTest
(
#PageNumber int,
#PageSize int
)
AS
DECLARE #FirstId int, #FirstRow int
SET #FirstRow = ( (#PageNumber - 1) * #PageSize ) + 1
SET ROWCOUNT #FirstRow
-- Add check here to ensure that #FirstRow is not
-- greater than the number of rows in the table.
SELECT #FirstId = [Id]
FROM dbo.TestTable
ORDER BY [Id]
SET ROWCOUNT #PageSize
SELECT *
FROM dbo.TestTable
WHERE [Id] >= #FirstId
ORDER BY [Id]
SET ROWCOUNT 0
GO
If you use a CTE with two row_number() columns - one sorted asc, one desc, you get row numbers for paging as well as the total records by adding the two row_number columns.
create procedure get_pages(#page_number int, #page_length int)
as
set nocount on;
with cte as
(
select
Row_Number() over (order by sort_column desc) as row_num
,Row_Number() over (order by sort_column) as inverse_row_num
,id as cte_id
From my_table
)
Select
row_num+inverse_row_num as total_rows
,*
from CTE inner join my_table
on cte_id=df_messages.id
where row_num between
(#page_number)*#page_length
and (#page_number+1)*#page_length
order by rownumber
Using OFFSET
Others have explained how the ROW_NUMBER() OVER() ranking function can be used to perform pages. It's worth mentioning that SQL Server 2012 finally included support for the SQL standard OFFSET .. FETCH clause:
SELECT first_name, last_name, score
FROM players
ORDER BY score DESC
OFFSET 40 ROWS FETCH NEXT 10 ROWS ONLY
If you're using SQL Server 2012 and backwards-compatibility is not an issue, you should probably prefer this clause as it will be executed more optimally by SQL Server in corner cases.
Using the SEEK Method
There is an entirely different, much faster way to perform paging in SQL. This is often called the "seek method" as described in this blog post here.
SELECT TOP 10 first_name, last_name, score
FROM players
WHERE (score < #previousScore)
OR (score = #previousScore AND player_id < #previousPlayerId)
ORDER BY score DESC, player_id DESC
The #previousScore and #previousPlayerId values are the respective values of the last record from the previous page. This allows you to fetch the "next" page. If the ORDER BY direction is ASC, simply use > instead.
With the above method, you cannot immediately jump to page 4 without having first fetched the previous 40 records. But often, you do not want to jump that far anyway. Instead, you get a much faster query that might be able to fetch data in constant time, depending on your indexing. Plus, your pages remain "stable", no matter if the underlying data changes (e.g. on page 1, while you're on page 4).
This is the best way to implement paging when lazy loading more data in web applications, for instance.
Note, the "seek method" is also called keyset paging.
Try something like this:
declare #page int = 2
declare #size int = 10
declare #lower int = (#page - 1) * #size
declare #upper int = (#page ) * #size
select * from (
select
ROW_NUMBER() over (order by some_column) lfd,
* from your_table
) as t
where lfd between #lower and #upper
order by some_column
Here's an updated version of #RoadWarrior's code, using TOP. Performance is identical, and extremely fast. Make sure you have an index on TestTable.ID
CREATE PROC dbo.PagingTest
#SkipRows int,
#GetRows int
AS
DECLARE #FirstId int
SELECT TOP (#SkipRows)
#FirstId = [Id]
FROM dbo.TestTable
ORDER BY [Id]
SELECT TOP (#GetRows) *
FROM dbo.TestTable
WHERE [Id] >= #FirstId
ORDER BY [Id]
GO
Try this
Declare #RowStart int, #RowEnd int;
SET #RowStart = 4;
SET #RowEnd = 7;
With MessageEntities As
(
Select ROW_NUMBER() Over (Order By [MESSAGE_ID]) As Row, [MESSAGE_ID]
From [TBL_NAFETHAH_MESSAGES]
)
Select m0.MESSAGE_ID, m0.MESSAGE_SENDER_NAME,
m0.MESSAGE_SUBJECT, m0.MESSAGE_TEXT
From MessageEntities M
Inner Join [TBL_NAFETHAH_MESSAGES] m0 on M.MESSAGE_ID = m0.MESSAGE_ID
Where M.Row Between #RowStart AND #RowEnd
Order By M.Row Asc
GO
Why not to use recommended solution:
SELECT VALUE product FROM
AdventureWorksEntities.Products AS product
order by product.ListPrice SKIP #skip LIMIT #limit