Variable ordering based on value Oracle SQL - sql

I have this query that are a number of positions that goes from A1 to A100, then B1 to B100, and so on.
I want to order this query so that the positions appear alphabetically in ascending order, but when it reaches the next letter, it goes descending, and keep this until the end of the table.
Here is an example.
SELECT
POSITION
FROM POSITION_TABLE
-------------------
POSITION
--------
A1
A2
...
A99
A100
B100
B99
B98
...
B2
B1
C1
C2
..
How can I do this ordering inside a query?
I tried ordering by the position using CASE, but I wanted something more generic, in case I needed to increase in size.

You can use:
SELECT position
FROM (
SELECT position,
REGEXP_SUBSTR(position, '^\D+') AS leading_string,
CASE MOD(DENSE_RANK() OVER (ORDER BY REGEXP_SUBSTR(position, '^\D+')), 2)
WHEN 1
THEN ROW_NUMBER() OVER (
PARTITION BY REGEXP_SUBSTR(position, '^\D+')
ORDER BY TO_NUMBER(REGEXP_SUBSTR(position, '\d+$')) ASC
)
ELSE ROW_NUMBER() OVER (
PARTITION BY REGEXP_SUBSTR(position, '^\D+')
ORDER BY TO_NUMBER(REGEXP_SUBSTR(position, '\d+$')) DESC
)
END AS rn
FROM position_table
)
ORDER BY
leading_string,
rn;
Which, for the sample data:
CREATE TABLE position_table (POSITION) AS
SELECT 'A' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'B' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'C' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10 UNION ALL
SELECT 'D' || LEVEL FROM DUAL CONNECT BY LEVEL <= 10;
Outputs:
POSITION
A1
A2
A3
...
A9
A10
B10
B9
...
B3
B2
B1
C1
C2
C3
...
C9
C10
D10
D9
...
D2
D1
fiddle

Related

Join rows with minimum and maximum value on a specific column

I have a table that looks like this:
COL ID_1 FOR A4
COL ID_2 FOR A4
COL ID_3 FOR A4
SELECT 'A1' ID_1, 'B1' ID_2, 10 NUM, 'X' ID_3 FROM DUAL UNION ALL
SELECT 'A1' ID_1, 'B2' ID_2, 20 NUM, 'Y' ID_3 FROM DUAL UNION ALL
SELECT 'A1' ID_1, 'B3' ID_2, 30 NUM, 'Z' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B1' ID_2, 1 NUM, 'Q' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B2' ID_2, 2 NUM, 'W' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B3' ID_2, 3 NUM, 'E' ID_3 FROM DUAL UNION ALL
SELECT 'C1' ID_1, 'B4' ID_2, 4 NUM, 'R' ID_3 FROM DUAL;
ID_1 ID_2 NUM ID_3
---- ---- ---- ----
A1 B1 10 X
A1 B2 20 Y
A1 B3 30 Z
C1 B1 1 Q
C1 B2 2 W
C1 B3 3 E
C1 B4 4 R
7 rows selected.
I want to join rows with minimum and maximum values on the NUM column. Result set grouped by ID_1 column. E.g.:
ID_1 ID_2 NUM ID_3
---- ---- ---- ----
A1 B1 10 X <-- A1: THIS MIN(NUM)
A1 B2 20 Y
A1 B3 30 Z <-- A1: THIS MAX(NUM)
C1 B1 1 Q <-- C1: THIS MIN(NUM)
C1 B2 2 W
C1 B3 3 E
C1 B4 4 R <-- C1: THIS MAX(NUM)
7 rows selected.
The expected result set is
A1 B1 10 X B3 30 Z
C1 B1 1 Q B4 4 R
Please advise how to achieve the desired result.
You can do it without any self-joins using aggregation functions with KEEP (DENSE_RANK [FIRST|LAST] ORDER BY ...):
SELECT id_1,
MIN(id_2) KEEP (DENSE_RANK FIRST ORDER BY num) AS min_id_2,
MIN(num) AS min_num,
MIN(id_3) KEEP (DENSE_RANK FIRST ORDER BY num, id_2) AS min_id_3,
MAX(id_2) KEEP (DENSE_RANK LAST ORDER BY num) AS max_id_2,
MAX(num) AS max_num,
MAX(id_3) KEEP (DENSE_RANK LAST ORDER BY num, id_2) AS max_id_3
FROM table_name
GROUP BY id_1;
Which, for the sample data, outputs:
ID_1
MIN_ID_2
MIN_NUM
MIN_ID_3
MAX_ID_2
MAX_NUM
MAX_ID_3
A1
B1
10
X
B3
30
Z
C1
B1
1
Q
B4
4
R
db<>fiddle here
Something like this:
with cte as (
select
id_1, id_2, num, id_3,
row_number() over (partition by id_1 order by num) as rn1,
row_number() over (partition by id_1 order by num desc) as rn2
from t
)
select
cte1.id_1,
cte1.id_2, cte1.num, cte1.id_3,
cte2.id_2, cte2.num, cte2.id_3
from cte cte1
join cte cte2 on cte1.id_1 = cte2.id_1 and cte2.rn2 = 1
where cte1.rn1 = 1

postgresql - count distinct combination of three columns- order doesn't matter

I'm trying to count distinct combinations of three columns, order of the columns doesn't matter
sample :
a a a
a a b
a b a
b b a
b a b
the result I'm getting :
a a a 1
a a b 1
a b a 1
b b a 1
b a b 1
desired result
aaa 1
aab 2
bba 2
You can use an ordered array
select v[1], v[2], v[3], count(*) n
from tbl t
cross join lateral (
select array_agg(col order by col) v
from (
values (c1),(c2),(c3)
) t(col)
) s
group by v[1], v[2], v[3];
db<>fiddle
Maybe you can use checksums for getting the required result eg if it is really just combinations 'a' and 'b' that you are dealing with, you could convert the letters to integers (by calling the ASCII() function) and add these up so that you get a checksum.
TABLE
create table t (c1, c2, c3 ) as
select 'a', 'a', 'a' union all
select 'a', 'a', 'b' union all
select 'a', 'b', 'a' union all
select 'b', 'b', 'a' union all
select 'b', 'a', 'b' ;
Checksums
select c1, c2, c3, ascii( c1 ) + ascii( c2 ) + ascii( c3 ) as checksum
from t ;
-- output
c1 c2 c3 checksum
a a a 291
a a b 292
a b a 292
b b a 293
b a b 293
If this works for you, then you can use window functions eg
select c1, c2, c3, rc_ as rowcount
from (
select c1, c2, c3
, count(*) over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rc_
, row_number() over ( partition by ascii( c1 ) + ascii( c2 ) + ascii( c3 ) order by 1 ) rn_
from t
) sq
where rc_ = rn_ ;
-- output
c1 c2 c3 rowcount
a a a 1
a b a 2
b a b 2
See dbfiddle.
If you are dealing with strings that cannot easily converted to integers, you could create a mapping between the strings and integers, and implement the map_ as a view (so that it is easy to use in subsequent queries) eg
MAP
-- {1} find all distinct elements
-- {2} map each element to an integer
create view map_
as
select val_, rank() over ( order by val_ ) weight_
from (
select distinct val_
from (
select distinct c1 val_ from t union all
select distinct c2 from t union all
select distinct c3 from t
) all_elements
) unique_elements ;
Once you have this map, you can use its values for creating checksums (maybe also in a view) ...
Checksums
create view t_checksums_
as
select c1, c2, c3, c1weight + c2weight + c3weight as checksum
from (
select
c1, ( select weight_ from map_ where c1 = map_.val_ ) c1weight
, c2, ( select weight_ from map_ where c2 = map_.val_ ) c2weight
, c3, ( select weight_ from map_ where c3 = map_.val_ ) c3weight
from t
) valandweight ;
... and then, you can use the same query as before, for obtaining the final result - see dbfiddle.

Optimize hierarchy query in Oracle

Table "links" contains 2 columns : PARENT_CODE, CHILD_CODE
PARENT_CODE | CHILD_CODE
------------|------------
A1 | B1
A1 | B2
B1 | C1
B1 | C2
B2 | C3
B3 | C3
C3 | D1
This table eventually used to form following graph :
My question will be how to write optimized hierarchy query in Oracle to get the full graph connected, and the input parameter will be any of node.
To illustrate the input/output, this is simplified SQL :
SELECT PARENT_CODE,CHILD_CODE FROM {HIERARCHY QUERY} where NODE = {ANY NODE}
The query below with show you all links. The idea is that first I create two ways connection between nodes. Using it we can do recursive query that returns our results. The last thing is decode that returns who is real parent and who is child basing on dir (0 - means original direction, 1 - means reverted direction).
with linkT (parent_code, child_code) as
(select 'A1','B1' from dual union all
select 'A1','B2' from dual union all
select 'B1','C1' from dual union all
select 'B1','C2' from dual union all
select 'B2','C3' from dual union all
select 'B3','C3' from dual union all
select 'C3','D1' from dual union all
select 'E1','F1' from dual ),
T1 as (
select parent_code, child_code, 0 Dir from linkT
union all
select child_code, parent_code, 1 Dir from linkT )
select distinct
decode(dir, 0, parent_code, child_code) as Parent_code,
decode(dir, 0, child_code, parent_code) as Child_Code
from (
select parent_code, child_code, dir
from T1 x
start with x.child_code='A1'
connect by nocycle prior x.parent_code=x.child_code )
order by 1,2

SQL - How can I use a level that depends on one column on other calculated column?

How can I do that in Oracle 11g?
The problem here is that the query on DUAL does not recognize APP table.
SELECT APP_VER || SUBVERSION.LEVEL
FROM APP,
(SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL < APP.NUM_SUBVERSIONS) SUBVERSION
Assuming that you need one row for each possibile subvesion, you can avoid the problem with some nesting, like the following:
select APP_VER , NUM_SUBVERSIONS, subversions.num
from app app
inner join
( select level num
from dual
connect by level <= ( select max(num_subversions) from app)
) subversions
on (subversions.num <= num_subversions)
This requires more scanning of your tables, so it may not be so efficient, but, from the names of your tables and columns, I assume you have not so many records to handle.
If I understood your problem correctly, you can use Recursive With subquerying to do it:
with a1 (app_ver, num_subversions, lvl) as (
select app_ver, num_subversions, 1
from app
union all
select app.app_ver, app.num_subversions, a1.lvl + 1
from app, a1
where app.app_ver = a1.app_ver
and a1.lvl < app.num_subversions)
search depth first by app_ver, num_subversions set my_order
select app_ver || lvl
from a1;
Sample execution:
SQL> create table app as
2 with app (app_ver, num_subversions) as (
3 select 'A', 2 from dual union all
4 select 'B', 3 from dual union all
5 select 'C', 4 from dual
6 )
7 select app_ver, num_subversions
8 from app a;
Table created
SQL> with a1 (app_ver, num_subversions, lvl) as (
2 select app_ver, num_subversions, 1
3 from app
4 union all
5 select app.app_ver, app.num_subversions, a1.lvl + 1
6 from app, a1
7 where app.app_ver = a1.app_ver
8 and a1.lvl < app.num_subversions)
9 search depth first by app_ver, num_subversions set my_order
10 select app_ver || lvl
11 from a1;
APP_VER||LVL
-----------------------------------------
A1
A2
B1
B2
B3
C1
C2
C3
C4
9 rows selected

Simplest way to repeat every N rows in sql

I know this is possible through some complex techniques, i want to know any simplest way to achieve this patter that every 10 rows should repeat .
for example
select a,b from tablename; (repeating 2 for example)
will give
a1,b1
a2,b2
a1,b1
a2.b2
a3,b3
a4,b4
a3,b3
a4,b4
where if it was 10 it will go like
a1,b1 to a10,b10 again a1,b1 to a10,b10
then
a11,b11 to a20,b20 again a11,b11 to a20,b20
and so on
You want blocks of ten rows repeated twice. So to get:
rows 1 to 10
rows 1 to 10
rows 11 to 20
rows 11 to 20
...
In order to get rows n-fold cross join with a table holding n records. (You get such for instance by querying a big enough table and stop at rowcount n.)
You also need the row number of your original records, so you can get block 1 first, then block 2 and so on. Use integer division to get from row numbers to blocks.
select t.a, t.b
from (select a, b, row_number() over (order by a, b) as rn from tablename) t
cross join (select rownum as repeatno from bigenoughtable where rownum <= 2) r
order by trunc((t.rn -1) / 10), r.repeatno, t.a, t.b;
Use a CTE and union all:
with rows as (
select a, b
from tablename
where rownum <= 2
)
select *
from rows
union all
select *
from rows;
Just some caveats to this. You should use an order by if you want particular rows from the table. This is important, because the same select can return different sets of rows. Actually, considering this, a better way is probably:
with rows as (
select a, b
from tablename
where rownum <= 2
)
select *
from rows cross join
(select 1 as n from dual union all select 2 from dual) n;
I would rather not use UNION so many times. My way would be CONNECT BY ROWNUM <=N. Actually a CARTESIAN JOIN. So, basically you need a ROW GENERATOR to cartesian join with it.,
Update
For example, this will repeat 10 rows 2 times -
SQL> WITH t AS
2 ( SELECT 'a1' A, 'b1' b FROM dual
3 UNION ALL
4 SELECT 'a2' a, 'b2' b FROM dual
5 UNION ALL
6 SELECT 'a3' a, 'b3' b FROM dual
7 UNION ALL
8 SELECT 'a4' A, 'b4' b FROM dual
9 UNION ALL
10 SELECT 'a5' A, 'b5' b FROM dual
11 UNION ALL
12 SELECT 'a6' a, 'b6' b FROM dual
13 UNION ALL
14 SELECT 'a7' A, 'b7' b FROM dual
15 UNION ALL
16 SELECT 'a8' a, 'b8' b FROM dual
17 UNION ALL
18 SELECT 'a9' a, 'b9' b FROM dual
19 UNION ALL
20 SELECT 'a10' a, 'b10' b FROM dual
21 )
22 SELECT A,B FROM t,
23 (SELECT 1 FROM DUAL CONNECT BY ROWNUM <=2
24 )
25 /
A B
--- ---
a1 b1
a2 b2
a3 b3
a4 b4
a5 b5
a6 b6
a7 b7
a8 b8
a9 b9
a10 b10
a1 b1
a2 b2
a3 b3
a4 b4
a5 b5
a6 b6
a7 b7
a8 b8
a9 b9
a10 b10
20 rows selected.
SQL>
So, above CONNECT BY ROWNUM <=10 means repeat the rows 10 times. If you want it to be repeated N times use CONNECT BY ROWNUM <=N.