I need to find the way to list all the characters used in the column in order to narrow down the "Approved" values within the insert template we are creating...
the idea is to allow all letters (only standard) without any dialect / country specific ones.
trying something like this... but need to have a list of the characters left over... like "$%()* etc.
SELECT * FROM mytable WHERE REGEXP_LIKE(column_1,^[a-zA-Z0-9-]+$)
To find the other characters, you could remove the ones you do expect and then see what is left:
SELECT REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) AS other_characters
FROM mytable
WHERE REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) IS NOT NULL
If you want to concatenate and remove duplicate characters:
WITH replace_expected ( str ) AS (
SELECT REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' )
FROM mytable
WHERE REGEXP_REPLACE( column1, '[a-zA-Z0-9-]' ) IS NOT NULL
),
split_strings ( str, pos, ch ) AS (
SELECT str, 1, SUBSTR(str, 1, 1)
FROM replace_expected
UNION ALL
SELECT str, pos + 1, SUBSTR(str, pos + 1, 1)
FROM split_strings
WHERE pos < LENGTH(str)
)
SELECT LISTAGG(DISTINCT ch) WITHIN GROUP (ORDER BY ch) AS other_characters
FROM split_strings;
fiddle
Two steps:
Extract the special characters from all strings in the column and concatenate them into one long string (with possibly many characters appearing multifold in the string).
Use a recursive query to loop through the characters and return a string with distinct characters.
The query:
with one_row (str) as
(
select listagg(regexp_replace(column_1, '[a-zA-Z0-9\-]'))
from mytable
where regexp_like(column_1, '[^a-zA-Z0-9\-]')
)
select listagg(distinct substr(str, level, 1)) as c
from one_row
connect by level <= length(str);
create table dummy_data as
select rownum as id, dbms_random.string(opt, len) str
from (
select case round(dbms_random.value(0, 3))
when 0 then 'a'
when 1 then 'x'
else 'p'
end opt,
round(dbms_random.value(5, 60)) len
from dual connect by level <= 30 );
SELECT * FROM DUMMY_DATA;
ID STR
1 UMUUJ0R5VM1T3X10TDCNIWC3MQ5ELOB041YMNEJSLT
2 _t8 }LeZhjiMB"8/a'/~a
3 BLSE6XX6SL3M7W0DG3HH28SCHPSAT11ZH2E5DOSKEV3KW9
4 1]Mh58(l<Wa}{
5 :_QiWUkwp}V$}O
6 NC911A4SRN35CNXT2EU5H2GZ67IQQLKH
7 e"8,z$=Yvy5egvEH2KUkNoVjkitd9IMm0ZktsB i(bk4uU]c3;E
8 MgbpIsLZpWEcAghOUKOISA
9 7H02ASKO3CZRN4D5FUNPEU6YUZD
10 KbJ+QrI\l.th%>^f!Io%wshsVA%
11 PO9A47VU7AXI17XYD5VMSWW8E
12 1ILWL4V
13 FgubwibYBytNvmJHxUfG
14 ?[ngH?0!k.onN>mF(nrkO
15 86G0HP3
16 WXDBV3OBMVSDKQ59YT73G0II3U94
17 GP375CFIQPPN6216I5A7L54O
18 i\L<K,"d'ye 6s~_MB0O1 aC$q;T"EaqpZ^s\gIiYu&:%OnhVj]<a]CmOgqM
19 WxUEtr\II(97i7PQ-Z]yqd#&`#CQB0M"c0;{.by9qo#HT
20 IF5OP7KS9AXW91
21 HNcKwxXozXjTVwKeFZDLdmNOzFKKKq
22 4D8CINXIVT244RDDRZ5TSDQ4CRF4
23 3)oxevW-(~=+#cP[^g)##|1.TL-_N9O-Zdgj"cwJC'*NR; FtK)K
24 AndzeLIEPklDuTWWEBrKrdNKdXwMGeLauJkRzKpKHEGAsxlEXliwBTHdK
25 dlEX1tGFuU5\5+{5`R
26 /W0.{B&)ax&lWEE#OSw
27 CBKOVLKDFKC3EVR
28 :V#Lc.Z"8[O-)cAWUpMjc?j\Kj?xV#%`Yp [VkEV1
29 P9P047
30 W)S<fB`F;N_brMP
with
h (ok_chars) as (
select '0123456789' || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ||
'abcdefghijklmnopqrstuvwxyz'
from dual
)
select c, count(*) as cnt
from dummy_data cross join lateral (
select substr(str, level, 1) as c
from h
where instr(ok_chars, substr(str, level, 1)) = 0
connect by level <= length(str)
)
group by c;
C CNT
} 4
- 5
' 3
< 4
, 2
= 2
_ 5
( 5
^ 3
[ 4
" 7
/ 3
: 3
. 6
% 5
! 2
? 4
` 4
8
; 5
+ 3
> 2
) 6
| 1
* 1
~ 3
\ 6
& 4
# 5
] 5
{ 4
# 4
$ 3
Related
Problem statement is to calculate median from a table that has two columns. One specifying a number and the other column specifying the frequency of the number.
For e.g.
Table "Numbers":
Num
Freq
1
3
2
3
This median needs to be found for the flattened array with values:
1,1,1,2,2,2
Query:
with ct1 as
(select num,frequency, sum(frequency) over(order by num) as sf from numbers o)
select case when count(num) over(order by num) = 1 then num
when count(num) over (order by num) > 1 then sum(num)/2 end median
from ct1 b where sf <= (select max(sf)/2 from ct1) or (sf-frequency) <= (select max(sf)/2 from ct1)
Is it not possible to use count(num) over(order by num) as the condition in the case statement?
Find the relevant row / 2 rows based of the accumulated frequencies, and take the average of num.
The example and Fiddle will also show you the
computations leading to the result.
If you already know that num is unique, rowid can be removed from the ORDER BY clauses
with
t1 as
(
select t.*
,nvl(sum(freq) over (order by num,rowid rows between unbounded preceding and 1 preceding),0) as freq_acc_sum_1
,sum(freq) over (order by num, rowid) as freq_acc_sum_2
,sum(freq) over () as freq_sum
from t
)
select t1.*
,case
when freq_sum/2 between freq_acc_sum_1 and freq_acc_sum_2
then 'V'
end as relevant_record
from t1
order by num, rowid
Fiddle
Example:
ID
NUM
FREQ
FREQ_ACC_SUM_1
FREQ_ACC_SUM_2
FREQ_SUM
RELEVANT_RECORD
7
8
1
0
1
18
5
10
1
1
2
18
1
29
3
2
5
18
6
31
1
5
6
18
3
33
2
6
8
18
4
41
1
8
9
18
V
9
49
2
9
11
18
V
2
52
1
11
12
18
8
56
3
12
15
18
10
92
3
15
18
18
MEDIAN
45
Fiddle for 1M records
You can find the one (or two) middle value(s) and then average:
SELECT AVG(num) AS median
FROM (
SELECT num,
freq,
SUM(freq) OVER (ORDER BY num) AS cum_freq,
(SUM(freq) OVER () + 1)/2 AS median_freq
FROM table_name
)
WHERE cum_freq - freq < median_freq
AND median_freq < cum_freq + 1
Or, expand the values using a LATERAL join to a hierarchical query and then use the MEDIAN function:
SELECT MEDIAN(num) AS median
FROM table_name t
CROSS JOIN LATERAL (
SELECT LEVEL
FROM DUAL
WHERE freq > 0
CONNECT BY LEVEL <= freq
)
Which, for the sample data:
CREATE TABLE table_name (Num, Freq) AS
SELECT 1, 3 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL;
Outputs:
MEDIAN
1.5
(Note: For your sample data, there are 6 items, an even number, so the MEDIAN will be half way between the value of 3rd and 4rd items; so half way between 1 and 2 = 1.5.)
db<>fiddle here
I have the below code:
SELECT
m, l
FROM
(
SELECT
2 * level + 3 l
FROM
dual
CONNECT BY
2 * level + 3 <= 1000
) CROSS APPLY (
SELECT
2 * level + 1 m
FROM
dual
CONNECT BY
power(2 * level + 1, 2) <= l
) order by m, l
However, the first results shown are the below. Why is the 2nd power of m connecting with l in that case if power(3, 2) is not <= 5 (or 7)?
m l
3 5
3 7
Because CONNECT BY checks whether the query will recurse after the first row is generated so you will always get at least one row (where LEVEL is 1); if you want to limit the number of rows then you need a START WITH or WHERE clause.
You can see the difference if you also include either clause and OUTER APPLY to return the l rows when m then has no generated rows (and would be NULL):
SELECT m, l
FROM ( SELECT 2 * level + 3 AS l
FROM dual
CONNECT BY 2 * level + 3 <= 25 )
OUTER APPLY
( SELECT 2 * level + 1 AS m
FROM dual
START WITH 9 <= l
CONNECT BY power(2 * level + 1, 2) <= l )
order by l, m
or
SELECT m, l
FROM ( SELECT 2 * level + 3 AS l
FROM dual
CONNECT BY 2 * level + 3 <= 25 )
OUTER APPLY
( SELECT 2 * level + 1 AS m
FROM dual
WHERE power(2 * level + 1, 2) <= l
CONNECT BY power(2 * level + 1, 2) <= l )
order by l, m
or
Outputs:
M
L
5
7
3
9
3
11
3
13
3
15
3
17
3
19
3
21
3
23
3
25
5
25
db<>fiddle here
I have a table for subjects as follows:
id Subject Grade Ext
100 Math 6 +
100 Science 4 -
100 Hist 3
100 Geo 2 +
100 CompSi 1
I am expecting output per student in a class(id = 100) as follows:
Grade Ext StudentGrade
6 + 1
6 0
6 - 0
5 + 0
5 0
5 - 0
4 + 0
4 0
4 - 1
3 + 0
3 1
3 - 0
2 + 1
2 0
2 - 0
1 + 0
1 1
1 - 0
I would want this done on oracle/sql rather than UI. Any inputs please.
You should generate rows first, before join them with your table like below. I use the with clause here to generate the 18 rows in your sample.
with rws (grade, ext) as (
select ceil(level/3), decode(mod(level, 3), 0, '+', 1, '-', null)
from dual
connect by level <= 3 * 6
)
select r.grade, r.ext, nvl2(t.Ext, 1, 0) studentGrade
from rws r
left join your_table t
on t.Grade = r.Grade and decode(t.Ext, r.Ext, 1, 0) = 1
order by 1 desc, decode(r.ext, null, 2, '-', 3, '+', 1)
You could do something like this. In the WITH clause I generate two small "helper" tables (really, inline views) for grades from 1 to 6 and for "extensions" of +, null and -. In the "extensions" view I also create an "ordering" column to use in ordering the final output (if you are wondering why I included that).
Also in the WITH clause I included sample data - you will have to remove that and instead use your actual table name in the main query.
The idea is to cross-join "grades" and "extensions", and left-outer-join the result to your input data. Count the grades from the input data, grouped by grade and extension, and after filtering the desired id. The decode thing in the join condition is needed because for extension we want to treat null as equal to null - something that decode does nicely.
with
sample_inputs (id, subject, grade, ext) as (
select 100, 'Math' , 6, '+' from dual union all
select 100, 'Science', 4, '-' from dual union all
select 100, 'Hist' , 3, null from dual union all
select 100, 'Geo' , 2, '+' from dual union all
select 100, 'CompSi' , 1, null from dual
)
, g (grade) as (select level from dual connect by level <= 6)
, e (ord, ext) as (
select 1, '+' from dual union all
select 2, null from dual union all
select 3, '-' from dual
)
select g.grade, e.ext, count(t.grade) as studentgrade
from g cross join e left outer join sample_inputs t
on t.grade = g.grade and decode(t.ext, e.ext, 0) = 0
and t.id = 100 -- change this as needed!
group by g.grade, e.ext, e.ord
order by g.grade desc, e.ord
;
OUTPUT:
GRADE EXT STUDENTGRADE
----- --- ------------
6 + 1
6 0
6 - 0
5 + 0
5 0
5 - 0
4 + 0
4 0
4 - 1
3 + 0
3 1
3 - 0
2 + 1
2 0
2 - 0
1 + 0
1 1
1 - 0
It looks like you want sparse data to be filled in as part of joining students and subjects.
Since Oracle 10g the correct way to do this has been with a "partition outer join".
The documentation has examples.
https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6
Is it possible to solve this situation by sql query in ORACLE?
I have a table like this:
TYPE UNIT
A 230
B 225
C 60
D 45
E 5
F 2
I need to separate units to the three(variable) 'same'(equally sized) intervals and foreach figure out the count? It means something like this:
0 - 77 -> 4
78 - 154 -> 0
155 - 230 -> 2
You can use the maximum value and a connect-by query to generate the upper and lower values for each range:
select ceil((level - 1) * int) as int_from,
floor(level * int) - 1 as int_to
from (select round(max(unit) / 3) as int from t42)
connect by level <= 3;
INT_FROM INT_TO
---------- ----------
0 76
77 153
154 230
And then do a left outer join to your original table to do the count for each range, so you get the zero value for the middle range:
with intervals as (
select ceil((level - 1) * int) as int_from,
floor(level * int) - 1 as int_to
from (select round(max(unit) / 3) as int from t42)
connect by level <= 3
)
select i.int_from || '-' || i.int_to as range,
count(t.unit)
from intervals i
left join t42 t
on t.unit between i.int_from and i.int_to
group by i.int_from, i.int_to
order by i.int_from;
RANGE COUNT(T.UNIT)
---------- -------------
0-76 4
77-153 0
154-230 2
Yes, this can be done in Oracle. The hard part is the definition of the bounds. You can use the maximum value and some arithmetic on a sequence with values of 1, 2, and 3.
After that, the rest is just a cross join and aggregation:
with bounds as (
select (case when n = 1 then 0
when n = 2 then trunc(maxu / 3)
else trunc(2 * maxu / 3)
end) as lowerbound,
(case when n = 1 then trunc(maxu / 3)
when n = 2 then trunc(2*maxu / 3)
else maxu
end) as upperbound
from (select 1 as n from dual union all select 2 from dual union all select 3 from dual
) n cross join
(select max(unit) as maxu from atable t)
)
select b.lowerbound || '-' || b.upperbound,
sum(case when units between b.lowerbound and b.upperbound then 1 else 0 end)
from atable t cross join
bounds b
group by b.lowerbound || '-' || b.upperbound;
I have a table that looks like follows:
NAME(varchar(6), STRING(varchar(250)
ABCD '1 2 1 173 1 8 9 1 1 2 4 7 1 3.....'
APLC '1 3 11 34 1 4 99 33 23 111 12 6 7 8....'
the string continues with this up to 250 characters.
What I am trying to do is get the values and there respective positions from this string.
I know I can use Charindex but that gives me only the first position of a number in the string.
e.g.
Select Charindex ('2',STRING) where Name = ABCD
ANSWER = 7
But what I'm looking for is something like a table that have the following in for each Name
Name Position Value
---------------------------
ABCD, 7, 2
ABCD, 1, 1
ABCD, 13, 1
ABCD, 18, 1
ABCD, 19, 7
Any ideas welcome :)
With a little help of a numbers table it could look like this.
select T.Name,
N.N as Position,
substring(T.STRING, N.N, 1) as Value
from YourTable as T
cross apply Numbers as N
where N.N between 1 and 250 and
substring(T.STRING, N.N, 1) <> ' '
Working sample with table variable and master..spt_values as a numbers table.
declare #T table
(
NAME varchar(6),
STRING varchar(250)
)
insert into #T values
('ABCD', '1 2 1 173 1 8 9 1 1 2 4 7 1 3'),
('APLC', '1 3 11 34 1 4 99 33 23 111 12 6 7 8')
;with Numbers(N) as
(
select Number
from master..spt_values
where type = 'P'
)
select T.Name,
N.N as Position,
substring(T.STRING, N.N, 1) as Value
from #T as T
cross apply Numbers as N
where N.N between 1 and 250 and
substring(T.STRING, N.N, 1) <> ' '
This approach would work for multi-digit numbers. If 173 should result in three result rows, check Mikael Eriksson's or podiluska's answer.
; with cte as
(
select 1 as start
, case
when patindex('%[0-9] %', string) > 0 then patindex('%[0-9] %', string)
else len(string)
end as [length]
, name
, string
from YourTable
union all
select start + [length] as start
, case
when patindex('%[0-9] %',
substring(string, start + [length], len(string)-start + [length]))
> 0 then patindex('%[0-9] %',
substring(string, start + [length], len(string)-start + [length]))
else len(string)-start + [length]
end as [length]
, name
, string
from cte
where start + [length] < len(string)
)
select Name
, start + patindex('%[0-9]%', substring(string, [start], [length])) - 1 as Position
, ltrim(substring(string, [start], [length])) as Value
from cte
Live example at SQL Fiddle.
Where #t is your table...
;WITH numbers ( n ) AS
(
select 1 as n
union all
select 1 + n FROM numbers WHERE n < 250
)
select name, n as position, SUBSTRING(string,n,1) as value
from #t, numbers
where SUBSTRING(string,n,1)<>' '
order by Name, n
option (maxrecursion 250)
If, on the other hand, you want to treat consecutive numbers as being a one number...
;WITH Pieces(name, pn, start, [stop], string) AS
(
SELECT name, 1, 1, CHARINDEX(' ', string),string from #t
UNION ALL
SELECT pieces.name, pn + 1, stop + 1, CHARINDEX(' ', pieces.string, stop + 1), pieces.string
FROM Pieces
inner join #t on pieces.name = #t.name
WHERE stop > 0
)
select *
from
( SELECT name, start as position,
SUBSTRING(string, start, CASE WHEN stop > 0 THEN stop-start ELSE 300 END) AS value
FROM Pieces
) v
where RTRIM(value)>''