Sorting Alphanumeric data in Oracle - sql

I have an alphanumeric data like:
1
1a
1b
2
2b
10
10a
If I sort this data, output will be like:
1
1a
10
10a
2
2b
But I want output as:
1
1a
2
2b
10
10a
How to get this output with Oracle command?

So, as I understand, you want to sort by numeric part of your data. For this purpose you can use regular expression (to extract the numeric part) like this:
SQL> select str from
2 (
3 select '1' str from dual union all
4 select '1a' from dual union all
5 select '1b' from dual union all
6 select '2' from dual union all
7 select '2b' from dual union all
8 select '10' from dual union all
9 select '10a' from dual
10 ) t
11 order by to_number(regexp_substr(str, '^[[:digit:]]*')), str
12 /
STR
---
1
1a
1b
2
2b
10
10a

You can also do the same by separating number and alphanumeric sorting order in order by clause. check below example:
SELECT tt.qdef_grid
FROM qgdm_qdef tt
ORDER BY to_number(substr(tt.qdef_grid,2,2)), substr(tt.qdef_grid,1,1);

Related

All tables consisting of numbers less than a fixed number

I am trying to find out all the tables where table names consist of numbers less than a fixed number 16284961 at the end preceded by an underscore for example LOG_16282961.
Sample User_segments table:
Segment_name Bytes
---------------------------------------
LOG_16282961 34
BAL1_16282961 78
BIN$xIDte/qXAFbgU4IeBEeQpw==$0 12
EXCH_16282961 28
C$_0LOG_16282961 17
LOG_16283961 89
BAL1_16283961 10
BIN$xIDte/qWAFbgU4IeBEeQpw==$0 19
EXCH_16283961 90
C$_0LOG_16283961 45
LOG_16284961 21
BAL1_16284961 81
BIN$w1RLAvSeAWjgU4IeBEe2Mw==$0 33
EXCH_16284961 67
C$_0LOG_16284961 39
.......................................
.......................................
Expected Output:
Segment_name Bytes
----------------------
LOG_16282961 34
BAL1_16282961 78
EXCH_16282961 28
C$_0LOG_16282961 17
LOG_16283961 89
BAL1_16283961 10
EXCH_16283961 90
C$_0LOG_16283961 45
.......................
.......................
Query:
SELECT segment_name, bytes/1024/1024 AS "SIZE in MB" FROM user_segments WHERE segment_type='TABLE' AND to_number(regexp_substr(segment_name, '[0-9]+')) < 16284961;
Using above query, although I am getting my result but additionally it also includes following tables which are not required in my output:
BIN$xIDte/qXAFbgU4IeBEeQpw==$0 12
BIN$xIDte/qWAFbgU4IeBEeQpw==$0 19
BIN$w1RLAvSeAWjgU4IeBEe2Mw==$0 33
Can you please help fix my query to get the desired output? Thanks.
Here's one way - using regexp_substr to isolate one or more consecutive digits at the end of the input string, only if immediately preceded by underscore. (If the string does not have that structure, regexp_substr returns null and the filter condition becomes null < [something], which is never true.)
Create mock-up table for testing:
create table test_data (segment_name, bytes) as
select 'LOG_16282961' , 34 from dual union all
select 'BAL1_16282961' , 78 from dual union all
select 'BIN$xIDte/qXAFbgU4IeBEeQpw==$0', 12 from dual union all
select 'EXCH_16282961' , 28 from dual union all
select 'C$_0LOG_16282961' , 17 from dual union all
select 'LOG_16283961' , 89 from dual union all
select 'BAL1_16283961' , 10 from dual union all
select 'BIN$xIDte/qWAFbgU4IeBEeQpw==$0', 19 from dual union all
select 'EXCH_16283961' , 90 from dual union all
select 'C$_0LOG_16283961' , 45 from dual union all
select 'LOG_16284961' , 21 from dual union all
select 'BAL1_16284961' , 81 from dual union all
select 'BIN$w1RLAvSeAWjgU4IeBEe2Mw==$0', 33 from dual union all
select 'EXCH_16284961' , 67 from dual union all
select 'C$_0LOG_16284961' , 39 from dual
;
Query and output:
select *
from test_data
where to_number(regexp_substr(segment_name, '_(\d+)$', 1, 1, null, 1))
< 16284961
;
SEGMENT_NAME BYTES
------------------------------ ----------
LOG_16282961 34
BAL1_16282961 78
EXCH_16282961 28
C$_0LOG_16282961 17
LOG_16283961 89
BAL1_16283961 10
EXCH_16283961 90
C$_0LOG_16283961 45
If query you wrote works, just omit tables you don't want. Those you mentioned have been dropped and are now in recycle bin. So, either purge recyclebin before running the query, or use additional condition, e.g.
SELECT segment_name, bytes / 1024 / 1024 AS "SIZE in MB"
FROM user_segments
WHERE segment_type = 'TABLE'
AND substr(segment_name, 1, 4) <> 'BIN$' --> this
AND TO_NUMBER (REGEXP_SUBSTR (segment_name, '[0-9]+')) < 16284961;

using oracle regexp_replace to replace some part of a code

I have some codes and I want to replace a peace of that code only. in my case BB to XX
AA/BB
AA/BB1
AA/BB-1
BB
BB1
BB-1
I tried use to regexp_replace with this simple form
Query:
select regexp_replace('AA/BB','BB','XX') from dual;
Result:
AA/XX
Query:
select regexp_replace('AA/BB-1','BB','XX') from dual;
Result:
AA/XX-1
It works fine but it can happen that before the slash AA will be BB as well but this time it shouldn't be replaced but still works for the rest of codes.
select regexp_replace('BB/BB','BB','XX') from dual;
gives me XX/XX of course but I want to achieve BB/XX etc.
Alternatively - see comments within code, where "starting at position" reads as: if there are more than 1 BB substrings there, start at position of the 2nd BB within the MYVAL. Otherwise, start from the beginning of MYVAL.
Thank you, #GMB, for sample data.
SQL> with t as (
2 select 'AA/BB' myval from dual
3 union all select 'AA/BB1' from dual
4 union all select 'AA/BB-1' from dual
5 union all select 'BB' from dual
6 union all select 'BB' from dual
7 union all select 'BB1' from dual
8 union all select 'BB-1' from dual
9 union all select 'BB/BB' from dual
10 union all select 'AA/BB/BB-2' from dual
11 )
12 select myval,
13 regexp_replace
14 (myval, --> in MYVAL
15 'BB', --> replace BB
16 'XX', --> with XX
17 case when regexp_count(myval, 'BB') = 1 then 1 --> starting at position*
18 else instr(myval, 'BB', 1, 2)
19 end
20 ) result
21 from t;
MYVAL RESULT
---------- ---------------
AA/BB AA/XX
AA/BB1 AA/XX1
AA/BB-1 AA/XX-1
BB XX
BB XX
BB1 XX1
BB-1 XX-1
BB/BB BB/XX
AA/BB/BB-2 AA/BB/XX-2
9 rows selected.
SQL>
Maybe we could phrase this as: replace 'BB' that is not followed by '/'?
regexp_replace(myval, 'BB($|[^/])', 'XX\1')
Demo on DB Fiddle:
with t as (
select 'AA/BB' myval from dual
union all select 'AA/BB1' from dual
union all select 'AA/BB-1' from dual
union all select 'BB' from dual
union all select 'BB' from dual
union all select 'BB1' from dual
union all select 'BB-1' from dual
union all select 'BB/BB' from dual
)
select myval, regexp_replace(myval, 'BB($|[^/])', 'XX\1') newval from t
MYVAL | NEWVAL
:------ | :------
AA/BB | AA/XX
AA/BB1 | AA/XX1
AA/BB-1 | AA/XX-1
BB | XX
BB | XX
BB1 | XX1
BB-1 | XX-1
BB/BB | BB/XX

Transform one row for columns where there is same data in other columns

Long time have visited this site and got lot of help. Now I could not find what I'm looking for.
I'm working on Oracle SQL database
Table
columnA ColumnB ColumnC
10A AA 00
10A AA 10
10A AA 20
10A AA 30
10A BB 00
10A BB 10
10A BB 20
10A BB 30
and what I would like to do is extend the columns where there is same data in ColumnA and B like:
columnA ColumnB ColumnC ext.ColumnD ext.ColumnE ext.ColumnF
10A AA 00 10 20 30
10A BB 00 10 20 30
I have tried different pivots, tried lot of suggestions I've found forums but just cant get the result I hope for.
This is my first post so sorry in advance if there is something wrong or information is missing.
Thank you so much in advance!
/ edit
And there would not be same data in ColumnC always or even as many differents. If BB would not have 10 and have differents, it should look something like this:
columnA ColumnB ColumnC ext.ColumnD ext.ColumnE ext.ColumnF
10A AA 00 10 20 30
10A BB 00 17 47
Also goal is to get them increasing order, but that can be done with query, sorting in the first place.
/Edit
Now that I have this one table:
ColumnA ColumnB ColumnC Height1 Height2 Height3 height4
5A 11 A 0 25 50 75
5A 11 B 0 25 50 75
5A 11 C 0 25 50
5A 11 D 0 25 50 75
I have one row in the original table that have random digits which are unique and it needs to go after the height. pivot table with unique row:
ColumnA ColumnB ColumnC Unique ColumnE ColumnF ColumnG ColumnG
5A 11 A 16805097 00
5A 11 A 62366527 25
5A 11 A 65672596 75
5A 11 A 68078617 50
So results would be:
ColumnA ColumnB ColumnC Height1 Unique1 Height2 Unique2 Height3 Unique3 Height4 Unique4
5A 11 A 00 16805097 25 62366527 75 65672596 50 68078617
So if we would take the original question's table, the start point would be like this:
columnA ColumnB ColumnC ColumnD
10A AA 00 699787
10A AA 10 439567
10A AA 20 429456
10A AA 30 122172
10A BB 00 133244
10A BB 10 328311
10A BB 20 247422
10A BB 30 769636
Br
Jan
You can use PIVOT as well and use it we can use row_number() to give a unique number by partition columnA and columnB and then use it in the pivoting clause.
The only disadvantage as you might be aware of is to hard code the values in the PIVOT clause. I have taken max 10 values as of now which you can adjust as per your need.
Note:- The column names generated by PIVOT will be in format of e.g. COLUMND,COLUMNE and so on.. which further can be changed at outer most query as you want. You can also give an alias to MAX(columnC) some_alias but then the columns names will be append it like COLUMND_SOME_ALIAS,COLUMNE_SOME_ALIAS and so on..
SELECT *
FROM
(
SELECT columnA,columnB,columnC,
row_number() OVER (PARTITION BY columnA,columnB ORDER BY columnC) rn_pivot
FROM table1
)
PIVOT
(
MAX(columnC)
FOR rn_pivot IN (1 AS columnD
,2 AS columnE
,3 AS columnF
,4 AS columnG
,5 AS columnH
,6 AS columnI
,7 AS columnJ
,8 AS columnK
,9 AS columnL
,10 AS columnM)
);
If you combine the values in ColumnC into a single CSV list using LISTAGG, you can then use REGEXP_SUBSTR to get the the values in that CSV list. The example below returns up to 5 values for each combination of ColumnA and ColumnB but you can add additional REGEXP_SUBSTR columns if more than 5 are needed.
Query
WITH
sample_data (cola, colb, colc)
AS
(SELECT '10A', 'AA', '00' FROM DUAL
UNION ALL
SELECT '10A', 'AA', '10' FROM DUAL
UNION ALL
SELECT '10A', 'AA', '20' FROM DUAL
UNION ALL
SELECT '10A', 'AA', '30' FROM DUAL
UNION ALL
SELECT '10A', 'BB', '00' FROM DUAL
UNION ALL
SELECT '10A', 'BB', '10' FROM DUAL
UNION ALL
SELECT '10A', 'BB', '20' FROM DUAL
UNION ALL
SELECT '10A', 'BB', '30' FROM DUAL
UNION ALL
SELECT '10A', 'BB', '40' FROM DUAL)
SELECT cola,
colb,
REGEXP_SUBSTR (colc_vals, '[^,]+', 1, 1) as colc_1,
REGEXP_SUBSTR (colc_vals, '[^,]+', 1, 2) as colc_2,
REGEXP_SUBSTR (colc_vals, '[^,]+', 1, 3) as colc_3,
REGEXP_SUBSTR (colc_vals, '[^,]+', 1, 4) as colc_4,
REGEXP_SUBSTR (colc_vals, '[^,]+', 1, 5) as colc_5
FROM ( SELECT cola, colb, LISTAGG (colc, ',') within group (order by colc) AS colc_vals
FROM sample_data
GROUP BY cola, colb);
Result
COLA COLB COLC_1 COLC_2 COLC_3 COLC_4 COLC_5
_______ _______ _________ _________ _________ _________ _________
10A BB 00 10 20 30 40
10A AA 00 10 20 30

Sql range query

Query returning 2 values from table TBL_CHARGES:
Range_in_hrs Range_to_hours charges
4 48 5
48 70 10
70 90 20
Select charges from table if range 47.59 is passed the return charge should be 5. If 48.00 is passed the charges will be 5.
If 48.01 is passed the charges should be 10.
I am trying this
SELECT *
FROM TBL_CHARGES
WHERE 48.00 between Range_in_hrs and Range_to_hours
But it does not works.
Sounds like you need to avoid using BETWEEN and instead use explicit ranges, like so:
select *
from tbl_charges
where 48 > range_in_hrs
and 48 <= range_to_hours;
And here's an example that shows the output you might get with various different values:
with tbl_charges as (select 4 range_in_hours, 48 range_to_hours, 5 charges from dual union all
select 48 range_in_hours, 70 range_to_hours, 10 charges from dual union all
select 70 range_in_hours, 90 range_to_hours, 20 charges from dual),
vals as (select 4 val from dual union all
select 4.01 val from dual union all
select 47.59 val from dual union all
select 48 val from dual union all
select 48.01 val from dual union all
select 70 val from dual union all
select 71 val from dual)
select vals.val,
tc.charges
from vals
left outer join tbl_charges tc on (vals.val > tc.range_in_hours and vals.val <= tc.range_to_hours);
VAL CHARGES
---------- ----------
4
4.01 5
47.59 5
48 5
48.01 10
70 10
71 20
My problem got solved by this query.
SELECT *
FROM TBL_CHARGES
WHERE 48.00 between Range_in_hrs||'.01' and Range_to_hours||'.00'
SELECT charges
FROM TBL_CHARGES
WHERE ceil(to_number('48.01')) between Range_in_hrs and Range_to_hours
Use Replace and Ceil.

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.