I want to export data from oracle database to csv, and I am putting a number filter on a varchar column. It thows ORA:01722 error. Please suggest - oracle

Select count(*) from table where loc between 300 to 400.
loc is a varchar column.
it is not selecting all the data
checking the count, gives ORA :01722 error
exporting the results with error.
Edit from comment:
loc contains values less than 300, more than 400, and alphanumeric like 'GT' , '3KT1'

loc is a varchar column.
[From comment] The Loc column has char type value also like GJ, 3KT1
LOC contains values which are not convertible to numbers. This matters because your WHERE clause predicates are defined as numbers, so Oracle applies an implicit to_number(loc) to the query. This is why using proper data types is best practice: it doesn't help you now but please learn the lesson, and use NUMBER columns for numeric data.
In the meantime you have several options to deal with your shonky data model.
If you're lucky enough to be using Oracle 12c R2 you can use the new VALIDATE_CONVERSION() function to exclude values of loc which can't be cast to numbers. Find out more
If you're using an earlier version of Oracle you can build your own function:
create or replace function is_number
(p_str in varchar2) return number
is
n number;
rv number;
begin
begin
n := to_number(p_str);
rv := 1;
exception
when invalid_number then
rv := 0;
end;
return rv;
end;
The weakest option would be casting the predicates to strings. where loc between '300' to '400' would include '3000', '4' and various other values you probably don't want.
Here is a LiveSQL demo (free Oracle Technet account required, alas).

Your current query is trying to compare a varchar to a number. So it tries to convert the varchar to a number on the fly. This is called implicit conversion.
You should make it compare a varchar to a varchar.
Use single quotes so that you are comparing to varchars, not numbers
Select count(*) from table where loc between '300' to '400'
Then go and read about implicit conversion
Based on the update to your question, this column is a legitimate varchar and should not be converted to a numeric data type.
However you do need to work out whether you are incorrectly storing different types of data in the same column

Related

Writing a Version Number Function in PL/SQL

I want to write a function that will give me the next version number for a table. The table stores the existing version on each record. For example,
I have the cat table
cats
seqid 1
name Mr Smith
version number 1.2b.3.4
How can I write a program that will be able to increment these values based on various conditions?
This is my first attempt
if v_username is not null
then v_new_nbr = substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
should be 1.2b.3.5
substr(v_cur_nbr, 1,7)||to_number(substr(v_cur_nbr, 8,1))+1
This hurls ORA-01722: invalid number. The reason is a subtle one. It seems Oracle applies the concatenation operator before the additions, so effectively you're adding one to the string '1.2b.3.4'.
One solution is using a TO_CHAR function to bracket the addition with the second substring before concatenating the result with the first substring:
substr(v_cur_nbr, 1,7) || to_char(to_number(substr(v_cur_nbr, 8,1))+1)
Working demo on db<>fiddle.
Incidentally, a key like this is a bad piece of data modelling. Smart keys are dumb. They always lead to horrible SQL (as you're finding) and risk data corruption. A proper model would have separate columns for each element of the version number. We can use virtual columns to concatenate the version number for display circumstances.
create table cats(
seqid number
,name varchar2(32)
,major_ver_no1 number
,major_ver_no2 number
,variant varchar2(1)
,minor_ver_no1 number
,minor_ver_no2 number
,v_cur_nbr varchar2(16) generated always as (to_char(major_ver_no1,'FM9') ||'.'||
to_char(major_ver_no2,'FM9') ||'.'||
variant ||'.'||
to_char(minor_ver_no1,'FM9') ||'.'||
to_char(minor_ver_no2,'FM9') ) );
So the set-up is a bit of a nause but incrementing the version numbers is a piece of cake.
update cats
set major_ver_no1 = major_ver_no1 +1
, major_ver_no2 = 0
, variant = 'a';
There's a db<>fiddle for that too.
Try searching mask for TO_NUMBER to be able to get the decimal number, this small example might help:
CREATE TABLE tmp_table (version varchar2(100));
INSERT INTO tmp_table(version) VALUES ('1.2b.3.4');
DECLARE
mainVersion NUMBER;
subVersion NUMBER;
currentVersion VARCHAR2(100);
BEGIN
SELECT version INTO currentVersion FROM tmp_table;
mainVersion := TO_NUMBER(SUBSTR(currentVersion,1,3),'9.9') + 0.1;
subVersion := TO_NUMBER(SUBSTR(currentVersion,6,3),'9.9') + 1.1;
UPDATE tmp_table SET version = (mainVersion||'b.'||subVersion);
END;

Oracle user defined records

In Oracle PL/SQL records we can use anchor datatypes (including %TYPE and %ROWTYPE) to define the fields.
When I populate a record from a query, in my select clause I want type conversion. Is that possible using an Oracle built-in function or some other approach?
In this example scenario I am using a simple decode function to perform a conversion:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITIAL_STATUS EMPLOYEE_MT.MARITAL_STATUS%TYPE);
EMPLOYEE_NAME TEST_RECORD;
BEGIN
SELECT EMP.FIRST_NAME,
EMP.LAST_NAME,
DECODE(EMP.MARITAL_STATUS, 1, 'MARRIED', 0, 'UN-MARRIED')
INTO EMPLOYEE_NAME
FROM EMPLOYEE_MT EMP
WHERE EMP.EMPLOYEE_ID = 1;
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITIAL_STATUS);
END;
which gets error:
ORA-06502: PL/SQL: numeric or value error: character to number conversion error
ORA-06512: at line 9
You have defined your record type with the maritial_status (shouldn't that be marital_status?) field using the same data type as the table column. From your decode that appears to be a number data type. You're then trying to set the record's field value to a string, either 'MARRIED' or 'UN-MARRIED', when that field is expecting a number. Clearly neither of those strings can be converted to a number, hence the error you're getting.
If you want the record to store the string value, you'll have to define it like that - explicitly as a string, rather than using %TYPE:
DECLARE
TYPE TEST_RECORD IS RECORD(
FIRST_NAME EMPLOYEE_MT.FIRST_NAME%TYPE,
LAST_NAME EMPLOYEE_MT.LAST_NAME%TYPE,
MARITAL_STATUS VARCHAR2(10));
...
DBMS_OUTPUT.put_line(EMPLOYEE_NAME.MARITAL_STATUS);
...
You can't do that automatically using %TYPE as the data type just doesn't match. You've explicitly told Oracle that you want the field's data type to be a number, so Oracle isn't going to let you put a string in that field instead. It isn't about there not being a built-in function, it just doesn't make sense.
This also means you can't use %ROWTYPE if you're changing the data type either (unless your modified value can be implicitly converted back to the column data type).

Apex Interactive Report ORA-1843

I'm trying to display at oracle apex IR query, a biggest date from 3 different tables, using greatest function to choice the biggest. For that, created two varchar2 type functions.
One of them return the date converted to char, of a selected table like this:
create or replace FUNCTION RETURN_DATES (key in number, parameter in NUMBER)
RETURN VARCHAR2 IS
...
BEGIN
case parameter
when 1 then
select distinct MAX(mydate) into seal from table1 v WHERE v.num_contract = number_contract;
return NVL(TO_CHAR(TO_DATE (seal),'mm/dd/yyyy'),'01/01/1980')
when 2 then
select... from table2 x WHERE...
...
END;
The second function calls that one using a greatest function between the parameter calls, which refers to case statement:
create or replace FUNCTION RETURN_BIGGEST_DATE(key in number) return VARCHAR2 IS
...
BEGIN
select greatest (RETURN_DATES(key,1),RETURN_DATES(key,2),RETURN_DATES(key,3)) into greatest_date from dual;
return greatest_date;
...
END;
When i call it on sql commands it's works fine, but at an interactive report its fails, returning the ORA-1843, using the quite same query.
Could anyone help?
Simply modifying the RETURN_DATES returning DATE
and the seal variable, of date type, without conversions, works fine at apex IR!
I noticed when a date column is relayed on ir query, there are an extra parameter on Column filter, named Date Ranges. It was the two way traffic to start simulations and change the returning type of the that function.
No more changes was required:
create or replace FUNCTION RETURN_DATES (key in number, parameter in NUMBER)
RETURN DATE IS
...
select distinct MAX(mydate) into seal from table1 v WHERE v.num_contract = number_contract;
return NVL(seal),'01/01/1980')

Determining input datatype Oracle/PLSQL

I am writing a PLSQL 'INSTEAD OF INSERT' Trigger whereby the ID field (GID) can be inserted as either a string or a number. If the GID value is a string I would like to attempt to convert that into the correct GID (number) otherwise if a number is input the script will continue.
The part I am struggling with here is determining the datatype of ':New.CHART_GID' - is this possible in PLSQL? I can't check for chars in the string as the string may only contain numbers in some instances.
Thanks.
You can use TRANSLATE to check if there is something other as numbers:
CREATE OR REPLACE TRIGGER trigger_name
INSTEAD OF INSERT
ON table_name
FOR EACH ROW
DECLARE
vGID INTEGER;
...... other things
BEGIN
IF :New.CHART_GID is not null AND TRANSLATE(:New.CHART_GID,'0123456789',' ') is null THEN
vGID := TO_NUMBER(:New.CHART_GID);
.... do what you want with number
ELSE
... do what you want with not number
END IF;
.... other things
END;
CHART_GID have to be varchar2 in the view
I realise what I was trying to achieve was actually not possible. The solution for me was actually to join the Chart_no into the view and insert into either that field of the GID. If I input a Chart_no the GID field would be automatically populated and the same for if I input a GID.

sqlloader date conversion: inconsistent data type

Am trying to load some data to an Oracle database using SQLLoader but I keep getting the errors ORA-00932: inconsistent datatypes: expected DATE got NUMBER or expecting char got date
The date comes in the format of e.g. 20130815 and it is to be stored into a database with a column of type Date.
The logic should be that if someone passes an invalid date a null or say an invalid month in the date string e.g. 20131301, then I want to insert a default date e.g. 19990101.
Here is the SQLLoader code I have so far:
COLUMN_DOB DATE 'YYYYMMDD' "CASE:COLUMN_DOB WHEN 'isdate(:COLUMN_DOB)=1' THEN :COLUMN_DOB ELSE to_date('19000101', 'YYYYMMDD') END",,
The immediate issue is that you're calling to_date() on the fixed value. The then part is returning the original string value of the date from the file; the else is returning a date; which leads to the 'expecting char got date' message.
If you remove the to_date() part then it will load all the values as 1900-01-01, because you have the case statement wrong. You're comparing the :COLUMN_DOB value with the string 'isdate(:COLUMN_DOB)=1'. It isn't calling the function, it's a fixed string, and your date field is never going to exactly match that text. So the case always goes into the else and gets the fixed value. You also seem to be mixing up the two forms of case statement.
So it should be:
... "CASE WHEN isdate(:COLUMN_DOB)=1 THEN :COLUMN_DOB ELSE '19000101' END"
Which, assuming you've built an isdate() function - since that is not an Oracle built-in - with a default format mask, something like this one based on an AskTom version:
create or replace function isdate (p_string in varchar2,
p_fmt in varchar2 := 'YYYYMMDD')
return number as
l_date date;
begin
if l_date is null then
return 0;
end if;
l_date := to_date(p_string, p_fmt);
return 1;
exception
when others then
return 0;
end;
/
... will put in valid dates as supplied, and invalid dates as 1900-01-01. The null check also means nulls will be inserted as 1900-01-01; it's perhaps simpler to have it here than to try to handle that separately in the control file.
You could maybe simplify it further by having a function that tries to convert the string and returns a date, and just calling that in the control file, without the date mask or the case statement. That approach is covered in that AskTom link too.
Personally I'd probably prefer to see the column left null rather than giving it a magic number. It isn't impossible to have someone with a valid DOB of 1900-01-01.
If you are creating a new function anyway, you could do this instead:
create or replace function my2date(p_str in varchar2) return date is
begin
return to_date(nvl(p_str, '19000101'), 'YYYYMMDD');
exception
when others then -- just about acceptable here
return date '1900-01-01';
end;
/
which you can execute from SQL*Plus, SQL Developer, Toad or whatever client you're using. And then you control file would have:
COLUMN_DOB "my2date(:COLUMN_DOB)"
I don't think there's a way of doing this without using a function. If you used an external table instead then you could use an anonymous block to do the conversion I suppose, but if you're stuck with SQL*Loader then I believe a function is the only way to stop a bad date causing the whole row to be rejected.

Resources