Select Table records in given order - Oracle - sql

I have table lets say - Students, with 5 records and id(s) are 1 to 5, now i want to select the records - in a way that result should come like given sorting order of id column
id column should be resulted - 5,2,1,3,4 ( order may vary each time)
is there any other way to do this in oracle sql?
In mysql we have FIELD() clause to do this. I want to achieve this in oracle.

A quick and dirty way uses instr():
order by instr(',5,2,1,3,4,', ',' || id ',')
To handle values not in the string, you can convert them to NULL and sort those accordingly:
order by nullif(instr(',5,2,1,3,4,', ',' || id ','), 0) nulls last

Oracle uses heap-oraganized table, which means by default rows are stored in no particular order. However, while selecting the rows, you could mention ORDER BY clause explicitly to return the result set in ASC(default)/DESC order.
You could use DECODE/CASE to customize the sorting, for example:
with data as(
select level num from dual connect by level <=5
)
select num
from data
order by case
when num = 5 then 1
when num = 2 then 2
when num = 1 then 3
when num = 3 then 4
when num = 4 then 5
end;
NUM
----------
5
2
1
3
4

Related

Keyset pagination with composite key

I am using oracle 12c database and I have a table with the following structure:
Id NUMBER
SeqNo NUMBER
Val NUMBER
Valid VARCHAR2
A composite primary key is created with the field Id and SeqNo.
I would like to fetch the data with Valid = 'Y' and apply ketset pagination with a page size of 3. Assume I have the following data:
Id SeqNo Val Valid
1 1 10 Y
1 2 20 N
1 3 30 Y
1 4 40 Y
1 5 50 Y
2 1 100 Y
2 2 200 Y
Expected result:
----------------------------
Page 1
----------------------------
Id SeqNo Val Valid
1 1 10 Y
1 3 30 Y
1 4 40 Y
----------------------------
Page 2
----------------------------
Id SeqNo Val Valid
1 5 50 Y
2 1 100 Y
2 2 200 Y
Offset pagination can be done like this:
SELECT * FROM table ORDER BY Id, SeqNo OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY;
However, in the actual db it has more than 5 millions of records and using OFFSET is going to slow down the query a lot. Therefore, I am looking for a ketset pagination approach (skip records using some unique fields instead of OFFSET)
Since a composite primary key is used, I need to offset the page with information from more than 1 field.
This is a sample SQL that should work in PostgreSQL (fetch 2nd page):
SELECT * FROM table WHERE (Id, SeqNo) > (1, 4) AND Valid = 'Y' ORDER BY Id, SeqNo LIMIT 3;
How do I achieve the same in oracle?
Use row_number() analytic function with ceil arithmetic fuction. Arithmetic functions don't have a negative impact on performance, and row_number() over (order by ...) expression automatically orders the data without considering the insertion order, and without adding an extra order by clause for the main query. So, consider :
select Id,SeqNo,
ceil(row_number() over (order by Id,SeqNo)/3) as page
from tab
where Valid = 'Y';
P.S. It also works for Oracle 11g, while OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY works only for Oracle 12c.
Demo
You can use order by and then fetch rows using fetch and offset like following:
Select ID, SEQ, VAL, VALID FROM TABLE
WHERE VALID = 'Y'
ORDER BY ID, SEQ
--FETCH FIRST 3 ROWS ONLY -- first page
--OFFSET 3 ROWS FETCH NEXT 3 ROWS ONLY -- second pages
--OFFSET 6 ROWS FETCH NEXT 3 ROWS ONLY -- third page
--Update--
You can use row_number analytical function as following.
Select id, seqNo, Val, valid from
(Select t.*,
Row_number(order by id, seq) as rn from table t
Where valid = 'Y')
Where ceil(rn/3) = 2 -- for page no. 2
Cheers!!

Sort by one column, but get offset by another

Let's say I have a table with two columns:
| ID | A |
I want to sort by A, then get the 10 records after a given ID. What would be the best way to handle this in Postgres?
To clarify, I want to sort by A, but do my pagination by the ID. So if I had a table like:
1 | 'C'
2 | 'B'
3 | 'A'
4 | 'G'
5 | 'A'
6 | 'H'
So after sorting by A, I'd want the first three values after id=1, so:
1 | 'C'
4 | 'G'
6 | 'H'
An ordering of any column is purely dependant on the order by clause and the terms "before" or "after" come into picture only when there's a pre-determined order. So, once the records are ordered by column "A", there's no guarantee that the id's will be ordered in the sequence 1,4,6, unless you also specified that ordering of id.
So, if you
want the first three values after id=1
It means there should be a way to determine the point where the id value has become 1 and all the rows beyond are to be considered. To ensure that you have to explicitly include id in the order by. A COUNT analytic function can come to our rescue to mark the point.
SELECT id,a
FROM ( SELECT t.*,COUNT(CASE WHEN id = 1 THEN 1 END) --the :id argument
OVER( ORDER BY a,id) se
FROM t order by a,id --the rows are ordered first by a, then by id
-- same as in the above count analytic function
) s
WHERE se = 1 limit 3; -- the argument 3 or 10 that you wish to pass
-- se = 1 won't change for other ids, it's a marker
-- that id = n is reached
DEMO
I think this will do:
SELECT *
FROM (SELECT * from MyTable where ID > givenId order by A) sub
LIMIT 10;
You don't want the A columns, so:
SELECT r.*
FROM t
WHERE t.id > ANY (SELET id FROM t t2 WHERE t2.col = 'A')
ORDER BY col
LIMIT 10;
Note that this does not return any rows with A as the value. It also works when the comparison value is not sorted first.
this will work:
SELECT * from Table1 where "ID"=1
order by "A" desc limit 2;
check :http://sqlfiddle.com/#!15/5854b/3
for your query :
SELECT * from Table1 where "ID"=1
order by "A" desc limit 10;

columns to rows change in oracle sql

I have columns a,b in table x.And i want to change this columns data into rows.
it is possible to have duplicate vales in table but in columns to row change only distinct values should come.
E.G:
a b
1 2
1 11
3 4
5 6
7 8
9 10
......etc
the result 1 (query 1) should be 1-2,1-11,3-4,5-6,7-8,9-10.....etc
The result 2 (query 2) should b 1,3,5,7,9....etc(only one 1 must come as we have duplicate data for column a)
how can i achieve this in oracle SQL.
Please help.
For Oracle 11 use function listagg() and in first query concatenate columns, in second - select distinct values at first.
Query 1:
select listagg(a||'-'||b, ',') within group (order by a, b) result from t
RESULT
------------------------------
1-2,1-11,3-4,5-6,7-8,9-10
Query 2:
select listagg(a, ',') within group (order by a) result
from (select distinct a from t)
RESULT
------------------------------
1,3,5,7,9
For older versions you can use wmsys.wm_concat.

SQL: Limit by unknown number of occurences

Having a SQL table, consistent of the columns id and type. I Want to select only the first occurences of a type without using WHERE, since i dont know which types wild occur first, and without LIMIT since i don't know how many.
id | type
---------
1 | 1
2 | 1
3 | 2
4 | 2
5 | 2
E.g.:
SELECT id FROM table ORDER BY type (+ ?) should only return id 1 and 2
SELECT id FROM table ORDER BY type DESC (+ ?) should only return id 3, 4 and 5
Can this be acheived via standard and simple SQL operators?
That's easy. You must use a where clause and evaluate the minimum type there.
SELECT *
FROM mytable
WHERE type = (select min(type) from mytable)
ORDER BY id;
EDIT: Do the same with max() if you want to get the maximum type records.
EDIT: In case the types are not ascending as in your example, you will have to get the type of the minimum/maximum id instead of getting the minimum/maximum type:
SELECT *
FROM mytable
WHERE type = (select type from mytable where id = (select min(id) from mytable))
ORDER BY id;

SQL: create sequential list of numbers from various starting points

I'm stuck on this SQL problem.
I have a column that is a list of starting points (prevdoc), and anther column that lists how many sequential numbers I need after the starting point (exdiff).
For example, here are the first several rows:
prevdoc | exdiff
----------------
1 | 3
21 | 2
126 | 2
So I need an output to look something like:
2
3
4
22
23
127
128
I'm lost as to where even to start. Can anyone advise me on the SQL code for this solution?
Thanks!
;with a as
(
select prevdoc + 1 col, exdiff
from <table> where exdiff > 0
union all
select col + 1, exdiff - 1
from a
where exdiff > 1
)
select col
If your exdiff is going to be a small number, you can make up a virtual table of numbers using SELECT..UNION ALL as shown here and join to it:
select prevdoc+number
from doc
join (select 1 number union all
select 2 union all
select 3 union all
select 4 union all
select 5) x on x.number <= doc.exdiff
order by 1;
I have provided for 5 but you can expand as required. You haven't specified your DBMS, but in each one there will be a source of sequential numbers, for example in SQL Server, you could use:
select prevdoc+number
from doc
join master..spt_values v on
v.number <= doc.exdiff and
v.number >= 1 and
v.type = 'p'
order by 1;
The master..spt_values table contains numbers between 0-2047 (when filtered by type='p').
If the numbers are not too large, then you can use the following trick in most databases:
select t.exdiff + seqnum
from t join
(select row_number() over (order by column_name) as seqnum
from INFORMATION_SCHEMA.columns
) nums
on t.exdiff <= seqnum
The use of INFORMATION_SCHEMA columns in the subquery is arbitrary. The only purpose is to generate a sequence of numbers at least as long as the maximum exdiff number.
This approach will work in any database that supports the ranking functions. Most databases have a database-specific way of generating a sequence (such as recursie CTEs in SQL Server and CONNECT BY in Oracle).