select Top 3 unexpexted result? - sql-server

This is table structure !
name varchar(10),
score float
With values:
('Alex',7),('john',5.6),('Tom',8.9),('Anonio',6),('sharti',7),('mamuzi',9)
I need o/p
name score
mamuzi 9
Tom 8.9
Alex 7
sharti 7
When i try using TOP as : select top 3 * from table order by score desc I can't get the expected results

Try using with Ties in sql server since Alex and sharti have same score
select top 3 with ties * from #t order by score desc
See here

Unless you include an ORDER BY statement, the ordering will be random when you use TOP.
Do this:
SELECT TOP 3 WITH TIES * FROM YourTable ORDER BY score DESC
Sample SQL Fiddle.
From the documentation:
When TOP is used in conjunction with the ORDER BY clause, the result
set is limited to the first N number of ordered rows; otherwise, it
returns the first N number of rows in an undefined order.
Edit: just noticed the change in your sample data. To make it work now you need to add the WITH TIES clause to TOP, as pointed out in the other answers. I've updated my answer accordingly.

You forgot ordering by score field. Modify your query as:
SELECT TOP 3 * FROM table ORDER BY score DESC
Answer for the last edit to your question: Using TOP and ORDER BY DESC worked for me but i tested with the new values you added to your question. My test was as follows:
DECLARE #table TABLE
(
name varchar(10),
score float
)
INSERT INTO #table VALUES ('Alex', 7)
INSERT INTO #table VALUES ('john',5.6)
INSERT INTO #table VALUES ('Tom',8.9)
INSERT INTO #table VALUES ('Anonio',6)
INSERT INTO #table VALUES ('sharti',7)
INSERT INTO #table VALUES ('mamuzi',9)
SELECT TOP 3 * FROM #table ORDER BY score DESC
It works since the result is as expected.
To include sharti and his score in the results since he has the same score as Alex, you will need to use WITH TIES as Vijaykumar Hadalgi suggested (More for WITH TIES).
Modify your query as: SELECT TOP(3) WITH TIES * FROM #table ORDER BY score DESC
name score
--------------
mamuzi 9
Tom 8,9
Alex 7
sharti 7

Related

Why does or doesn't COUNT() work with SELECT TOP in MS SQL Serveer?

I have just started learning MS SQL Server and came across a problem to find median value of LAT_N from a DB on HackerRank.
Here's the problem.
Here's the DB schema if aren't able to view the above link without logging in.
Here's my code.
SELECT
CASE
WHEN COUNT(LAT_N)%2=0 THEN TOP COUNT(LAT_N)/2 LAT_N
ELSE TOP COUNT(LAT_N)/2+1 LAT_N
END as col1
INTO T1
FROM STATION
ORDER BY LAT_N ASC;
SELECT TOP COUNT(LAT_N)/2 LAT_N AS col2
INTO T2
FROM STATION
ORDER BY LAT_N DESC;
SELECT TOP 1 col1
FROM T1
ORDER BY LAT_N DESC;
SELECT TOP 1 col2
FROM T2
ORDER BY LAT_N ASC;
And I got here's the error:
Msg 156, Level 15, State 1, Server dbrank-tsql, Line 4
Incorrect syntax near the keyword 'TOP'.
So I decided to try it on a small database on W3schools.
Here's the editor.
This query works:
SELECT TOP 3 *
FROM Customers;
But this doesn't:
SELECT TOP COUNT(*)/2 *
FROM Customers;
Can someone please explain:
Why it doesn't work and
How to make TOP COUNT(*)/2 work.
Your SQL doesn't make sense. TOP needs a scalar value after it, and if it's an expression, it need to be in parenthesis (()). SELECT TOP COUNT(*)/2 * doesn't make sense, as you want to TOP what? Then you have COUNT(*)/2 * What are you then multiplying that value by? You're missing part of the expression.
The same is true for SELECT TOP COUNT(LAT_N)/2 LAT_N AS col2. The TOP what? You're missing a value for your TOP and then go straight to asking for a COUNT of the non-NULL values of LAT_N divided by 2. It then appears your missing a comma, but considering you are returning a COUNT of LAT_N it would need to be in your GROUP BY so it is unclear what your goal is there.
If you after the top "half" of the table, (from your comment "How to make TOP COUNT()/2 work."*) use TOP(50 PERCENT):
SELECT TOP (50 PERCENT)
*
FROM dbo.CUSTOMERS
ORDER BY ???;
SELECT TOP ((SELECT COUNT(*) FROM CUSTOMERS) / 2) *
FROM CUSTOMERS
Like Larnu he said I think the best option is to select the TOP X PERCENT and if you've got a column, like created date for eg you can get the most recent 50% rows created:
SELECT TOP 50 PERCENT
*
FROM
CUSTOMERS
ORDER BY
CreatedDate DESC
The TOP requires two parameters:
PARAMETER 1. how many records,
PARAMETER 2. which columns you want to get.
Here is an example usage of the TOP (adapted to your question)
SELECT TOP
/* PARAMETER 1. how many records, in this case half: */
((SELECT COUNT(LAT_N) FROM [STATION]) / 2)
/* PARAMETER 2. which columns you want to get, use star for ALL: */
*
FROM [STATION];
You can also use calculations for choosing the desired number of records.
Here is an example with CASE
SELECT TOP (
/* PARAMETER 1. how many records, in this case
if total number of records in a table is even, then get top half of the rows,
otherwise, make it even, then get the top half: */
CASE
WHEN (SELECT COUNT(LAT_N) FROM [STATION]) % 2 = 0
THEN ((SELECT COUNT(LAT_N) FROM [STATION]) )
ELSE
((SELECT COUNT(LAT_N) + 1 FROM [STATION]) / 2)
END
)
/* PARAMETER 2. which columns you want to get, use star for ALL: */
*
FROM [STATION];

How to skip the max function which has only one entry when i do a group by in SQL Server

I have a requirement where I do a group by the table
Table
Name salary
------------
abc 10000
abc 1000
def 100
Query:
select max(salary)
from table
group by Name
Result:
abc 10000
def 100
I don't want 'def' to be displayed since it's a single entry in the table. How can I achieve this?
You can add a HAVING clause.
Having specifies a search condition for a group or an aggregate.
HAVING can be used only with the SELECT statement. HAVING is typically
used with a GROUP BY clause. When GROUP BY is not used, there is an
implicit single, aggregated group.
select
Name
,max(salary)
from table
group by Name having count(*) > 1
This will only return the aggregates for names that have more than 1 row, which seems to be what you want.
EXAMPLE
declare #table table (name varchar(16), salary int)
insert into #table
values
('abc',10000),
('abc',1000),
('def',100),
('xxf',100)
select
Name
,max(salary)
from #table
group by Name
having count(*) > 1

Getting Random Number for each row

I have a table with some names in a row. For each row I want to generate a random name. I wrote the following query to:
BEGIN transaction t1
Create table TestingName
(NameID int,
FirstName varchar(100),
LastName varchar(100)
)
INSERT INTO TestingName
SELECT 0,'SpongeBob','SquarePants'
UNION
SELECT 1, 'Bugs', 'Bunny'
UNION
SELECT 2, 'Homer', 'Simpson'
UNION
SELECT 3, 'Mickey', 'Mouse'
UNION
SELECT 4, 'Fred', 'Flintstone'
SELECT FirstName from TestingName
WHERE NameID = ABS(CHECKSUM(NEWID())) % 5
ROLLBACK Transaction t1
The problem is the "ABS(CHECKSUM(NEWID())) % 5" portion of this query sometime returns more than 1 row and sometimes returns 0 rows. I must be missing something but I can't see it.
If I change the query to
DECLARE #n int
set #n= ABS(CHECKSUM(NEWID())) % 5
SELECT FirstName from TestingName
WHERE NameID = #n
Then everything works and I get a random number per row.
If you take the query above and paste it into SQL management studio and run the first query a bunch of times you will see what I am attempting to describe.
The final update query will look like
Update TableWithABunchOfNames
set [FName] = (SELECT FirstName from TestingName
WHERE NameID = ABS(CHECKSUM(NEWID())) % 5)
This does not work because sometimes I get more than 1 row and sometimes I get no rows.
What am I missing?
The problem is that you are getting a different random value for each row. That is the problem. This query is probably doing a full table scan. The where clause is executed for each row -- and a different random number is generated.
So, you might get a sequence of random numbers where none of the ids match. Or a sequence where more than one matches. On average, you'll have one match, but you don't want "on average", you want a guarantee.
This is when you want rand(), which produces only one random number per query:
SELECT FirstName
from TestingName
WHERE NameID = floor(rand() * 5);
This should get you one value.
Why not use top 1?
Select top 1 firstName
From testingName
Order by newId()
This worked for me:
WITH
CTE
AS
(
SELECT
ID
,FName
,CAST(5 * (CAST(CRYPT_GEN_RANDOM(4) as int) / 4294967295.0 + 0.5) AS int) AS rr
FROM
dbo.TableWithABunchOfNames
)
,CTE_ForUpdate
AS
(
SELECT
CTE.ID
, CTE.FName
, dbo.TestingName.FirstName AS RandomName
FROM
CTE
LEFT JOIN dbo.TestingName ON dbo.TestingName.NameID = CTE.rr
)
UPDATE CTE_ForUpdate
SET FName = RandomName
;
This solution depends on how smart optimizer is.
For example, if I use INNER JOIN instead of LEFT JOIN (which is the correct choice for this query), optimizer would move calculation of random numbers outside the join loop and end result would be not what we expect.
I created a table TestingName with 5 rows as in the question and a table TableWithABunchOfNames with 100 rows.
Here is the execution plan with LEFT JOIN. You can see the Compute scalar that calculates random numbers is done before the join loop. You can see that 100 rows were updated:
Here is the execution plan with INNER JOIN. You can see the Compute scalar that calculates random numbers is done after the join loop and with extra filter. This query may update not all rows in TableWithABunchOfNames and some rows in TableWithABunchOfNames may be updated several times. You can see that Filter left 102 rows and Stream aggregate left only 69 rows. It means that only 69 rows were eventually updated and also there were multiple matches for some rows (102 - 69 = 33).
To guarantee that the result is what you expect you should generate random number for each row in TableWithABunchOfNames and explicitly remember the result, i.e. materialize the CTE shown above. Then use this temporary result to join with the table TestingName.
You can add a column to TableWithABunchOfNames to store generated random numbers or save CTE to a temp table or table variable.

selecting random rows with normal distribution based on a column in SQL Server 2012

FULL DETAILS:
let me explain more clear. this is a table including about 100 question. every question has a BooKRange property that shows from which part of the book, this question hast fetched with values 1,2,3,4. and there is another property called Level that shows level of the difficulty of the question with values 1,2,3,4,5. now i need to randomly select 20 question that have to include all four Book Ranges and all five levels with a normal distribution.
please consider that i need to select distinct rows.
thank you very much.
edit: added the table
CREATE TABLE [dbo].[Question] (
[QuesID] INT IDENTITY (1, 1) NOT NULL,
[BookRange] NVARCHAR (50) NULL,
[Level] NVARCHAR (50) NULL,
PRIMARY KEY CLUSTERED ([QuesID] ASC)
);
You can do this query (assuming a uniform distribution) without doing a union. You just need to specify the ordering correctly.
If you want to select 5 questions from each of the levels, then you can do so by assigning a sequential number to the questions in each level. If these are assigned randomly, then you should meet the requirement of randomness for the levels:
with q as (
select q.*,
row_number() over (partition by [range] order by newid()) as seqnum
from Question q
)
select *
from q
where seqnum <= 5;
If you want to ensure that these is exactly one question for each level and range, but want the questions random, then do:
with q as (
select q.*,
row_number() over (partition by [range], [level] order by newid()) as seqnum
from Question q
)
select *
from q
where seqnum = 1;
By the way, range and level are reserved words in SQL Server. In general, it is good practice to avoid using reserved words for the names of things like tables, columns, stored procedures, and so on.
Select distinct id from table where level=1 order by rand() limit 5 union Select distinct id from table where level=2 order by rand() limit 5 union Select distinct id from table where level=3 order by rand() limit 5 union Select distinct id from table where level=4 order by rand() limit 5
Since you havent provided any table schema, Assuming we have a table dbo.Number with One column with values from 1 - 30 you could do something like this ...
;With NthGroups
AS
(
SELECT * , NTILE(4) OVER (ORDER BY Nums) Np
FROM dbo.Number
),
Top25Perc
AS
(
SELECT TOP 5 * FROM NthGroups
WHERE NP = 1
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM NthGroups
WHERE NP = 2
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM NthGroups
WHERE NP = 3
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM NthGroups
WHERE NP = 4
ORDER BY NEWID()
)
SELECT * FROM Top25Perc
Update
Just read your comment in other answer and you have mentioned you have a column Range with values (1,2,3,4) , this makes query even simpler , you can do something like this
;With
RandTop5
AS
(
SELECT TOP 5 * FROM TableName
WHERE [Range] = 1
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM TableName
WHERE [Range] = 2
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM TableName
WHERE [Range] = 3
ORDER BY NEWID()
UNION ALL
SELECT TOP 5 * FROM TableName
WHERE [Range] = 4
ORDER BY NEWID()
)
SELECT * FROM RandTop5

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