Take top count dynamically - sql

I want to create a stored procedure which takes integer values as a #top from me so that I can use it in my query, but it's not allowing me to set value of top dynamically.
select top #top * from (select url,
count(1) as shared from tblshared
group by url, uniqid having
uniqid = #uniqid) as sha order by
shared desc
I want to retrieve top n records from table so I want to pass the value of n in stored procedure and it will return me that number of top records.
Note: I don't want to use exec.
Thanks.

That will work fine if you wrap #top in brackets
select top (#top) *
from (
select url, count(1) as shared
from tblshared
group by url, uniqid
having uniqid=#uniqid) as sha
order by shared desc

You can use ROW_NUMBER() isntead of top
with t1 as(
select url,row_number() over(
partition by url, uniqid order by url, uniqid desc) as shared
from tblshared
where uniqid = #uniqid )
select * from t1 where shared < #top
REF

You could use SET ROWCOUNT:
SET ROWCOUNT #top
SELECT ...
SET ROWCOUNT 0

declare #v1 int
set #v1 = 25
set rowcount #v1
select * from MyTable Order by DateColumn
set rowcount 0

Related

Scope of table when using with clause

Below is a piece of my stored proc.
I am getting error as invalid object MyCount, Please let me know where am going wrong
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
select #cnt = COUNT(*) from MyCount
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end
select #Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )
MSDN clearly states the following about the scope of a Common Table Expression (CTE):
A common table expression (CTE) can be thought of as a temporary result set that is defined within the execution scope of a single SELECT, INSERT, UPDATE, DELETE, or CREATE VIEW statement
Once you have run the first select query, you can no longer use the CTE for your next one. You may want to consider storing the data in a temporary table or table variable if you want to access it in multiple queries.
MyCount is available only for the first query after it. Try to select all you need in this one query:
;With MyCount AS
(
Select DispatchToRegionId ,FolderNo, row_number() OVER(ORDER BY FolderNo DESC) as Row
from tblTransite where FolderNo = #VAL group by DispatchToRegionId,FolderNo
)
SELECT
#cnt = (select COUNT(*) from MyCount),
#Region =( Select top 1 DispatchToRegionId from MyCount
order by Row desc )
if #cnt = 0
begin
set #InvalidFolderNo = #VAL
print 'cnt -' + cast(#cnt as varchar(max) ) + 'invalid folder - ' + cast(#InvalidFolderNo as varchar(max) )
return
end

Use top based on condition

I have a parameter in my stored procedure that specifies number of rows to select. (Possible values: 0-100. 0 Means Select All rows)
For example #Rows = 5;
Then I can do this:
Insert into #MyTableVar
Select Top(#Rows) *
from myTable
Now, as I said before if 0 is supplied I need to return all rows.
This is a pseudo-code of what I need:
if (#Rows=0) then select * else select top(#Rows) *
I found out that there's SET ROWCOUNT that accepts 0 to return ALL rows, but I need to do an insert into a table variable which is not supported by ROWCOUNT.
Is it possible to achieve this without dynamic sql?
(I understand that I can write a simple if else statement and duplicate query, but I have pretty complex queries and there are lots fo them, I just want to avoid code duplication)
One way is to just put a big number in:
set #Rows = 5;
declare #RowsToUse = (case when #Rows = 0 then 1000000000 else #Rows end);
select top(#RowsToUse) * from myTable
First of all, you are missing the ORDER BY clause, since you are using TOP. You could do this:
SET #Rows = 5;
WITH CTE AS
(
SELECT *,
RN = ROW_NUMBER() OVER(ORDER BY Id) --put the right order here
FROM myTable
)
INSERT INTO #MyTableVar
SELECT YourColumns
FROM CTE
WHERE RN <= #Rows OR #Rows = 0

Get N th row value in sql server

DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
set #ActionNumber=(select top 1 * from dbo.ANOSplit(#ActionNumber,'-')
order by ROW_NUMBER() OVER (ORDER BY items))
select #ActionNumber
from above query i need to return the 2ND and 3RD index from initial #ActionNumber
'EHPL-DES-SQ-1021' after Split().
format of the ActionNumber is exactly as above but DES, SQ and 1021 can change.
so i can not use ORDER BY items ASC or ORDER BY items DESC because it will order alphabetically.
above query returns 'EHPL'.how can i get DES and SQ.
You can do it with the ANOSplit function, but I would insert the result into a temp table or table variable.
As you said yourself, you can't just ORDER BY the values returned by the ANOSplit function because it will order alphabetically.
--> So you can use a temp table with an IDENTITY column, and use this for sorting:
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
declare #tmp table
(
id int identity(1,1),
item varchar(20)
)
insert into #tmp (item)
select * from dbo.ANOSplit(#ActionNumber,'-')
select * from #tmp where id in (2,3)
The items will be inserted into the table in the exact order returned by the function, so after inserting you know that the lines with id 2 and 3 are the ones you want.
Try to use Substring with CharIndex >>>
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
select SUBSTRING (#ActionNumber,CHARINDEX ('-',#ActionNumber,0) + 1, 3)
This isn't tested, but I think it will work:
DECLARE #ActionNumber varchar(20)='EHPL-DES-SQ-1021'
WITH nCTE AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY items) AS RNum
FROM dbo.ANOSplit(#ActionNumber,'-')
)
SELECT * FROM nCTE WHERE RNum = 2 --put n here

Split query result by half in TSQL (obtain 2 resultsets/tables)

I have a query that returns a large number of heavy rows.
When I transform this rows in a list of CustomObject I have a big memory peak, and this transformation is made by a custom dotnet framework that I can't modify.
I need to retrieve a less number of rows to do "the transform" in two passes and then avoid the memory peak.
How can I split the result of a query by half? I need to do it in DB layer. I thing to do a "Top count(*)/2" but how to get the other half?
Thank you!
If you have identity field in the table, select first even ids, then odd ones.
select * from Table where Id % 2 = 0
select * from Table where Id % 2 = 1
You should have roughly 50% rows in each set.
Here is another way to do it from(http://www.tek-tips.com/viewthread.cfm?qid=1280248&page=5). I think it's more efficient:
Declare #Rows Int
Declare #TopRows Int
Declare #BottomRows Int
Select #Rows = Count(*) From TableName
If #Rows % 2 = 1
Begin
Set #TopRows = #Rows / 2
Set #BottomRows = #TopRows + 1
End
Else
Begin
Set #TopRows = #Rows / 2
Set #BottomRows = #TopRows
End
Set RowCount #TopRows
Select * From TableName Order By DisplayOrder
Set RowCount #BottomRows
Select * From TableNameOrder By DisplayOrderDESC
--- old answer below ---
Is this a stored procedure call or dynamic sql? Can you use temp tables?
if so, something like this would work
select row_number() OVER(order by yourorderfield) as rowNumber, *
INTO #tmp
FROM dbo.yourtable
declare #rowCount int
SELECT #rowCount = count(1) from #tmp
SELECT * from #tmp where rowNumber <= #rowCount / 2
SELECT * from #tmp where rowNumber > #rowCount / 2
DROP TABLE #tmp
SELECT TOP 50 PERCENT WITH TIES ... ORDER BY SomeThing
then
SELECT TOP 50 PERCENT ... ORDER BY SomeThing DESC
However, unless you snapshot the data first, a row in the middle may slip through or be processed twice
I don't think you should do that in SQL, unless you will always have a possibility to have the same record 2 times.
I would do it in an "software" programming language, not SQL. Java, .NET, C++, etc...

Select TOP N and set variable if more could be selected

Is there a more efficient way of doing the following SQL?
I want to select the top 50 results, but I also want to set a variable to tell me if I would have gotten more results back without the TOP
DECLARE #MoreExists BIT
SET #MoreExists = 0
DECLARE #Count INT
SELECT #Count = Count(*)
FROM MyTable WHERE ... --some expensive where clause
IF #Count > 50
SET #MoreExists = 1
SELECT TOP 50 Field1, Field2, ...
FROM MyTable WHERE ... --same expensive where clause
Select 51 results instead, use the top 50 in the client layer, and use the count to know if there are more.
A spin on #Dougs answer
SET NOCOUNT ON
SELECT TOP 51 Field1, Field2, ...
into #t
FROM MyTable WHERE ... --same expensive where clause
if ##rowcount > 50
SET #MoreExists = 1
SET NOCOUNT OFF
SELECT TOP 50 Field1, Field2, ...
from #t
-- maintain ordering with an order by clause
Yes.
The common approach is to use ROW_NUMBER():
WITH MyTableEntries AS
(
SELECT ROW_NUMBER() OVER (ORDER BY Date DESC) AS Row, col1, col2
FROM MyTable
WHERE
-- some expensive WHERE clause
)
SELECT col1, col2
FROM MyTableEntries
WHERE Row BETWEEN(#PageIndex - 1) * #PageSize + 1 and #PageIndex*#PageSize
The efficient approach shown at this SqlServercentral article:
DECLARE #startRow INT ; SET #startrow = 50
;WITH cols
AS
(
SELECT table_name, column_name,
ROW_NUMBER() OVER(ORDER BY table_name, column_name) AS seq,
ROW_NUMBER() OVER(ORDER BY table_name DESC, column_name desc) AS totrows
FROM [INFORMATION_SCHEMA].columns
)
SELECT table_name, column_name, totrows + seq -1 as TotRows
FROM cols
WHERE seq BETWEEN #startRow AND #startRow + 49
ORDERBY seq
How about using COUNT(*) OVER... in a sub-query?
DECLARE #ReqCount int;
SET #ReqCount = 50;
SELECT TOP (#ReqCount) *
FROM
(
SELECT *, Count(*) OVER() AS TotalCnt
FROM MyTable WHERE ...
) t
ORDER BY ...
;
And if you want to use ROW_NUMBER() too, then try:
SELECT *
FROM
(
SELECT *, ROW_NUMBER() OVER (ORDER BY ...) AS RowNum, Count(*) OVER() AS TotalCnt
FROM MyTable WHERE ...
) t
WHERE RowNum BETWEEN #StartRange AND #EndRange
ORDER BY ...
;
And then you can easily check to see if TotalCnt > #ReqCount (or #EndRange), to be able to see if there are more to fetch.
Rob