Filter records based on numeric value when column type is varchar - sql

I have a column in a table with datatype varchar that stores version numbers.I need to filter and select only version numbers that are less than equal to 5
Input
5.0.0.330
Eclair
5.0.0
5.0.0.591
5.0.0.405
6.0.0.522
4.0.2
7.1.0.205
5.0.0.592
2.3.4-ez
4.2.2-2013-12-11-V1.0
4.6.0.304
nubernel-2.6.35_v0.0.1
2.1-update1
2.3
Output
5.0.0
4.0.2
2.3.4-ez
4.2.2-2013-12-11-V1.0
4.6.0.304
2.1-update1
2.3
I can get all the versions less than 5 by converting the first character of the varchar column.However I can't figure out how to include the 5.0.0 version in the result set.
select distinct os_ver,substring(os_ver,1,1)
from
dbo.mytable
where
os_ver like '[0-9]%' and cast (substring(os_ver,1,1) as int) < 5
This gives me all version less than 5 except the version 5.0.0
4.0.2
2.3.4-ez
4.2.2-2013-12-11-V1.0
4.6.0.304
2.1-update1
2.3

Select *
From dbo.mytable
Where os_ver<='5.0.0'
Returns
os_ver
5.0.0
4.0.2
2.3.4-ez
4.2.2-2013-12-11-V1.0
4.6.0.304
2.1-update1
2.3

Try this condition.
select * from (
select '5.0.0.330' as a union
select 'Eclair' union
select '5.0.0' union
select '5.0.0.591' union
select '5.0.0.405' union
select '6.0.0.522' union
select '4.0.2' union
select '7.1.0.205' union
select '5.0.0.592' union
select '2.3.4-ez' union
select '4.2.2-2013-12-11-V1.0' union
select '4.6.0.304' union
select 'nubernel-2.6.35_v0.0.1' union
select '2.1-update1' union
select '2.3') b
where a <= '5.0.0' and ISNUMERIC(SUBSTRING(a, 1, 1)) = 1

Related

Find value that is not a number or a predefined string

I have to test a column of a sql table for invalid values and for NULL.
Valid values are: Any number and the string 'n.v.' (with and without the dots and in every possible combination as listed in my sql command)
So far, I've tried this:
select count(*)
from table1
where column1 is null
or not REGEXP_LIKE(column1, '^[0-9,nv,Nv,nV,NV,n.v,N.v,n.V,N.V]+$');
The regular expression also matches the single character values 'n','N','v','V' (with and without a following dot). This shouldn't be the case, because I only want the exact character combinations as written in the sql command to be matched. I guess the problem has to do with using REGEXP_LIKE. Any ideas?
I guess this regexp will work:
NOT REGEXP_LIKE(column1, '^([0-9]+|n\.?v\.?)$', 'i')
Note that , is not a separator, . means any character, \. means the dot character itself and 'i' flag could be used to ignore case instead of hard coding all combinations of upper and lower case characters.
No need to use regexp (performance will increase by large data) - plain old TRANSLATE is good enough for your validation.
Note that the first translate(column1,'x0123456789','x') remove all numeric charcters from the string, so if you end with nullthe string is OK.
The second translate(lower(column1),'x.','x') removes all dots from the lowered string so you expect the result nv.
To avoid cases as n.....v.... you also limit the string length.
select
column1,
case when
translate(column1,'x0123456789','x') is null or /* numeric string */
translate(lower(column1),'x.','x') = 'nv' and length(column1) <= 4 then 'OK'
end as status
from table1
COLUMN1 STATUS
--------- ------
1010101 OK
1012828n
1012828nv
n.....v....
n.V OK
Test data
create table table1 as
select '1010101' column1 from dual union all -- OK numbers
select '1012828n' from dual union all -- invalid
select '1012828nv' from dual union all -- invalid
select 'n.....v....' from dual union all -- invalid
select 'n.V' from dual; -- OK nv
You can use:
select count(*)
from table1
WHERE TRANSLATE(column1, ' 0123456789', ' ') IS NULL
OR LOWER(column1) IN ('nv', 'n.v', 'nv.', 'n.v.');
Which, for the sample data:
CREATE TABLE table1 (column1) AS
SELECT '12345' FROM DUAL UNION ALL
SELECT 'nv' FROM DUAL UNION ALL
SELECT 'NV' FROM DUAL UNION ALL
SELECT 'nV' FROM DUAL UNION ALL
SELECT 'n.V.' FROM DUAL UNION ALL
SELECT '...................n.V.....................' FROM DUAL UNION ALL
SELECT '..nV' FROM DUAL UNION ALL
SELECT 'n..V' FROM DUAL UNION ALL
SELECT 'nV..' FROM DUAL UNION ALL
SELECT 'xyz' FROM DUAL UNION ALL
SELECT '123nv' FROM DUAL;
Outputs:
COUNT(*)
5
or, if you want any quantity of . then:
select count(*)
from table1
WHERE TRANSLATE(column1, ' 0123456789', ' ') IS NULL
OR REPLACE(LOWER(column1), '.') = 'nv';
Which outputs:
COUNT(*)
9
db<>fiddle here

Order versions as numbers

I have a table with file-names and version with subversion of files separated by ..
FNAME, VERSION
A 0.0.10
B 10.12.412
-- For example
create table file_versions as
select chr(mod(level,13)+65) as fname
, decode(mod(level,99),0, '0',
mod(level,10)||'.'||mod(level,500)||'.'||mod(level,14)
)
as version
from dual connect by level < 1001;
I'd like to order files by version, but use versions as numbers
select fname, version from file_versions
order by fname, version
FNAME, VERSION
A 0.0.10
A 0.0.6
...
I'd like don't think about subversion level(there may be one number (0) or more(1.23.14123)). How should I write order by statement ?
I may write something like:
select fname, version from file_versions
order by fname
, to_number(substr(version, 1, instr(version, '.',1,1)-1))
, to_number(substr(version, instr(version, '.',1,1)+1, instr(version, '.',1,2)-instr(version, '.',1,1)-1))
, to_number(substr(version, instr(version, '.',1,2)+1))
But its not so good and will not work if one digit was added to the version string (e.g. 0.0.0.123). Is there a better solution?
You can use regexp_substr():
order by fname,
cast(regexp_substr(version, '[^.]+', 1, 1) as number),
cast(regexp_substr(version, '[^.]+', 1, 2) as number),
cast(regexp_substr(version, '[^.]+', 1, 3) as number)
You may use two regexp first for enhance you group to add 5 zeros to any group. And another one to take last 5 digits from each group. And you get constant length rows and be able to sort it as chars.
with s(txt) as (select '1' from dual
union all
select '1.12' from dual
union all
select '1.12.410' from dual
union all
select rpad('1.12.410',401,'.03') from dual
union all
select rpad('1.12.410',401,'.03')||'.01' from dual
union all
select rpad('1.12.410',401,'.03')||'.02' from dual
)
select txt,regexp_replace(regexp_replace(txt, '(\d+)','00000\1'),'\d+ (\d{5})','\1') from s
order by regexp_replace(regexp_replace(txt, '(\d+)','00000\1'),'\d+(\d{5})','\1')
It will work up to 99999 version or subversion.
More for fun than as a serious suggestion, here's an alternative to parsing the string -- treating the version numbers as inet addresses.
Trickier when you have three levels in your version, but trivial for four levels:
Starting with the idea of:
select a.i::varchar
from (select '192.168.100.128'::inet i union
select '22.168.100.128'::inet) a
order by 1;
i
--------------------
192.168.100.128/32
22.168.100.128/32
(2 rows)
So for three-level versions you can:
with
versions as (
select '1.12.1' v union
select '1.3.100'),
inets as (
select (v||'.0')::inet i
from versions)
select substr(i::varchar,1,length(i::varchar)-5)
from inets
order by i;
substr
---------
1.3.100
1.12.1
(2 rows)
Maybe everyone should have four level versions ...

Index like sql order

I have a column with a string value, something like 1, 1.1, 1.1.2, 1.2, 2, 2.1, 1.3, 1.1.3, one for record, of course, and i want a sentence that returns the records ordered by this field, like a book index
1
1.1
1.1.2
1.1.3
1.2
1.3
2
2.1
Thanks
Use ORDER BY:
CREATE TABLE #tab(col VARCHAR(1000));
INSERT INTO #tab(col)
SELECT '1'
UNION ALL SELECT '1.1'
UNION ALL SELECT '1.1.2'
UNION ALL SELECT '1.1.3'
UNION ALL SELECT '1.2'
UNION ALL SELECT '1.3'
UNION ALL SELECT '2'
UNION ALL SELECT '2.1';
SELECT *
FROM #tab
ORDER BY col;
LiveDemo
EDIT:
Just for fun and experiment solution for SQL Server 2012+:
WITH cte AS (
SELECT col,
CASE LEN(col) - LEN(REPLACE(col, '.', ''))
WHEN 0 THEN col + '.0.0.0'
WHEN 1 THEN col + '.0.0'
WHEN 2 THEN col + '.0'
ELSE col
END AS col_alt
FROM #tab
)
SELECT col
FROM cte
ORDER BY
LEN(PARSENAME(col_alt,4)),
PARSENAME(col_alt,4),
LEN(PARSENAME(col_alt,3)),
PARSENAME(col_alt,3),
LEN(PARSENAME(col_alt,2)),
PARSENAME(col_alt,2),
LEN(PARSENAME(col_alt,1)),
PARSENAME(col_alt,1);
LiveDemo2
If the values between the dots are all single characters (as in the question), then the easiest way is to order by the length of the string and then the string:
order by len(col), col
(In some databases, len might be spelled length.)
Note: this only works when single digits separate the dots. A more general solution requires some knowledge of the database.

How to convert String in SQL (ORACLE)

I try to select from table_1 where ITEM_FIELD_A is not in ITEM_FIELD_B. The Item_FIELD_B value are look as below. I was expecting no COVER_TAPE & SHIPPING_REELS will be selected. But unfortunately, it's not working.
The sql I used to select the table
select * from table_1 where MST.ITEM_FIELD_A not in ITEM_FIELD_B
Question:
In Oracle, is there any function to decode the string. so that the above select statement will not return COVER_TAPE and SHIPPING_REELS??
The IN operator would be used when you wish to compare (or negate) one item in a list such as
WHERE ITEM_FIELD_A NOT IN ('COVER_TAPE', 'SHIPPING_REELS', '')
What you want is the LIKE operator:
WHERE ITEM_FIELD_B NOT LIKE '%' || ITEM_FIELD_A || '%'
Apologies if I got the wildcard wrong, been a while since I last touched Oracle.
Check out below Query:
WITH TAB1 AS
( SELECT 'COVER_TAPE' ITEM_A FROM DUAL
UNION
SELECT 'CARRIER_TAPE' ITEM_A FROM DUAL
UNION
SELECT 'SHIPPING_REELS' ITEM_A FROM DUAL
),
TAB2 AS
(
SELECT 'COVER_TAPE,SHIPPING_REELS' ITEM_B FROM DUAL
)
SELECT ITEM_A, ITEM_B FROM TAB1, TAB2 WHERE INSTR(ITEM_B, ITEM_A) <=0
INSTR will return >0 if same sequence of characters is available.
SQL> with t(x , y) as
2 (
3 select 'A', q'[('A','B','C')]' from dual union all
4 select 'R', q'[('A','B','C','D')]' from dual union all
5 select 'C', q'[('A', 'C','D')]' from dual
6 )
7 select x, y
8 from t where y not like q'[%']'||x||q'['%]'
9 /
X Y
---------- --------------------------------------------------
R ('A','B','C','D')

Select Union SQL

I am using the following query :
select 8 Union Select 0 Union Select 15
to populate the these 3 number in a column. The result I get is:
0
8
15
But I want 8 to come first and then 0 and then 15, e.g.
8
0
15
How do I do this?
Use UNION ALL
E.g.
select 8 UNION ALL Select 0 UNION ALL Select 15
#SimonMartin's answer works for the exact data set you give, but be aware that if your data set contains duplicate values, the UNION ALL will produce different results than UNION.
The UNION operator removes duplicates, whereas the UNION ALL will preserve them (as well as their order, as noted in #SimonMartin's answer).
If you want to combine the functionality of your UNION operator with the ordering capabilities provided by UNION ALL, then you need to start with UNION ALL then filter out the duplicate values yourself:
-- baseline query + 1 duplicate record at the end
with query as
(
select 8 as Val
UNION ALL
Select 0 as Val
UNION ALL
Select 15 as Val
UNION ALL
Select 0 as Val
)
-- now add row numbers
, queryWithRowNumbers as
(
select row_number() over (order by (select 0)) as rn, Val
from query
)
-- finally, get rid of the duplicates
select Val from (
select Val, min(rn) as minRn
from querywithrownumbers
group by val
) q
order by minRn
This will give results of
8
0
15
whereas if you ONLY use UNION ALL you will end up with
8
0
15
0