How to update field value by determining each digit in field? - sql

Although I saw update statements to update field based on existing values, I could not find anything similar to this scenario:
Suppose you have a table with only one column of number(4) type. The value in the first record is 1010.
create table stab(
nmbr number(4)
);
insert into stab values(1010);
For each digit
When the digit is 1 -- add 3 to the digit
When the digit is 0 -- add four to the digit
end
This operations needs to be completed in a single statement without using pl/sql.
I think substr function need to be used but don't know how to go about completing this.
Thanks in advance.

SELECT DECODE(SUBSTR(nmbr,1,1), '1', 1 + 3, '0', 0 + 4) AS Decoded_Nmbr
FROM stab
ORDER BY Decoded_Nmbr
Is that what you are after?

So, it seems you need to convert every 0 and 1 to a 4, and leave all the other digits alone. This seems like a string operation (and the reference to "digits" itself suggests the same thing). So, convert the number to a string, use the Oracle TRANSLATE function (see the documentation), and convert back to number.
update stab
set nmbr = to_number(translate(to_char(nmbr, '9999'), '01', '44'))
;

assuming its always a 4 digit #; you could use substring like below
-- postgres SQL example
SELECT CASE
WHEN a = 0 THEN a + 4
ELSE a + 3
end AS a,
CASE
WHEN b = 0 THEN b + 4
ELSE b + 3
end AS b,
CASE
WHEN c = 0 THEN c + 4
ELSE c + 3
end AS c,
CASE
WHEN d = 0 THEN d + 4
ELSE c + 3
end AS d
FROM ( SELECT Substr( '1010', 1, 1 ) :: INT AS a,
Substr( '1010', 2, 1 ) :: INT b,
Substr( '1010', 3, 1 ) :: INT c,
Substr( '1010', 4, 1 ) :: INT d )a
--- Other option may be (tried in postgreSQL :) ) to split the number using regexp_split_to_table into rows;then add individual each digit based on the case statement and then concat the digits back into a string
SELECT array_to_string ( array
(
select
case
WHEN val = 0 THEN val +4
ELSE val +3
END
FROM (
SELECT regexp_split_to_table ( '101010','' ) ::INT val
) a
) ,'' )

My answer to the interview question would have been that the DB design violates the rules of normalization (i.e. a bad design) and would not have this kind of "update anomaly" if it were properly designed. Having said that, it can easily be done with an expression using various combinations of single row functions combined with the required arithmetic operations.

Related

remove last n characters from a varchar in SQL

I am trying to remove the last n characters from a string. I tried this:
replace( str, right(str, 3), '' )
But it fails on str where the pattern repeats more than once. 888106106. In this case I get 888, instead of 888106
Now I am using
left (str, length(str)-3)
Is there a more efficient away of achieving this?
If you fancy a regex based solution:
regexp_replace(str,'...$','')
It will leave strings with < 3 characters unchanged
So checking that LEFT and/or SUBSTR work equally (I assume LEFT is faster):
select
column1
,left(column1, length(column1) -3) as r1
,substr(column1, 0, length(column1) -3) as r2
from values
('abc123')
,('ab123')
,('a123')
,('123')
,('12')
,('1')
,('')
,(null);
gives:
COLUMN1
R1
R2
abc123
abc
abc
ab123
ab
ab
a123
a
a
123
null
null
12
null
null
1
null
null
''
null
null
null
null
null
so no checks are needed, nice to know.
if you do some perf testing:
create database test;
create schema test.test;
create or replace table test.test.many_string as
select seq8()::text as a
from table(generator(ROWCOUNT => 10000000));
ALTER SESSION SET USE_CACHED_RESULT = false;
select sum(length(left(a, length(a) -3))) from test.test.many_string;
select sum(length(substr(a, 0, length(a) -3))) from test.test.many_string;
after running them both a couple of times on my x-small, I get results in the order of 300ms, so these are equal.
So it seems you have a fast solution, and easy to read.
In SQL Server, that's the most effective way.
Note that you should do one of:
assert that all input strings will be length>2 [a bit lazy]
handle the error where 1 of the rows has a length<3 and the query terminates early [a bit shoddy]
use a case statement to handle the case where length < 3 [the preferred approach]
CASE
WHEN LENGTH(str) > 2 THEN LEFT(str, LENGTH(str) - 3)
ELSE str
END
Other flavours of SQL you may have to work without the case statement.

Check specific integer is even

I want to know if the 4th integer in the ID, is even, or if its odd.
If the 4th number is even (if the number is either 0,2,4,6,8 I want to put the ID into a new column named 'even'
IF the 4th number is odd, the column should have the name 'Odd'
select ID as 'Female'
from Users2
where ID LIKE '%[02468]'
This shows if any of the numbers are even. I want to specify the 4th number
Try this:
select *, OddOrEven = iif(substring(ID,4,1) in ('0','2','4','6','8') , 'Even', 'Odd') from Users2
This will tell you whether the 4th character is Odd or Even.
This is of course assuming that the 4th character of ID column will be numeric.
To make it permanently part of the table, you can add a computed column as shown below.
alter table Users2
add OddOrEven as iif(substring(ID,4,1) in ('0','2','4','6','8'), 'Even', 'Odd')
Substring the character you are interested in
Convert to an int
Check whether modulus 2 returns 0 (i.e. even).
select id
, case when convert(int,substring(id, 4, 1)) % 2 = 0 then 'Even' else 'Odd' end
from Users;
Example:
select id
, case when convert(int,substring(id, 4, 1)) % 2 = 0 then 'Even' else 'Odd' end
from (values ('4545-4400'), ('4546-4400')) X (id);
Returns
id
4545-4400
Odd
4546-4400
Even
Thats assuming there is always a 4th character. If not you would need to check for it.
You were close, but only need to check a single character against a set of characters:
where Substring( Id, 4, 1 ) like '[02468]'
Note that there is no wildcard (%) in the pattern.
It can be used in an expression like:
case when Substring( Id, 4, 1 ) like '[02468]' then 'Even' else 'Odd' end as Oddity

How to auto generate a ID with random numbers in sql server

For example I had a column named 'ID'
I want to get the output as
ID
---
ABCXX708
ABCXX976
ABCXX654
ABCXX081
In short ABCXX should be common for every row but the remaining 3 numbers should be random and integer..
with t (n) as (select 0 union all select n+1 from t where n <100)
select 'ABC'
+ format(n,'00')
+ cast(cast(rand(cast(newid() as varbinary(100)))*10 as int) as char(1))
from t
Alternative solution
with t (n) as (select 0 union all select n+1 from t where n <100)
select 'ABC'
+ right ('0' + cast(n as varchar(2)),2)
+ cast(cast(rand(cast(newid() as varbinary(100)))*10 as int) as char(1))
from t
You can write like this
select 'ABCXX'+CAST(FLOOR(RAND()*(1000-100)+100) as varchar(3)) 'id'
With the RAND() function you can get Random numbers. And For the 'ABCXX' you can follow your previous logic.
SELECT CAST(RAND()*10.00 AS INT)
The above RAND() function will give values between 0.0 to 1.0 in decimals every time you hit the Statement. To make it for a single digit Multiply with 10 and Cast it to INT to remove the next decimal values.
Reference " MSDN
Since SQL Server 2012 you have FORMAT function and SEQUENCE object. Hence the below query will work.
First you need to create a Sequence object.
CREATE SEQUENCE DemopSeq
START WITH 1
INCREMENT BY 1;
Then the following query will generate results as per your requirement.
SELECT CONCAT('ABC',FORMAT(NEXT VALUE FOR DemopSeq, '00'),ABS(Checksum(NewID()) % 10))
Hope this helps.

SQL SELECT statement with Cast, LPad and Max

Are there any issues with this select statement ??
SELECT SUBSTR(FIELD_A,10,3) as MOVID,
MAX(LPAD((CAST(SUBSTR(FIELD_A,-3,3) as INT) + 1 ), 3, 0)) as NEXTMOVID
FROM ...
The field is a VARCHAR2.
I want to maintain 3 characters(which are numbers) and add 1 and concatenate with another varchar2
retrieve a portion of the FIELD_A
convert is to an integer
add 1
Left Pad it to 3 characters with 0
Grab the MAX
Later on I concatenate with another field
Wondering if there was a better way to do this ??
As I understand, you need following:
SELECT SUBSTR(FIELD_A,10,3) as MOVID,
to_char(to_number(SUBSTR(FIELD_A, -3, 3)) + 1), '000') as NEXTMOVID
FROM ...

Sorting a varchar column as integer value in Oracle query

I have a column DOOR which is a VARCHAR2 in a Table ADDRESS. I want to sort the column DOOR.
DOOR contains only two digits and no - sign
currently when I use the query
select sname, door, zip from address a order by door
I get the following result:
a
b
1
10
11
2
3
31
But I want the result to look like this:
a
b
1
2
3
10
11
31
I tried to convert DOOT into a numeric value using to_number as
select sname, to_number(door) dnr, zip from address a order by dnr
but it is giving me an error ORA-01722.
You can do this with the logic in the order by:
order by (case when regexp_like(door, '^[0-9]*$') = 0 then 1 else 0 end) desc,
(case when regexp_like(door, '^[0-9]*$') = 0 then door end),
length(door),
door
This first puts the non-numeric values first. The second clauses sorts these alphabetically. The third and fourth are for numbers. By sorting for the length before the value, you will get the numbers in order.
ORA-01722 error coming because of value 'a' ,'b',
Go for custom function which will take varchar and return number to convert , use custom function in order by clause of your query.
CREATE OR REPLACE FUNCTION tonumber (no_str varchar2)
RETURN number IS
num number := 0;
BEGIN
RETURN to_number(no_str);
EXCEPTION -- exception handlers begin A < B < 1 < 2
WHEN value_error THEN -- handles all other errors
dbms_output.put_line('in other exception catch.');
CASE
WHEN ( upper(no_str) = 'B' ) THEN return -1;
WHEN ( upper(no_str) ='A') THEN return -2;
ELSE return -999;
END CASE;
END;
Add when condition as in required. now assumed it can have only A B. for rest it will return default.
(This approach assumes that there aren't any mixed values like "234abc567".)
So, going old school...just 0-pad the strings to the maximum length of the column so that they'll sort properly as characters. But, to get the "non-numeric" values to sort first, nullify non-numeric values, put the NULLs first and padded values after that.
select door
from address
order by case when replace(translate(door, '012345679', '0000000000'), '0', '') is null
then lpad(door, 10, '0') -- value was entirely made of digits (change the 10 to the max width of the column)
else null
end nulls first
, door -- sorting within the group of NULL rows generated in the previous expression.
use the below query
SELECT PUMP_NAME
FROM MASTER.PUMPS
ORDER BY LPAD(PUMP_NAME, 10);