Select records from n1 to n2 - sql

I have, for example, 10.000 records in my table. Every time I execute the query I get the page number n as a parameter. And I have to select records from n to n*100, which satisfy some complicated condition. I also use ORDER BY, so I can't keep the PrimaryKey of the last element and select top n records using the primary key. How can I achieve this?

Pass the value of From and To parameter in following query on basis of Page calculation.
WITH NumberedMyTable AS
(
SELECT
*,
ROW_NUMBER() OVER (ORDER BY Id) AS RowNumber
FROM
MyTable
)
SELECT
*
FROM
NumberedMyTable
WHERE
RowNumber BETWEEN #From AND #To

declare #n int
set #n=2
with my_query as(
select ROW_NUMBER() over (order by name) as ID, *
from sys.tables
)
select *
from my_query
where ID >=#n and ID<=(#n*10)

Related

Generate a random number which is not there in a table in sql server

I am looking for generating a random number which the generated number is not there on another table.
For Example: If a table called randomNums having the values 10,20,30,40,50.
I like to generate a number apart from the above values.
I tried the following query.
Query
;WITH CTE AS
(
SELECT FLOOR(RAND()*100) AS rn
)
SELECT rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);
But sometimes this query returns nothing.
Because that time it generates the number which is there in the table randomNums.
How to solve this issue?
Fiddle for reference
Yet another option, I've always liked NEWID() for random ordering, and cross joins create many rows very efficiently:
;with cte AS (SELECT 1 n UNION ALL SELECT 1)
,cte2 AS (SELECT TOP 100 ROW_NUMBER() OVER(ORDER BY a.n) n
FROM cte a,cte b,cte c,cte d, cte e, cte f, cte g)
SELECT TOP 1 n
FROM cte2 a
WHERE NOT EXISTS (SELECT 1
FROM randomNums b
WHERE a.n = b.num)
ORDER BY NEWID()
Demo: SQL Fiddle
If you don't want to use a WHILE loop then you might look into this solution which employs a recursive CTE:
;WITH CTE AS
(
SELECT FLOOR(RAND()*100) AS rn
UNION ALL
SELECT s.rn
FROM (
SELECT rn
FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums)
) t
CROSS JOIN (SELECT FLOOR(RAND()*100) AS rn) AS s
WHERE t.rn IS NULL
)
SELECT rn
FROM CTE
EDIT:
As stated in comments below the above does not work: If the first generated number (from the CTE anchor member) is a number already present in randomNums, then the CROSS JOIN of the recursive member will return NULL, hence the number from the anchor member will be returned.
Here is a different version, based on the same idea of using a recursive CTE, that works:
DECLARE #maxAttempts INT = 100
;WITH CTE AS
(
SELECT FLOOR(RAND()*100) AS rn,
1 AS i
UNION ALL
SELECT FLOOR(RAND(CHECKSUM(NEWID()))*100) AS rn, i = i + 1
FROM CTE AS c
INNER JOIN randomNums AS r ON c.rn = r.num
WHERE (i = i) AND (i < #maxAttempts)
)
SELECT TOP 1 rn
FROM CTE
ORDER BY i DESC
Here, the anchor member of the CTE firstly generates a random number. If this number is already present in randomNums the INNER JOIN of the recursive member will succeed, hence yet another random number will be generated. Otherwise, the INNER JOIN will fail and the recursion will terminate.
A couple of things more to note:
i variable is used to record the number of attempts made to generate a 'unique' random number.
The value of i is used in the INNER JOIN operation of the recursive member so as to join with the random value of the immediately preceding recursion only.
Since repetitive calls of RAND() with the same seed value return the same results, we have to use CHECKSUM(NEWID()) as the seed of RAND().
#maxAttempts can optionally be used to specify the maximum number of attempts made in order to generate a 'unique' random number.
SQL Fiddle Demo here
Another option could be to create an unique index on num value for table randomNums. Then in your code catch the possible error if duplicated key is generated, and in that case choose another number and re-try.
Query
declare #RandomNums table (Num int);
insert into #RandomNums values (10),(20),(30),(40),(50),(60),(70),(80),(90);
-- Make a table of AvailableNumbers
with N as
(
select n from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) t(n)
),
AvailableNumbers as
(
select -- top 97 -- limit as you need
row_number() over(order by (select 1)) as Number
from
N n1, N n2 --, N n3, N n4, N n5, N n6 -- multiply as you need
),
-- Find which of AvailableNumbers is Vacant
VacantNumbers as
(
select
OrdinalNumber = row_number() over(order by an.Number) ,
an.Number
from
AvailableNumbers an
left join #RandomNums rn on rn.Num = an.number
where
rn.Num is null
)
-- select rundom VacantNumber by its OrdinalNumber in VacantNumbers
select
Number
from
VacantNumbers
where
OrdinalNumber = floor(rand()*(select count(*) from VacantNumbers) + 1);
Try
declare #n as int
while #n is null and (select COUNT(*) from randomNums) < 100
Begin
;WITH CTE AS
(
SELECT FLOOR(RAND()*100) AS rn
)
SELECT #n = rn FROM CTE
WHERE rn NOT IN (SELECT num FROM randomNums);
End
select #n
It would only be advisable to use this approach, if the number of exclusions is relatively small.

Find the lowest non-contiguous value

I was asked to write a T-SQL statement that will find the lowest unused value of MyId in the sequence below (i.e. in this case the result should be 3):
DECLARE #MyTable TABLE (MyId INT);
INSERT INTO #MyTable(MyId) VALUES(1),(2),(4),(5);
;With CTE
AS
(
SELECT * , ROW_NUMBER() OVER (ORDER BY MyID ASC) AS RN
FROM #MyTable
)
SELECT TOP 1 rn
FROM CTE
WHERE Rn <> MyId
ORDER BY MyId ASC
Here is how I would probably answer that question (though it is impossible for anyone here to know exactly what the interviewer was after).
First, you can easily generate a sequence of contiguous numbers from existing tables or views in any SQL Server system. For this, let's use master..spt_values (which will cover a sequence of about 2000 values, depending on version):
SELECT TOP (5) n = number + 1
FROM master.dbo.spt_values
WHERE type = N'P'
ORDER BY number;
Results:
n
------
1
2
3
4
5
Now, you don't know in advance that you need 5, so you can determine the number you need by taking the min and max from the table:
DECLARE #min INT, #max INT;
SELECT #min = MIN(MyId), #max = MAX(MyId) FROM #MyTable;
Now you can get the exact set you need (since it may not always start at 1):
SELECT TOP (#max-#min+1) number
FROM master.dbo.spt_values
WHERE number >= #min AND type = N'P'
ORDER BY number;
Now, finally, we can perform a left anti-semi-join to find the first value that exists in our contiguous set but not in the table:
;WITH x AS
(
SELECT TOP (#max-#min+1) number
FROM master.dbo.spt_values
WHERE number >= #min AND type = N'P'
ORDER BY number
)
SELECT MIN(number) FROM x
WHERE NOT EXISTS
(SELECT 1 FROM #MyTable WHERE MyId = x.number);
If you need more than 2000 values, you can use other things like sys.all_columns and if that isn't enough you can CROSS JOIN multiple tables. See http://www.sqlperformance.com/generate-a-set-1, http://www.sqlperformance.com/generate-a-set-2 and http://www.sqlperformance.com/generate-a-set-3.
Of course, if you know the sequence should always start at 1, rather than the minimum value in the table, then the other answers are slightly simpler. This caters to the case where the set doesn't necessarily start with 1, and you don't care about "missing" values that are below the minimum value.
with cte as (
select MyId, row_number over (order by MyId asc) RowId
from #MyTable
)
select top 1 c1.MyId + 1 FirstMissingMyId
from cte c1
join cte c2
on c1.RowId + 1 = c2.RowId
where c1.MyId + 1 <> c2.MyId
order by c1.MyId asc
The idea is to find first non-sequential number for a sequence that strats with (assumingly) with 1. You can do this by comparing to sequential numbers, generated by ROW_NUMBER() function
SELECT TOP(1) RN FROM
(SELECT MyID, ROW_NUMBER() OVER (ORDER BY MyId) AS RN FROM #MyTable) MT
WHERE MyID <> RN
ORDER BY MyID ASC
Demo: http://sqlfiddle.com/#!3/c1a90/2

Apply aggregate function on subset of rows

I have a table that I want to calculate the average of one column but only for the last 10 rows.
SELECT AVG(columnName) as avg FROM tableName
I cannot apply top directly since this query only returns one row. I need a way to get the latest 10 rows and do the average on them.
Try this:
SELECT AVG(columnName) FROM
(SELECT TOP 10 columnName FROM tableName ORDER BY ColumnWhichHoldsOrder DESC) A
select avg(columnName)
from (
select columnName,
row_number() over (order by some column desc) as rn
from tableName
) t
where rn <= 10;

Equivalent of LIMIT and OFFSET for SQL Server?

In PostgreSQL there is the Limit and Offset keywords which will allow very easy pagination of result sets.
What is the equivalent syntax for SQL Server?
This feature is now made easy in SQL Server 2012.
This is working from SQL Server 2012 onwards.
Limit with offset to select 11 to 20 rows in SQL Server:
SELECT email FROM emailTable
WHERE user_id=3
ORDER BY Id
OFFSET 10 ROWS
FETCH NEXT 10 ROWS ONLY;
ORDER BY: required
OFFSET: optional number of skipped rows
NEXT: required number of next rows
Reference: https://learn.microsoft.com/en-us/sql/t-sql/queries/select-order-by-clause-transact-sql
The equivalent of LIMIT is SET ROWCOUNT, but if you want generic pagination it's better to write a query like this:
;WITH Results_CTE AS
(
SELECT
Col1, Col2, ...,
ROW_NUMBER() OVER (ORDER BY SortCol1, SortCol2, ...) AS RowNum
FROM Table
WHERE <whatever>
)
SELECT *
FROM Results_CTE
WHERE RowNum >= #Offset
AND RowNum < #Offset + #Limit
The advantage here is the parameterization of the offset and limit in case you decide to change your paging options (or allow the user to do so).
Note: the #Offset parameter should use one-based indexing for this rather than the normal zero-based indexing.
select top {LIMIT HERE} * from (
select *, ROW_NUMBER() over (order by {ORDER FIELD}) as r_n_n
from {YOUR TABLES} where {OTHER OPTIONAL FILTERS}
) xx where r_n_n >={OFFSET HERE}
A note:
This solution will only work in SQL Server 2005 or above, since this was when ROW_NUMBER() was implemented.
You can use ROW_NUMBER in a Common Table Expression to achieve this.
;WITH My_CTE AS
(
SELECT
col1,
col2,
ROW_NUMBER() OVER(ORDER BY col1) AS row_number
FROM
My_Table
WHERE
<<<whatever>>>
)
SELECT
col1,
col2
FROM
My_CTE
WHERE
row_number BETWEEN #start_row AND #end_row
Specifically for SQL-SERVER you can achieve that in many different ways.For given real example we took Customer table here.
Example 1: With "SET ROWCOUNT"
SET ROWCOUNT 10
SELECT CustomerID, CompanyName from Customers
ORDER BY CompanyName
To return all rows, set ROWCOUNT to 0
SET ROWCOUNT 0
SELECT CustomerID, CompanyName from Customers
ORDER BY CompanyName
Example 2: With "ROW_NUMBER and OVER"
With Cust AS
( SELECT CustomerID, CompanyName,
ROW_NUMBER() OVER (order by CompanyName) as RowNumber
FROM Customers )
select *
from Cust
Where RowNumber Between 0 and 10
Example 3 : With "OFFSET and FETCH", But with this "ORDER BY" is mandatory
SELECT CustomerID, CompanyName FROM Customers
ORDER BY CompanyName
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
Hope this helps you.
-- #RowsPerPage can be a fixed number and #PageNumber number can be passed
DECLARE #RowsPerPage INT = 10, #PageNumber INT = 2
SELECT *
FROM MemberEmployeeData
ORDER BY EmployeeNumber
OFFSET #PageNumber*#RowsPerPage ROWS
FETCH NEXT 10 ROWS ONLY
For me the use of OFFSET and FETCH together was slow, so I used a combination of TOP and OFFSET like this (which was faster):
SELECT TOP 20 * FROM (SELECT columname1, columname2 FROM tablename
WHERE <conditions...> ORDER BY columname1 OFFSET 100 ROWS) aliasname
Note: If you use TOP and OFFSET together in the same query like:
SELECT TOP 20 columname1, columname2 FROM tablename
WHERE <conditions...> ORDER BY columname1 OFFSET 100 ROWS
Then you get an error, so for use TOP and OFFSET together you need to separate it with a sub-query.
And if you need to use SELECT DISTINCT then the query is like:
SELECT TOP 20 FROM (SELECT DISTINCT columname1, columname2
WHERE <conditions...> ORDER BY columname1 OFFSET 100 ROWS) aliasname
Note: The use of SELECT ROW_NUMBER with DISTINCT did not work for me.
Adding a slight variation on Aaronaught's solution, I typically parametrize page number (#PageNum) and page size (#PageSize). This way each page click event just sends in the requested page number along with a configurable page size:
begin
with My_CTE as
(
SELECT col1,
ROW_NUMBER() OVER(ORDER BY col1) AS row_number
FROM
My_Table
WHERE
<<<whatever>>>
)
select * from My_CTE
WHERE RowNum BETWEEN (#PageNum - 1) * (#PageSize + 1)
AND #PageNum * #PageSize
end
Another sample :
declare #limit int
declare #offset int
set #offset = 2;
set #limit = 20;
declare #count int
declare #idxini int
declare #idxfim int
select #idxfim = #offset * #limit
select #idxini = #idxfim - (#limit-1);
WITH paging AS
(
SELECT
ROW_NUMBER() OVER (order by object_id) AS rowid, *
FROM
sys.objects
)
select *
from
(select COUNT(1) as rowqtd from paging) qtd,
paging
where
rowid between #idxini and #idxfim
order by
rowid;
There is here someone telling about this feature in sql 2011, its sad they choose a little different keyword "OFFSET / FETCH" but its not standart then ok.
The closest I could make is
select * FROM( SELECT *, ROW_NUMBER() over (ORDER BY ID ) as ct from [db].[dbo].[table] ) sub where ct > fromNumber and ct <= toNumber
Which I guess similar to select * from [db].[dbo].[table] LIMIT 0, 10
Elaborating the Somnath-Muluk's answer just use:
SELECT *
FROM table_name_here
ORDER BY (SELECT NULL AS NOORDER)
OFFSET 9 ROWS
FETCH NEXT 25 ROWS ONLY
w/o adding any extra column.
Tested in SQL Server 2019, but I guess could work in older ones as well.
select top (#TakeCount) * --FETCH NEXT
from(
Select ROW_NUMBER() OVER (order by StartDate) AS rowid,*
From YourTable
)A
where Rowid>#SkipCount --OFFSET
#nombre_row :nombre ligne par page
#page:numero de la page
//--------------code sql---------------
declare #page int,#nombre_row int;
set #page='2';
set #nombre_row=5;
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY etudiant_ID ) AS RowNum, *
FROM etudiant
) AS RowConstrainedResult
WHERE RowNum >= ((#page-1)*#nombre_row)+1
AND RowNum < ((#page)*#nombre_row)+1
ORDER BY RowNum
Since nobody provided this code yet:
SELECT TOP #limit f1, f2, f3...
FROM t1
WHERE c1 = v1, c2 > v2...
AND
t1.id NOT IN
(SELECT TOP #offset id
FROM t1
WHERE c1 = v1, c2 > v2...
ORDER BY o1, o2...)
ORDER BY o1, o2...
Important points:
ORDER BY must be identical
#limit can be replaced with number of results to retrieve,
#offset is number of results to skip
Please compare performance with previous solutions as they may be more efficient
this solution duplicates where and order by clauses, and will provide incorrect results if they are out of sync
on the other hand order by is there explicitly if that's what's needed
I assume that, In C# Expression/LINQ statement of skip and take generating below SQL Command
DECLARE #p0 Int = 1
DECLARE #p1 Int = 3
SELECT [t1].[Id]
FROM (
SELECT ROW_NUMBER() OVER (ORDER BY [t0].[Id]
FROM [ShoppingCart] AS [t0]
) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN #p0 + 1 AND #p0 + #p1
ORDER BY [t1].[ROW_NUMBER]
In SQL server you would use TOP together with ROW_NUMBER()
Since, I test more times this script more useful by 1 million records each page 100 records with pagination work faster my PC execute this script 0 sec while compare with mysql have own limit and offset about 4.5 sec to get the result.
Someone may miss understanding Row_Number() always sort by specific field. In case we need to define only row in sequence should use:
ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
SELECT TOP {LIMIT} * FROM (
SELECT TOP {LIMIT} + {OFFSET} ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS ROW_NO,*
FROM {TABLE_NAME}
) XX WHERE ROW_NO > {OFFSET}
Explain:
{LIMIT}: Number of records for each page
{OFFSET}: Number of skip records

SQL question: how to select arbitrary numbers of records in each record group?

I have a table 'articles' : id / uid / last_update / content.
I want to construct a sql statement that selects an arbitrary number(say 3) of records of each user(identified by uid) that are most recently updated from the table. How can I do that?
In SQL Server and Oracle, you can use ROW_NUMBER() to label records per user. The following query tags rn=1 on the latest row for that user, rn=2 on the second latest, and so on:
select *
from (
select
row_number() over (partition by uid order by UpdateDt desc)
as rn
, *
from YourTable
) sub
where rn <= 3
The subquery is required because you can't use ROW_NUMBER() in a WHERE clause directly.
If you're using MySQL, this problem is much harder. Here's a link to a solution with user variables.
DECLARE #Top tinyint;
SELECT #Top = ABS(CHECKSUM(NEWID())) % 5 + 1;
;WITH MyCTE AS
(
SELECT
stuff, things,
ROW_NUMBER() OVER (PARTITION BY uid ORDER BY UpdatedDateTime DESC) AS Ranking
FROM
MyTable
)
SELECT
stuff, things
FROM
MyCTE
WHERE
Ranking <= #Top