Sql Select top 2 , bottom 2 and 6 random records - sql

How to select top 2 , bottom 2 and 6 random (not in Top 2 and Bottom 2) records of the table using one SQL select query?

In MS SQL 2005/2008:
with cte
as
(
select
row_number() over (order by name) RowNumber,
row_number() over (order by newid()) RandomOrder,
count(*) over() Total,
*
from sys.tables
)
select *
from cte
where RowNumber <= 2 or Total - RowNumber + 1 <= 2
union all
select *
from
(
select top 6 *
from cte
where RowNumber > 2 and Total - RowNumber > 2
order by RandomOrder
) tt
Replace sys.tables with your table name and alter order by name to specify order condition for top 2 and bottom 2.

Perhaps not a single select statment, but it can be executed in one call:
/* Top 2 - change order by to get the 'proper' top 2 */
SELECT * from table ORDER BY id DESC LIMIT 2
UNION ALL
/* Random 6.. You may want to add a WHERE and random data to get the random 6 */
/* Old Statement before edit - SELECT * from table LIMIT 6 */
SELECT * from table t
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS top ON top.id = t.id
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS bottom ON bottom.id = t.id
WHERE ISNULL(top.id ) AND ISNULL(bottom.id)
ORDER BY RANDOM()
LIMIT 6
UNION ALL
/* Bottom 2 - change order by to get the 'proper' bottom 2 */
SELECT * from table ORDER BY id ASC LIMIT 2
Something along those lines. Basically the UNION All is the trick.

Assuming the "order" is by the id column:
select * from (select id, id from my_table order by id limit 2) t1
union
select * from (select id, id from my_table where id not in (
select * from (select id from my_table order by id asc limit 2) t22
union
select * from (select id from my_table order by id desc limit 2 ) t23)
order by rand()
limit 6) t2
union
select * from (select id, id from my_table order by id desc limit 2) t3
EDIT: Fixed syntax and tested query - it works

Related

Merge three tables in Select query by rule 3, 2, 1 records from each table

Merge three tables in a Select query by rule 3, 2, 1 records from each table as follows:
TableA: ID, FieldA, FieldB, FieldC,....
TableB: ID, FieldA, FieldB, FieldC,....
TableC: ID, FieldA, FieldB, FieldC,....
ID : auto number in each table
FieldA will be unique in all three tables.
I am looking for a Select query to merge three tables as follows:
TOP three records from TableA sorted by ID
TOP two records from TableB sorted by ID
TOP 1 record from TableC sorted by ID
Repeat this until select all records from all three tables.
If some table has fewer records or does not meet the criteria, ignore that and continue with others.
My attempt:
I did it totally through programming way, like cursors and If conditions inside a SQL Server stored procedure.
It makes delay.
This requires a formula that takes row numbers from each table and transforms it into a series of integers that skips the desired values.
In the query below, I am adding some CTE for the sake of shortening the formula. The real magic is in the UNION. Also, I am adding an additional field for your control. Feel free to get rid of it.
WITH A_Aux as (
SELECT 'A' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableA.*
FROM TableA
), B_Aux AS (
SELECT 'B' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableB.*
FROM TableB
), C_Aux AS (
SELECT 'C' As FromTable, ROW_NUMBER() OVER (Order BY ID) AS RowNum, TableC.*
FROM TableC
)
SELECT *
FROM (
SELECT RowNum+3*FLOOR((RowNum-1)/3) As ColumnForOrder, A_Aux.* FROM A_Aux
UNION ALL
SELECT 3+RowNum+4*FLOOR((RowNum-1)/2), B_Aux.* FROM B_Aux
UNION ALL
SELECT 6*RowNum, C_Aux.* FROM C_Aux
) T
ORDER BY ColumnForOrder
PS: note the pattern Offset + RowNum + (6-N) * Floor((RowNum-1)/N) to group N records together (it of course simplifies a lot for TableC).
PPS: I don't have a SQL server at hand to test it. Let me know if there is a syntax error.
You may try this..
GO
select * into #temp1 from (select * from table1) as t1
select * into #temp2 from (select * from table2) as t2
select * into #temp3 from (select * from table3) as t3
select * into #final from (select col1, col2, col3 from #temp1 where 1=0) as tb
declare #i int
set #i=1
while( (select COUNT(*) from #temp1)>#i)
Begin
;with ct1 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp1
),ct2 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp2
),ct3 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp3
),cfinal as (
select top 3 * from #temp1
union all
select top 2 * from #temp2
union all
select top 1 * from #temp3
)
insert into #final ( col1 , col2, col3 )
select col1, col2, col3 from cfinal
delete from #temp1 where id in (select top 3 ID from #temp1)
delete from #temp2 where id in (select top 2 ID from #temp2)
delete from #temp3 where id in (select top 1 ID from #temp3)
set #i = #i+1
End
Select * from #final
Drop table #temp1
Drop table #temp2
Drop table #temp3
GO
First create temp table for all 3 tables with each insert delete the inserted record and this will result you the desired result, if nothing is missing from my side.
Please see to this if this works.
There is not a lot of information to go with here, but I assume you can use UNION to combine multiple statements.
SELECT * TableA ORDER BY ID DESC OFFSET 3 ROWS
UNION
SELECT * TableB ORDER BY ID DESC OFFSET 2 ROWS
UNION
SELECT * TableC ORDER BY ID DESC OFFSET 1 ROWS
Execute and see if this works.
/AF
From my understanding, I create three temp tables as ta, tb, tc.
select * into #ta from (
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
) a
select * into #tb from (
select 'B' b
union all
select 'B'
union all
select 'B'
union all
select 'B'
union all
select 'B'
) b
select * into #tc from (
select 'C' c
union all
select 'C'
union all
select 'C'
union all
select 'C'
union all
select 'C'
) c
If tables match you tables, then the output looks like A,A,A,B,B,C,A,A,A,B,B,C,A,B,C,C,C
T-SQL
declare #TAC int = (select count (*) from #ta) -- Table A Count = 7
declare #TBC int = (select count (*) from #tb) -- Table B Count = 5
declare #TAR int = #TAC % 3 -- Table A Reminder = 1
declare #TBR int = #TBC % 2 -- Table B Reminder = 1
declare #TAQ int = (#TAC - #TAR) / 3 -- Table A Quotient = (7 - 1) / 3 = 2, is will passed on NTILE
-- So we gonna split as two group (111), (222)
declare #TBQ int = (#TBC - #TBR) / 2 -- Table B Quotient = (5 - 1) / 2 = 2, is will passed on NTILE
-- So we gonna split as two group (11), (22)
select * from (
select *, NTILE (#TAQ) over ( order by a) FirstOrder, 1 SecondOrder from (
select top (#TAC - #TAR) * from #ta order by a
) ta -- 6 rows are obtained out of 7.
union all
select *, #TAQ + 1, 1 from (
select top (#TAR) * from #ta order by a desc
) ta -- Remaining one row is obtained. Order by desc is must
-- Here FirstOrder is next value of previous value.
union all
select *, NTILE (#TBQ) over ( order by b), 2 from (
select top (#TBC - #TBR) * from #tb order by b
) tb
union all
select *, #TBQ + 1, 2 from (
select top (#TBR) * from #tb order by b desc
) tb
union all
select *, ROW_NUMBER () over (order by c), 3 from #tc
) abc order by FirstOrder, SecondOrder
Let me explain the T-SQL:
Before that, FYR: NTILE and Row Number
Get the count.
Find the Quotient which will pass to NTILE function.
Order by the NTILE value and static.
Note:
I am using SQL Server 2017.
If T-SQL works fine, then you need to change the column in order by <yourcolumn>.

Max with other fields in SQL Server

Here is my query :
SELECT id, id_gamut, position from operation
It returns :
N°1) id id_gamut position
--------------------------
1 19 1
2 19 2
3 19 3
4 25 1
5 25 2
6 12 1
I need to group it by id_gamut with the max position to get a result like this :
N°2) id id_gamut position
--------------------------
3 19 3
5 25 2
6 12 1
Then i tried something like this :
SELECT gamut, Max(position) from(
SELECT id, gamut, position from operation) as req
GROUP BY gamut
It works, but my problem is i really need to have the field 'id' in my query, but if i add it like this :
SELECT id, gamut, Max(position) from(
SELECT id, gamut, position from operation) as req
GROUP BY gamut,id
My group by is broken and i have a result like result N°1
How can i group by id_gamut with max position and having the 'id' field too ?
using top with ties with row_number()
select top 1 with ties
id
, id_gamut
, position
from operation
order by row_number() over (
partition by id_gamut
order by position desc
)
Or using a common table expression with row_number()
;with cte as (
select *
, rn = row_number() over (
partition by id_gamut
order by position desc
)
from operation
)
select
id
, id_gamut
, position
from cte
where rn = 1
Or as a subquery without the cte
select
id
, id_gamut
, position
from (
select *
, rn = row_number() over (
partition by id_gamut
order by position desc
)
from operation
) s
where rn = 1
Or with cross apply()
select distinct
x.id
, o.id_gamut
, x.position
from operation o
cross apply (
select top 1
id
, position
from operation i
where i.id_gamut = o.id_gamut
order by position desc
) x
Or with not exists() (This will return more than 1 row per id_gamut if there is more than one row with the same max position)
select *
from operation o
where not exists (
select 1
from operation i
where i.id_gamut = o.id_gamut
and i.position > o.position
)
Or with not exists() (Additional clause to return highest id in the event of multiple rows with the same max position)
select *
from operation o
where not exists (
select 1
from operation i
where i.id_gamut = o.id_gamut
and (i.position > o.position
or (i.position = o.position and i.id > o.id)
)
)
rextester demo: http://rextester.com/INV77202
Using ROW_NUMBER() inside Common Table Expression
WITH CTE AS
(
SELECT id, id_gamut, position
, ROW_NUMBER() OVER (PARTITION BY id_gamut ORDER BY position DESC) AS Rn
FROM operation
)
SELECT * FROM CTE WHERE Rn = 1
Use MAX function and GROUP BY clause :
SELECT *
FROM table1
JOIN
(
SELECT id_gamut,MAX(position) position
GROUP BY id_gamut
) A ON A.id_gamut = table1.id_gamut AND A.position = table1.position

SQL Server need top 1 record for one column and all other records

I need to have one query for to select all records of KitchenBlinkSound='Y' plus top 1 of KitchenBlinkSound='N', here is my table structure.
And my requested result will be OrderNo 225,226,227. Basically it contains all KitchenBlinkSound='Y' records plus top 1 of KitchenBlinkSound='N' record.
SELECT * FROM TABLE WHERE KitchenBlinkSound='Y'
UNION ALL
SELECT TOP 1 * FROM TABLE WHERE KitchenBlinkSound='N'
ORDER BY ORDERNO
UPDATE
SELECT TOP 3 * FROM
(
SELECT * FROM #TABLE1 WHERE KitchenBlinkSound='Y'
UNION ALL
SELECT ORDERNO,KITCHENSTATUS,KitchenBlinkSound FROM
(
SELECT *,
ROW_NUMBER() OVER(PARTITION BY KitchenBlinkSound ORDER BY ORDERNO ASC) RNO
FROM #TABLE1 WHERE KitchenBlinkSound='N'
)TAB
WHERE TAB.RNO=1
)MAIN
As sub-queries doesn't allow ORDER BY, ROW_NUMBER() is added in order to make sure that you are selecting the TOP 1 ORDERNO in ascending order. If you want to take in descending order change ASC to DESC.
Select * from [your table name] where KitchenBlickSound = 'Y'
UNION
Select TOP 1 * from [your table name] where KitchenBlickSound = 'N'

How do I intermerge multiple SELECT results?

First of all, I'm not familiar with SQL in depth, so this may be a beginner question.
I know how to select data ordered by Id: SELECT * FROM foo ORDER BY id LIMIT 100 as well as how to select a random subset: SELECT * FROM foo ORDER BY RAND() LIMIT 100.
I'd like to merge these two queries into 1 in a zip manner, choosing limit/2 from each (i.e. 50). For example:
0
85
1
35
2
38
3
19
4
...
I would like to avoid duplicates. The easiest way is probably to just add a WHERE id > 100/2 to the part of the query that retrieves randomly ordered rows.
Additional info: It is unknown how many rows exist.
To get the "zip-manner" merge add a generated rownumber to each query and use an union with order by rownnumber.
Use even numbers for one and odd numbers for the other query.
Try this for MySQL
SELECT
#rownum0:=#rownum0+2 rn,
f.*
FROM ( SELECT * FROM foo ORDER BY id ) f, (SELECT #rownum0:=0) r
UNION
SELECT #rownum1:=#rownum1+2 rn,
b.*
FROM ( SELECT * FROM bar ORDER BY RAND() ) b, (SELECT #rownum1:=-1) r
ORDER BY rn
LIMIT 100
This should be self-explicative but doesnt remove duplicates:
select #rownum:=#rownum+1 as rownum,
(#rownum-1) % 50 as sortc, u.id
from (
(select id from player order by id limit 50)
union all
(select id from player order by rand() limit 50)) u,
(select #rownum:=0) r
order by sortc,rownum;
If you replace "union all" with "union", you remove duplicates but get less rows as a consequence.
This will deal with duplicates, does not restrict random numbers in the ids > 50, and always return 100 rows:
SELECT #rownum := #rownum + 1 AS rownum,
( #rownum - 1 ) % 50 AS sortc,
u.id
FROM ((SELECT id
FROM foo
ORDER BY Rand()
LIMIT 50)
UNION
(SELECT id
FROM foo
WHERE id <= 100
ORDER BY id)) u,
(SELECT #rownum := 0) r
WHERE #rownum < 100
ORDER BY sortc,
rownum DESC
LIMIT 100;
SELECT * FROM foo ORDER BY id LIMIT 50 UNION SELECT * FROM foo ORDER BY RAND() LIMIT 50
If I understand your requirement correctly. UNION removes duplicates by itself

Select top and bottom rows

I'm using SQL Server 2005 and I'm trying to achieve something like this:
I want to get the first x rows and the last x rows in the same select statement.
SELECT TOP(5) BOTTOM(5)
Of course BOTTOM does not exist, so I need another solution. I believe there is an easy and elegant solution that I'm not getting. Doing the select again with GROUP BY DESC is not an option.
Using a union is the only thing I can think of to accomplish this
select * from (select top(5) * from logins order by USERNAME ASC) a
union
select * from (select top(5) * from logins order by USERNAME DESC) b
Check the link
SQL SERVER – How to Retrieve TOP and BOTTOM Rows Together using T-SQL
Did you try to using rownumber?
SELECT *
FROM
(SELECT *, ROW_NUMBER() OVER (Order BY columnName) as TopFive
,ROW_NUMBER() OVER (Order BY columnName Desc) as BottomFive
FROM Table
)
WHERE TopFive <=5 or BottomFive <=5
http://www.sqlservercurry.com/2009/02/select-top-n-and-bottom-n-rows-using.html
I think you've two main options:
SELECT TOP 5 ...
FROM ...
ORDER BY ... ASC
UNION
SELECT TOP 5 ...
FROM ...
ORDER BY ... DESC
Or, if you know how many items there are in the table:
SELECT ...
FROM (
SELECT ..., ROW_NUMBER() OVER (ORDER BY ... ASC) AS intRow
FROM ...
) AS T
WHERE intRow BETWEEN 1 AND 5 OR intRow BETWEEN #Number - 5 AND #Number
Is it an option for you to use a union?
E.g.
select top 5 ... order by {specify columns asc}
union
select top 5 ... order by {specify columns desc}
i guess you have to do it using subquery only
select * from table where id in (
(SELECT id ORDER BY columnName LIMIT 5) OR
(SELECT id ORDER BY columnName DESC LIMIT 5)
)
select * from table where id in (
(SELECT TOP(5) id ORDER BY columnName) OR
(SELECT TOP(5) id ORDER BY columnName DESC)
)
EDITED
select * from table where id in (
(SELECT TOP 5 id ORDER BY columnName) OR
(SELECT TOP 5 id ORDER BY columnName DESC)
)
No real difference between this and the union that I'm aware of, but technically it is a single query.
select t.*
from table t
where t.id in (select top 5 t2.id from table t2 order by MyColumn)
or
t.id in (select top 5 t2.id from table t2 order by MyColumn desc);
SELECT *
FROM (
SELECT x, rank() over (order by x asc) as rown
FROM table
) temp
where temp.rown = 1
or temp.rown = (select count(x) from table)
Then you are out - doing the select again IS the only option, unless you want to pull in the complete result set and then throwing away everything in between.
ANY sql I cna think of is the same way - for the bottom you need to know first either how many items you have (materialize everything or use count(*)) or a reverse sort order.
Sorry if that does not suit you, but at the end.... reality does not care, and I do not see any other way to do that.
I had to do this recently for a very large stored procedure; if your query is quite large, and you want to minimize the amount of queries you could declare a #tempTable, insert into that #tempTable then query from that #tempTable,
DECLARE #tempTable TABLE ( columns.. )
INSERT INTO #tempTable
VALUES ( SELECT.. your query here ..)
SELECT TOP(5) columns FROM #tempTable ORDER BY column ASC -- returns first to last
SELECT TOP(5) columns FROM #tempTable ORDER BY column DESC -- returns last to first