How to retrieve the total row count of a query with TOP - sql-server

I have a SQL Server 2008 query
SELECT TOP 10 *
FROM T
WHERE ...
ORDER BY ...
I'd like to get also the total number of the rows. The obious way is to make a second query
SELECT COUNT(*)
FROM T
WHERE ...
ORDER BY ...
Is there an efficient method?
Thanks

Do you want a second query?
SELECT TOP 10
*, foo.bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
OR
DECLARE #bar int
SELECT #bar = COUNT(*) AS bar FROM T WHERE ...
SELECT TOP 10
*, #bar
FROM
T
CROSS JOIN
(SELECT COUNT(*) AS bar FROM T WHERE ...) foo
WHERE
...
ORDER BY
...
Or (Edit: using WITH)
WITH cTotal AS
(
SELECT COUNT(*) AS bar FROM T WHERE ...)
)
SELECT TOP 10
*, cTotal .bar
FROM
T
WHERE
...
ORDER BY
...

What is in this answer seems to work:
https://stackoverflow.com/a/19125458/16241
Basically you do a:
SELECT top 100 YourColumns, TotalCount = Count(*) Over()
From YourTable
Where SomeValue = 32
TotalCount will have the total number of rows. It is listed on each row though.
When I tested this the query plan showed the table only being hit once.

Remove the ORDER BY clause from the 2nd query as well.

No.
SQL Server doesn't keep COUNT(*) in metadata like MyISAM, it calculates it every time.
UPDATE: If you need an estimate, you can use statistics metadata:
SELECT rows
FROM dbo.sysindexes
WHERE name = #primary_key,
where #primary_key is your table's primary key name.
This will return the COUNT(*) from last statistics update.

SELECT TOP (2) *,
(SELECT COUNT(*) AS Expr1 FROM T) AS C
FROM T

Related

T-SQL Multi-layered CTE query with Aggregates

I have a long Common Table Expression (CTE) query which is trying to calculate percent difference between each users' average score and group average score.
I would like for my multi-layered CTE query to filter and reduce bulk of records down to the following table:
UserID Tag UserAvg GroupAvg PercentDifference
1 Cat 72.50 73 -0.68
2 Cat 75.50 73 3.36
3 Cat 75 73 2.70
4 Cat 73.25 73 0.34
5 Cat 52.3333 73 -32.97
6 Cat 86.25 73 16.64
My problem is getting GroupAvg column so that I can perform % Difference calculation.
To illustrate the current approach I am using; here is the summary of my CTE query:
WITH
-- select 1st 3 columns
UserScores AS (select UserID, Tag, Score FROM {multiple-table} WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (select UserID, Tag, AVG(Score) AS UserAvg GROUP BY UserID, Tag FROM UserScores),
-- calculate GroupAvg
GroupAverage AS (select AVG(UserAvg) AS GroupAvg FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (select UserID, Tag, UserAvg, 73 AS GroupAvg, (((UserAvg-73)/((UserAvg+73)/2))*100) AS PercentDifference FROM ScoreAverages )
-- do something with results
select * from PercentDiff
Simple enough; right?
Notice that I have hard coded 73 as my GroupAvg value. I am unsure how to construct required sql query that would allow me to go from ScoreAverages to PercentDiff table.
Is it possible to perform SELECT within a SELECT statement? And I am not looking for something of the following:
select * from X where Id in (select Id from Y where Name like '%abc%')
Or I am simply trying to do too much in one go?
Yes, it's called a sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, Column3
FROM ...
To use the result of the sub-select in another column's calculation, you can either repeat the sub-select:
SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage, (Column3 - (SELECT QUERY THAT GETS GROUP AVERAGE)) AS Column4
FROM ...
Or you can reference it the same as you would any other column in the outer query or a subsequent CTE:
WITH CTE1 AS (SELECT Column1, Column2, (SELECT QUERY THAT GETS GROUP AVERAGE) AS GroupAverage
FROM ...)
, CTE2 AS (SELECT *, Column3-GroupAverage) AS Column4
FROM CTE1
JOIN ...
It is possible, as shown in Tab Alleman's answer, but in your case it's not necessary. Since you already calculate the GroupAvg in the cte chain, you can use it in the final query. and since the GroupAverage only contains one row, you can simply add a CROSS JOIN to it:
;WITH
-- select 1st 3 columns
UserScores AS (
select UserID, Tag, Score
FROM {multiple-table}
WHERE Tag = 'Cat'),
-- add UserAvg column by grouping records
ScoreAverages AS (
select UserID, Tag, AVG(Score) AS UserAvg
FROM UserScores
GROUP BY UserID, Tag),
-- calculate GroupAvg
GroupAverage AS (
select AVG(UserAvg) AS GroupAvg
FROM ScoreAverages),
-- calculate % difference
PercentDiff AS (
select UserID, Tag, UserAvg, GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM ScoreAverages
CROSS JOIN GroupAverage)
-- do something with results
select * from PercentDiff
I just thought you could do this with a single cte like so.
;WITH UserAverages AS
(
SELECT UserID,
Tag,
AVG(Score) AS UserAvg,
AVG(AVG(Score)) OVER () AS GroupAvg
FROM {multiple-table}
WHERE Tag = 'Cat'
GROUP BY UserID, Tag
)
SELECT UserID,
Tag,
UserAvg,
GroupAvg,
(((UserAvg-GroupAvg)/((UserAvg+GroupAvg)/2))*100) AS PercentDifference
FROM UserAverages

Using select * and count(*) together in a query in SQL Server 2012

I have a table with 12 columns.
I need a query for computing COUNT(*) and selecting all the columns.
I mean I want to have these two queries just in one query:
select *
from mytable
where OneOfTheColumns = something;
select COUNT(*)
from mytable
where OneOfTheColumns = something;
Conditions and tables are the same.
Can I do this?
Thanks a million.
You can use a window function for that
select *,
count(*) over () as total_count
from mytable
where OneOfTheFields = something;

How to select data top x data after y rows from SQL Server

For example I have a table which contains 10'000 rows. I want to select top 100 rows after top 500th row. How can I do this most efficiently.
Query needed for SQL Server 2008
For example i have this query already but i wonder are there any more effective solution
SELECT TOP 100 xx
FROM nn
WHERE cc NOT IN
(SELECT TOP 500 cc
FROM nn ORDER BY cc ASC)
Tutorial 25: Efficiently Paging Through Large Amounts of Data
with cte as (
SELECT ...,
ROW_NUMBER () OVER (ORDER BY ...) as rn
FROM ...)
SELECT ... FROM cte
WHERE rn BETWEEN 500 and 600;
Select T0P 600 *
from my table
where --whatever condition you want
except
select top 500 *
from mytable
where --whatever condition you want
SELECT
col1,
col2
FROM (
SELECT ROW_NUMBER() OVER (
ORDER BY [t0].someColumn) as ROW_NUMBER,
col1,
col2
FROM [dbo].[someTable] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN 501 and 600
ORDER BY [t1].[ROW_NUMBER]
Selecting TOP 500, then concatenating the TOP 100 to the result set.
Normally, in order to worth doing this, you need to have some criteria on which to base what your need 500 records for, and only 100 for another condition. I assume that these conditions are condition1 for the TOP 500, and condition2 for the TOP 100 you want. Because the conditions differ, that is the reason why the records might not be the same based on TOP 100.
select TOP 500 *
from MyTable
where -- condition1 -- Retrieving the first 500 rows meeting condition1
union
select TOP 100 *
from MyTable
where -- condition2 -- Retrieving the first 100 rows meeting condition2
-- The complete result set of the two queries will be combined (UNIONed) into only one result set.
EDIT #1
this is not what i meant. i want to select top 100 rows coming after top 500 th row. so selecting rows 501-600
After your comment, I better understood what you want to achieve. Try this:
WITH Results AS (
select TOP 600 f.*, ROW_NUMBER() OVER (ORDER BY f.[type]) as RowNumber
from MyTable f
) select *
from Results
where RowNumber between 501 and 600
Does this help?

Anyway to get a value similar to ##ROWCOUNT when TOP is used?

If I have a SQL statement such as:
SELECT TOP 5
*
FROM Person
WHERE Name LIKE 'Sm%'
ORDER BY ID DESC
PRINT ##ROWCOUNT
-- shows '5'
Is there anyway to get a value like ##ROWCOUNT that is the actual count of all of the rows that match the query without re-issuing the query again sans the TOP 5?
The actual problem is a much more complex and intensive query that performs beautifully since we can use TOP n or SET ROWCOUNT n but then we cannot get a total count which is required to display paging information in the UI correctly. Presently we have to re-issue the query with a #Count = COUNT(ID) instead of *.
Whilst this doesn't exactly meet your requirement (in that the total count isn't returned as a variable), it can be done in a single statement:
;WITH rowCTE
AS
(
SELECT *
,ROW_NUMBER() OVER (ORDER BY ID DESC) AS rn1
,ROW_NUMBER() OVER (ORDER BY ID ASC) AS rn2
FROM Person
WHERE Name LIKE 'Sm%'
)
SELECT *
,(rn1 + rn2) - 1 as totalCount
FROM rowCTE
WHERE rn1 <=5
The totalCount column will have the total number of rows matching the where filter.
It would be interesting to see how this stacks up performance-wise against two queries on a decent-sized data-set.
you'll have to run another COUNT() query:
SELECT TOP 5
*
FROM Person
WHERE Name LIKE 'Sm%'
ORDER BY ID DESC
DECLARE #r int
SELECT
#r=COUNT(*)
FROM Person
WHERE Name LIKE 'Sm%'
select #r
Something like this may do it:
SELECT TOP 5
*
FROM Person
cross join (select count(*) HowMany
from Person
WHERE Name LIKE 'Sm%') tot
WHERE Name LIKE 'Sm%'
ORDER BY ID DESC
The subquery returns one row with one column containing the full count; the cross join includes it with all rows returned by the "main" query"; and "SELECT *" would include new column HowMany.
Depending on your needs, the next step might be to filter out that column from your return set. One way would be to load the data from the query into a temp table, and then return just the desired columns, and get rowcount from the HowMany column from any row.

How do I select last 5 rows in a table without sorting?

I want to select the last 5 records from a table in SQL Server without arranging the table in ascending or descending order.
This is just about the most bizarre query I've ever written, but I'm pretty sure it gets the "last 5" rows from a table without ordering:
select *
from issues
where issueid not in (
select top (
(select count(*) from issues) - 5
) issueid
from issues
)
Note that this makes use of SQL Server 2005's ability to pass a value into the "top" clause - it doesn't work on SQL Server 2000.
Suppose you have an index on id, this will be lightning fast:
SELECT * FROM [MyTable] WHERE [id] > (SELECT MAX([id]) - 5 FROM [MyTable])
The way your question is phrased makes it sound like you think you have to physically resort the data in the table in order to get it back in the order you want. If so, this is not the case, the ORDER BY clause exists for this purpose. The physical order in which the records are stored remains unchanged when using ORDER BY. The records are sorted in memory (or in temporary disk space) before they are returned.
Note that the order that records get returned is not guaranteed without using an ORDER BY clause. So, while any of the the suggestions here may work, there is no reason to think they will continue to work, nor can you prove that they work in all cases with your current database. This is by design - I am assuming it is to give the database engine the freedom do as it will with the records in order to obtain best performance in the case where there is no explicit order specified.
Assuming you wanted the last 5 records sorted by the field Name in ascending order, you could do something like this, which should work in either SQL 2000 or 2005:
select Name
from (
select top 5 Name
from MyTable
order by Name desc
) a
order by Name asc
You need to count number of rows inside table ( say we have 12 rows )
then subtract 5 rows from them ( we are now in 7 )
select * where index_column > 7
select * from users
where user_id >
( (select COUNT(*) from users) - 5)
you can order them ASC or DESC
But when using this code
select TOP 5 from users order by user_id DESC
it will not be ordered easily.
select * from table limit 5 offset (select count(*) from table) - 5;
Without an order, this is impossible. What defines the "bottom"? The following will select 5 rows according to how they are stored in the database.
SELECT TOP 5 * FROM [TableName]
Well, the "last five rows" are actually the last five rows depending on your clustered index. Your clustered index, by definition, is the way that he rows are ordered. So you really can't get the "last five rows" without some order. You can, however, get the last five rows as it pertains to the clustered index.
SELECT TOP 5 * FROM MyTable
ORDER BY MyCLusteredIndexColumn1, MyCLusteredIndexColumnq, ..., MyCLusteredIndexColumnN DESC
Search 5 records from last records you can use this,
SELECT *
FROM Table Name
WHERE ID <= IDENT_CURRENT('Table Name')
AND ID >= IDENT_CURRENT('Table Name') - 5
If you know how many rows there will be in total you can use the ROW_NUMBER() function.
Here's an examble from MSDN (http://msdn.microsoft.com/en-us/library/ms186734.aspx)
USE AdventureWorks;
GO
WITH OrderedOrders AS
(
SELECT SalesOrderID, OrderDate,
ROW_NUMBER() OVER (ORDER BY OrderDate) AS 'RowNumber'
FROM Sales.SalesOrderHeader
)
SELECT *
FROM OrderedOrders
WHERE RowNumber BETWEEN 50 AND 60;
In SQL Server 2012 you can do this :
Declare #Count1 int ;
Select #Count1 = Count(*)
FROM [Log] AS L
SELECT
*
FROM [Log] AS L
ORDER BY L.id
OFFSET #Count - 5 ROWS
FETCH NEXT 5 ROWS ONLY;
Try this, if you don't have a primary key or identical column:
select [Stu_Id],[Student_Name] ,[City] ,[Registered],
RowNum = row_number() OVER (ORDER BY (SELECT 0))
from student
ORDER BY RowNum desc
You can retrieve them from memory.
So first you get the rows in a DataSet, and then get the last 5 out of the DataSet.
There is a handy trick that works in some databases for ordering in database order,
SELECT * FROM TableName ORDER BY true
Apparently, this can work in conjunction with any of the other suggestions posted here to leave the results in "order they came out of the database" order, which in some databases, is the order they were last modified in.
select *
from table
order by empno(primary key) desc
fetch first 5 rows only
Last 5 rows retrieve in mysql
This query working perfectly
SELECT * FROM (SELECT * FROM recharge ORDER BY sno DESC LIMIT 5)sub ORDER BY sno ASC
or
select sno from(select sno from recharge order by sno desc limit 5) as t where t.sno order by t.sno asc
When number of rows in table is less than 5 the answers of Matt Hamilton and msuvajac is Incorrect.
Because a TOP N rowcount value may not be negative.
A great example can be found Here.
i am using this code:
select * from tweets where placeID = '$placeID' and id > (
(select count(*) from tweets where placeID = '$placeID')-2)
In SQL Server, it does not seem possible without using ordering in the query.
This is what I have used.
SELECT *
FROM
(
SELECT TOP 5 *
FROM [MyTable]
ORDER BY Id DESC /*Primary Key*/
) AS T
ORDER BY T.Id ASC; /*Primary Key*/
DECLARE #MYVAR NVARCHAR(100)
DECLARE #step int
SET #step = 0;
DECLARE MYTESTCURSOR CURSOR
DYNAMIC
FOR
SELECT col FROM [dbo].[table]
OPEN MYTESTCURSOR
FETCH LAST FROM MYTESTCURSOR INTO #MYVAR
print #MYVAR;
WHILE #step < 10
BEGIN
FETCH PRIOR FROM MYTESTCURSOR INTO #MYVAR
print #MYVAR;
SET #step = #step + 1;
END
CLOSE MYTESTCURSOR
DEALLOCATE MYTESTCURSOR
Thanks to #Apps Tawale , Based on his answer, here's a bit of another (my) version,
To select last 5 records without an identity column,
select top 5 *,
RowNum = row_number() OVER (ORDER BY (SELECT 0))
from [dbo].[ViewEmployeeMaster]
ORDER BY RowNum desc
Nevertheless, it has an order by, but on RowNum :)
Note(1): The above query will reverse the order of what we get when we run the main select query.
So to maintain the order, we can slightly go like:
select *, RowNum2 = row_number() OVER (ORDER BY (SELECT 0))
from (
select top 5 *, RowNum = row_number() OVER (ORDER BY (SELECT 0))
from [dbo].[ViewEmployeeMaster]
ORDER BY RowNum desc
) as t1
order by RowNum2 desc
Note(2): Without an identity column, the query takes a bit of time in case of large data
Get the count of that table
select count(*) from TABLE
select top count * from TABLE where 'primary key row' NOT IN (select top (count-5) 'primary key row' from TABLE)
If you do not want to arrange the table in ascending or descending order. Use this.
select * from table limit 5 offset (select count(*) from table) - 5;

Resources