Is there a way to pad 0s before numbers stored as VARCHAR in DB2?
Like this:
some_column result
----------- ------
12 ==> 00012
123 ==> 00123
6454 ==> 06454
If the function LPAD is available:
SELECT LPAD(some_column, 5, '0')
FROM table
Otherwise you can use a combination of RIGHT and REPEAT:
SELECT RIGHT(REPEAT('0', 5) || some_column, 5)
FROM table
some_column | Concatenate five '0's to some_column | Return the five rightmost characters
------------------------------------------------------------------------
12 => 0000012 => 00012
123 => 00000123 => 00123
6454 => 000006454 => 06454
Related
I am looking for numeric datatype with limited digits
(before and after the decimal point)
The function kills only digits after the decimal point. (PG version >= 13)
create function num_flex( v numeric, d int) returns numeric as
$$
select case when v=0 then 0
when v < 1 and v > -1 then trim_scale(round(v, d - 1 ) )
else trim_scale(round(v, d - 1 - least(log(abs(v))::int,d-1) ) ) end;
$$
language sql ;
For testing:
select num_flex( 0, 6)
union all
select num_flex( 1.22000, 6)
union all
select num_flex( (-0.000000123456789*10^x)::numeric,6)
from generate_series(1,15,3) t(x)
union all
select num_flex( (0.0000123456789*10^x)::numeric,6)
from generate_series(1,15,3) t(x) ;
It runs,
but have someone a better idea or find a bug (a situation, that is not implemented)?
The next step is to integrate this in PG, so that I can write
select 12.123456789::num_flex6 ;
select 12.123456789::num_flex7 ;
for a num_flex datatype with 6 or 7 digits.
with types from num_flex2 to num_flex9. Is this possible?
There are a few problems with your function:
Accepting negative digit counts (parameter d). num_flex(1234,-2) returns 1200 - you specified you want the function to only kill digits after decimal point, so 1234 would be expected.
Incorrect results between -1 and 1. num_flex(0.123,3) returns 0.12 instead of 0.123. I guess this might also be desired effect if you do want to count 0 to the left of decimal point. Normally, that 0 is ignored when a number's precision and scale are considered.
Your counting of digits to the left of decimal point is incorrect due to how ::int rounding works. log(abs(11))::int is 1 but log(abs(51))::int is 2. ceil(log(abs(v)))::int returns 2 in both cases, while keeping int type to still work as 2nd parameter in round().
create or replace function num_flex(
input_number numeric,
digit_count int,
is_counting_unit_zero boolean default false)
returns numeric as
$$
select trim_scale(
case
when input_number=0
then 0
when digit_count<=0 --avoids negative rounding
then round(input_number,0)
when (input_number between -1 and 1) and is_counting_unit_zero
then round(input_number,digit_count-1)
when (input_number between -1 and 1)
then round(input_number,digit_count)
else
round( input_number,
greatest( --avoids negative rounding
digit_count - (ceil(log(abs(input_number))))::int,
0)
)
end
);
$$
language sql;
Here's a test
select *,"result"="should_be"::numeric as "is_correct" from
(values
('num_flex(0.1234 ,4)',num_flex(0.1234 ,4), '0.1234'),
('num_flex(1.234 ,4)',num_flex(1.234 ,4), '1.234'),
('num_flex(1.2340000 ,4)',num_flex(1.2340000 ,4), '1.234'),
('num_flex(0001.234 ,4)',num_flex(0001.234 ,4), '1.234'),
('num_flex(123456 ,5)',num_flex(123456 ,5), '123456'),
('num_flex(0 ,5)',num_flex(0 ,5), '0'),
('num_flex(00000.00000 ,5)',num_flex(00000.00000 ,5), '0'),
('num_flex(00000.00001 ,5)',num_flex(00000.00001 ,5), '0.00001'),
('num_flex(12345678901 ,5)',num_flex(12345678901 ,5), '12345678901'),
('num_flex(123456789.1 ,5)',num_flex(123456789.1 ,5), '123456789'),
('num_flex(1.234 ,-4)',num_flex(1.234 ,4), '1.234')
) as t ("operation","result","should_be");
-- operation | result | should_be | is_correct
----------------------------+-------------+-------------+------------
-- num_flex(0.1234 ,4) | 0.1234 | 0.1234 | t
-- num_flex(1.234 ,4) | 1.234 | 1.234 | t
-- num_flex(1.2340000 ,4) | 1.234 | 1.234 | t
-- num_flex(0001.234 ,4) | 1.234 | 1.234 | t
-- num_flex(123456 ,5) | 123456 | 123456 | t
-- num_flex(0 ,5) | 0 | 0 | t
-- num_flex(00000.00000 ,5) | 0 | 0 | t
-- num_flex(00000.00001 ,5) | 0.00001 | 0.00001 | t
-- num_flex(12345678901 ,5) | 12345678901 | 12345678901 | t
-- num_flex(123456789.1 ,5) | 123456789 | 123456789 | t
-- num_flex(1.234 ,-4) | 1.234 | 1.234 | t
--(11 rows)
You can declare the precision (total number of digits) of your numeric data type in the column definition. Only digits after decimal point will be rounded. If there are too many digits before the decimal point, you'll get an error.
The downside is that numeric(n) is actually numeric(n,0), which is dictated by the SQL standard. So if by limiting the column's number of digits to 5 you want to have 12345.0 as well as 0.12345, there's no way you can configure numeric to hold both. numeric(5) will round 0.12345 to 0, numeric(5,5) will dedicate all digits to the right of decimal point and reject 12345.
create table test (numeric_column numeric(5));
insert into test values (12345.123);
table test;
-- numeric_column
------------------
-- 12345
--(1 row)
insert into test values (123456.123);
--ERROR: numeric field overflow
--DETAIL: A field with precision 5, scale 0 must round to an absolute value less than 10^5.
I have a table like below
When I select item_no>'1623G' from above table
I want to print the result below
1623H | 1623I | 1666 | 1674 | 1912 | 1952 | 1953
I am trying below command
select * from t where substring(item_no,'([0-9]+)') :: int > 1623G
But it's not giving result
please help
I would go the regexp way:
demo:db<>fiddle
WITH cte AS (
SELECT
item_no,
regexp_replace(item_no, '\D', '', 'g')::int AS digit,
regexp_replace(item_no, '\d', '', 'g') AS nondigit,
regexp_replace('200a', '\D', '', 'g')::int AS compare_digit,
regexp_replace('200a', '\d', '', 'g') AS compare_nondigit
FROM t
)
SELECT
item_no
FROM
cte
WHERE
(digit > compare_digit) OR (digit = compare_digit AND nondigit > compare_nondigit)
Splitting both values (the row value and the comparing one) into its both parts (digits and non-digits) and compare each part separately.
I am curious if there is better solution.
You can use CONVERT_TO as:
testdb1=# CREATE TABLE t (item_no varchar(20));
CREATE TABLE
testdb1=# INSERT INTO t VALUES('2'),('20'),('200'),('200a'),('200b'),('200c'),('2000');
INSERT 0 7
testdb1=# SELECT * FROM t;
item_no
---------
2
20
200
200a
200b
200c
2000
(7 rows)
testdb1=# select * from t where substring(convert_to(item_no,'SQL_ASCII')::text,3)::int > substring(convert_to('2a','SQL_ASCII')::text,3)::int;
item_no
---------
200
200a
200b
200c
2000
(5 rows)
testdb1=# select * from t where substring(convert_to(item_no,'SQL_ASCII')::text,3)::int > substring(convert_to('150','SQL_ASCII')::text,3)::int;
item_no
---------
200
200a
200b
200c
2000
(5 rows)
I think this might be a PostgreSQL bug but I'm posting it here in case I'm just missing something. When my WHERE clause has a NOT IN () clause, having null in the list makes the clause no longer truthy. Below is a dumbed down example of my issue.
=# select 1 where 1 not in (1);
?column?
----------
(0 rows)
=# select 1 where 1 not in (2);
?column?
----------
1
(1 row)
=# select 1 where 1 not in (null);
?column?
----------
(0 rows)
=# select 1 where 1 not in (null, 2);
?column?
----------
(0 rows)
=# select 1 where 1 not in (2, null);
?column?
----------
(0 rows)
=# select 1 where 1 not in (2, 3);
?column?
----------
1
(1 row)
So where 1 not in (1) returns 0 rows as expected since 1 is in the list, where 1 not in (2) returns 1 row as expected since 1 is not in the list, but where 1 not in (null) returns 0 rows even though 1 is not in the list.
This is not a PostgreSQL bug.
The problem is that NOT IN is just the short version for testing all inequalities one by one.
1 NOT IN (null, 2) is equivalent to:
1 <> null
AND
1 <> 2
However, NULL is a special value, so 1 <> null is itself NULL (not TRUE). See the documentation:
Do not write expression = NULL because NULL is not “equal to” NULL. (The null value represents an unknown value, and it is not known whether two unknown values are equal.)
As far as I know that's the standard SQL behaviour.
PostgreSQL has an additional keyword to check whether a value is different from null:
1 IS DISTINCT FROM NULL would be TRUE.
I have a select statement and in that select statement I have a few columns on which I perform basic calculations (e.g. [Col1] * 3.14). However, occasionally I run into non-numeric values and when that happens, the whole stored procedure fails because of one row.
I've thought about using a WHERE ISNUMERIC(Col1) <> 0, but then I would be excluding information in the other columns.
Is there a way in TSQL to somehow replace all stings with NULL or 0??
Something like...
SELECT blah1, blah2, blah3
CASE WHEN ISNUMERIC(Col1) = 1 THEN [Col1] * 3.14 ELSE NULL END as whatever
FROM your_table
A case can also be made that..
The non-numeric values should be converted to numeric or NULL if that's what's expected in the column, and
If numbers are expected then the column should be a numeric data type in the first place and not a character data type, which allows for these types of errors.
I prefer Try_Cast:
SELECT
someValue
,TRY_CAST(someValue as int) * 3.14 AS TRY_CAST_to_int
,TRY_CAST(someValue as decimal) * 3.14 AS TRY_CAST_to_decimal
,IIF(ISNUMERIC(someValue) = 1, someValue, null) * 3.14 as IIF_IS_NUMERIC
FROM (values
( 'asdf'),
( '2' ),
( '1.55')
) s(someValue)
ISNUMERIC is a terrible way to do this, as there are far too many things that identify as NUMERIC which are not able to be multiplied by a non-MONEY data type.
https://www.brentozar.com/archive/2018/02/fifteen-things-hate-isnumeric/
This fails miserably, as '-' is a numeric...
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT CASE WHEN ISNUMERIC(numerics) = 1 THEN numerics * 3.14 ELSE NULL END
FROM #example;
Try TRY_CAST instead (albeit amend your DECIMAL precision to suit your needs):
DECLARE #example TABLE (numerics VARCHAR(10));
INSERT INTO #example VALUES ('-')
SELECT TRY_CAST(numerics AS decimal(10,2)) * 3.14 FROM #example;
trycast will test for a specfic type
declare #T table (num varchar(20));
insert into #T values ('12'), ('3.14'), ('5.6E12'), ('$120'), ('-'), (''), ('cc'), ('aa'), ('bb'), ('1/5');
select t.num, ISNUMERIC(t.num) as isnumeric
, isnull(TRY_CONVERT(smallmoney, t.num), 0) as smallmoney
, TRY_CONVERT(float, t.num) as float
, TRY_CONVERT(decimal(18,4), t.num) as decimal
, isnull(TRY_CONVERT(smallmoney, t.num), TRY_CONVERT(float, t.num)) as mix
from #T t
num isnumeric smallmoney float decimal
-------------------- ----------- --------------------- ---------------------- ---------------------------------------
12 1 12.00 12 12.0000
3.14 1 3.14 3.14 3.1400
5.6E12 1 0.00 5600000000000 NULL
$120 1 120.00 NULL NULL
- 1 0.00 NULL NULL
0 0.00 0 NULL
cc 0 0.00 NULL NULL
aa 0 0.00 NULL NULL
bb 0 0.00 NULL NULL
1/5 0 0.00 NULL NULL
interesting the last still fails
I'm trying to remove trailing spaces without success:
select trim(trailing ' ' from '1234 '), '56' from sysibm.sysdummy1;
1 2
--------------------------- --
1234 56
What am I missing?
It is documented that trim will leave the data type as is - check out the Knowledge Center
describe "select trim( '1234 '), '56' from sysibm.sysdummy1"
Column Information
Number of columns: 2
SQL type Type length Column name Name length
-------------------- ----------- ------------------------------ -----------
448 VARCHAR 27 1 1
448 VARCHAR 2 2 1
If you want to change the look / data type of the result you can cast it to the length or data type you need
select varchar(trim( '1234 '), 5), '56' from sysibm.sysdummy1"
1 2
----- --
1234 56
1 record(s) selected.
Do you mean:
select trim(trailing ' ' from '1234 ')|| '56' from sysibm.sysdummy1
Specifically:
select length(trim(trailing ' ' from '1234 ')|| '56') from sysibm.sysdummy1;
1
-----------
6
1 record(s) selected
.
Other method, if your db2 version enable RPAD function (V7R2 on iserie) :
select rpad('1234 ', 5, ' '), '56'
from sysibm.sysdummy1