Issue in adding decimal values stored as varchar - sql

I have a staging table with two amount columns, created as varchar2 types to allow import from excel. Later these fields are validated to make sure they contains numeric data & summation of values for each row should greater than zero, before making the updates to actual table.
I have a function to validate whether the column contains any non numeric data, which works as expected.
FUNCTION f_isnumber (pin_string IN VARCHAR2)
RETURN INT
IS
v_new_num NUMBER;
BEGIN
IF TRIM (pin_string) IS NOT NULL
THEN
v_new_num := TO_NUMBER (pin_string);
RETURN 1;
END IF;
RETURN 0;
EXCEPTION
WHEN VALUE_ERROR
THEN
RETURN 0;
END f_isnumber;
Next is I wanted to list all the rows where value of two amount field is zero. Below query works where I am just selecting the total of both columns.
WITH CTE1
AS (SELECT TO_NUMBER (PURCH_AMT) + TO_NUMBER (SELL_AMT) AS Total, ROW_ID
FROM STG_MAINTENANCE
WHERE PKG_MAINTENANCE.f_isnumber (PURCH_AMT) > 0
AND PKG_MAINTENANCE.f_isnumber (SELL_AMT) > 0)
SELECT TO_NUMBER (Total), CTE1.*
FROM CTE1
But as soon as I add where clause, the query fails with error "ORA-01722: invalid number"
WITH CTE1
AS (SELECT TO_NUMBER (PURCH_AMT) + TO_NUMBER (SELL_AMT) AS Total, ROW_ID
FROM STG_MAINTENANCE
WHERE PKG_MAINTENANCE.f_isnumber (PURCH_AMT) > 0
AND PKG_MAINTENANCE.f_isnumber (SELL_AMT) > 0)
SELECT TO_NUMBER (Total), CTE1.*
FROM CTE1
WHERE Total = 0
Table contains both valid numbers, spaces & valid decimals, zero etc. I was hoping inner where clause will eliminate all non numeric values & I would only get valid decimal values & if user has imported 0 or 0.00
But somehow invalid number error comes up.
Sample Data:
Create table STG_MAINTENANCE (ROW_ID INT, PURCH_AMT VARCHAR2(500), SELL_AMT VARCHAR2(500));
INSERT INTO STG_MAINTENANCE values(1, 'A','4.5');
INSERT INTO STG_MAINTENANCE values(2, '0','0.0');
INSERT INTO STG_MAINTENANCE values(3, '5.5','4.5');
INSERT INTO STG_MAINTENANCE values(4, '','4.5');
INSERT INTO STG_MAINTENANCE values(5, 'B','C');
INSERT INTO STG_MAINTENANCE values(6, '','');

I think The way Oracle is processing your query causes the error in your query.
For your example, You can use MATERIALIZE hint and write your query like the following:
WITH CTE1 AS (
SELECT /*+MATERIALIZE*/
TO_NUMBER(PURCH_AMT) + TO_NUMBER(SELL_AMT) AS TOTAL,
ROW_ID
FROM
STG_MAINTENANCE
WHERE
PKG_MAINTENANCE.F_ISNUMBER(PURCH_AMT) > 0
AND PKG_MAINTENANCE.F_ISNUMBER(SELL_AMT) > 0
)
SELECT
TO_NUMBER(TOTAL),
CTE1.*
FROM
CTE1
WHERE
TOTAL = 0
You can learn more about MATERIALIZE hint from oracle docs.
Hope this is helpful to you.

Related

Cant figure out this SQL Trigger

so I'm suppose to be making a trigger for my database that will limit how many classes a faculty member can be assigned. If QUALIFIED = 'Y', then they can teach up to three classes. the trouble i'm running into is that I dont know what is wrong with my SQL statement that wont let it be run.
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGN"
FOR EACH ROW
BEGIN
DECLARE
A_COUNT NUMBER;
A_QUALIFY CHAR(2);
SET(SELECT QUALIFY FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_QUALIFY
SET(SELECT COUNT(FID) FROM QUALIFY WHERE (FID = :NEW.FID)) AS A_COUNT
IF (A_QUALIFY = 'Y' AND A_COUNT < 3) THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END;
The two errors i'm getting are
line 8, position 8 PLS-00103: Encountered the symbol "(" when expecting one of the following: constant exception table long double ref char time timestamp
line 8, position 61 PLS-00103: Encountered the symbol "AS" when expecting one of the following: set
The 1st problem here is that the BEGIN needs to move down below the DECLARE and the variable declarations.
The 2nd problem is the way you're attempting to set those variables. Get rid of those SETs and AS's. In PL/SQL, one valid way to set a variable with the result of a SQL statement is with a SELECT INTO. Like so....
SELECT QUALIFY
INTO A_QUALIFY
FROM QUALIFY
WHERE FID = :NEW.FID;
...and you can do the same for A_COUNT
I won't guarantee everything will work right after you do that, but that's the bare minimum to fix here.
Also, even if doing the above works, watch out for SELECT INTO because you'll get a "no data found" error if there's ever a scenario where you don't already have a FID = :NEW.FID being passed in OR an "exact fetch returns more than requested number of rows" error if you have more than 1 existing record with that FID in your table. You then either have to handle those exceptions, or use a different method of assigning values to your variable (such as declaring the SQL in a cursor and then OPEN cursor, FETCH cursor INTO variable, CLOSE cursor.)
Lastly, I think there may be a problem in your logic. You're asking for the value in QUALIFY for a FID, but then you're asking for the number of records that have that FID. That implies that the FID isn't the primary key on your table, which means there could be different records with the same FID but different values in the QUALIFY field. So if you're going to be using that variable in your logic later on, then that may be a problem, since the same FID can have one record with QUALIFY = 'Y', and another record with QUALIFY = 'N'
You have used the BEGIN after DECLARE part. And am not sure why you are using SET .. AS. We can combine both selects into one and use it in IF condition.
I don't think you can trigger on the same table and do insert at the same time. You will end up with ORA-04088 error.
Instead, you can restrict the insertion by throwing an error.
(my option would be a Foreign Key Constraint over the ASSIGN table)
--Creating Tables
create table ASSIGN (FID number, CID number);
create table QUALIFY (FID number, QUALIFY char);
-- Loading sample data
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (1, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (2, 'Y');
insert into QUALIFY values (3, 'N');
insert into QUALIFY values (4, 'Y');
CREATE OR REPLACE trigger "ASSIGN_T1"
BEFORE
INSERT ON "ASSIGNEE" --< change table name to yours
FOR EACH ROW
DECLARE
A_COUNT NUMBER;
BEGIN
SELECT COUNT(QUALIFY) into A_COUNT FROM QUALIFY WHERE QUALIFY='Y' AND FID = :NEW.FID;
-- If they are qualified and already has 3 classes. They are not allowed/record is not inserted.
IF A_COUNT = 3 THEN
Raise_Application_Error (-20343, 'FID is not Qualified or already has 3 Classes.');
END IF;
END;
/
Test by inserting data into the ASSIGNEE table
-- FID 1 already assigned to 3 classes, should not be allowed any more.
insert into ASSIGNEE values (1,3);
-- See error below
Error report -
ORA-20343: FID is not Qualified or already has 3 Classes.
-- FID 2 has only 2 classes, so allowed to insert.
insert into ASSIGNEE values (2,3);
1 row inserted.
One way to accomplish your goal is to do something like this:
CREATE OR REPLACE TRIGGER ASSIGN_T1
BEFORE INSERT ON ASSIGN
FOR EACH ROW
BEGIN
FOR aRow IN (SELECT q.QUALIFY,
COUNT(*) OVER (PARTITION BY q.FID) AS FID_COUNT
FROM QUALIFY q
WHERE q.FID = :NEW.FID)
LOOP
IF aRow.QUALIFY = 'Y' AND aRow.FID_COUNT < 3 THEN
INSERT INTO ASSIGN (FID, CID) VALUES (:NEW.FID, :NEW.CID);
END IF;
END LOOP;
END ASSIGN_T1;

ORA-01722: invalid number while select numeric column and same numeric column reference in where

select id
from abc
where id = 1001;
return invalid number.
abc is a view
datatype of id is number
In create view cast is used
CREATE OR REPLACE VIEW abc AS
SELECT CAST (SUBSTR(T_ID,3,100) AS NUMBER) AS ID
from TEST
id column has all numeric values only
SELECT * FROM ABC WORKS FINE AND SO insert query works
tried below code which returns nothing in dbms output
declare
l_dummy number;
begin
for cur in (select ID from abc)
loop
begin
l_dummy := to_number(cur.ID);
exception
when others then dbms_output.put_line(cur.ID);
end;
end loop;
end;
column datatype nullable
ID NUMBER Yes 1 NO NO NO
SELECT *
FROM abc
WHERE
ID = 1001
returns:
ORA-01722: invalid number
01722. 00000 - "invalid number"
*Cause: The specified number was invalid.
*Action: Specify a valid number.
There are two problems:
SUBSTR(T_ID,3,100) will not return number always -- We need to consider the only numeric query
If we apply anything in WHERE condition of the view, Order of the execution of the WHERE condition is the call of Oracle optimizer.
Please see below code:
-- Data preparation
create table TEST (T_ID varchar2(20));
INSERT INTO TEST VALUES('AB1000'); -- Good data
INSERT INTO TEST VALUES('AB1001'); -- Good data
INSERT INTO TEST VALUES('CD1001'); -- Good data
INSERT INTO TEST VALUES('XY1004'); -- Good data
INSERT INTO TEST VALUES('XYZ1004'); -- Bad data
--
-- Data in the table
SELECT * FROM TEST;
Output
-- You need to create your view as following
-- rownum is used so that WHERE clause of view is executed first
-- and then any external WHERE clause on the view is executed
CREATE OR REPLACE VIEW ABC_NEWVIEW AS
SELECT
ID
FROM
(
SELECT
CAST(SUBSTR(T_ID, 3, 100) AS NUMBER) AS ID,
ROWNUM RN
FROM
TEST
WHERE
CASE
WHEN TRIM(TRANSLATE(SUBSTR(T_ID, 3, 100), '0123456789-,.', ' ')) IS NULL THEN 'numeric'
ELSE 'alpha'
END = 'numeric'
)
--
-- View
SELECT * FROM ABC_NEWVIEW
Output
-- Query using WHERE condition
SELECT *
FROM ABC_NEWVIEW
WHERE
ID = 1001
Output
You can find the demo in the following link:
DB Fiddle demo
You can see the demo of VIEW without ROWNUM:
Reproduced your issue -- So the issue is the order of the execution.
Cheers!!

Can't save comma separated number string in varchar2()

I've got a list of items I want to add in a single click, for this purpose I created a table with a column with a type varchar2(4000), in this column I want to list id's that refer to the other table so I can paste the value of this column as a parameter. ex. select t.* from table_name t where t.point_id in (varchar2 string of comma seprated point_ids).
The problem I've got is that when I put more than 1 id in the varchar2 field I get ORA-06502: PL/SQL: numeric or value error: character to number conversion error
How can I avoid this error? My field is varchar2, not number and I don't want it to be converted. I need the value I'm parsing to be saved. ex. (11, 12)
Picture of my Table:
EDIT: Note - My select is working okay, the problem I'm having is with saving the information.
My Insert :
procedure lab_water_pointsgroup (v_group_id lab_water_pointsgroups.group_name%type,
v_group_name lab_water_pointsgroups.group_code%type,
v_group_code lab_water_pointsgroups.lab_points_ids%type,
v_lab_points_ids lab_water_pointsgroups.group_id%type) as
begin
update lab_water_pointsgroups
set group_name = v_group_name,
group_code = v_group_code,
lab_points_ids = v_lab_points_ids
where group_id = v_group_id;
if ( SQL%RowCount = 0 ) then
insert into lab_water_pointsgroups
(group_id, group_name, group_code, lab_points_ids)
values
(v_group_id, v_group_name, v_group_code, v_lab_points_ids);
end if;
end;
Not sure how exactly I can help you here as you gave no example. Have a look at the below demo, maybe the contruct with xmltable solves your problem. HTH KR
create table testtab (id number);
insert into testtab values (1);
select * from testtab where id in ('1'); -- works
select * from testtab where id in (1); -- works
select * from testtab where id in (1,2); -- works
select * from testtab where id in ('1,2'); -- ORA-01722: invalid number
select * from testtab where id in (select to_number(xt.column_value) from xmltable('1,2') xt); -- works
Here is how you defined parameters for your procedure:
v_group_id lab_water_pointsgroups.group_name%type,
v_group_name lab_water_pointsgroups.group_code%type,
v_group_code lab_water_pointsgroups.lab_points_ids%type,
v_lab_points_ids lab_water_pointsgroups.group_id%type
I suspect that you made mistake with types, because id has name type, name has code type etc. So it should be:
v_group_id lab_water_pointsgroups.group_id%type,
v_group_name lab_water_pointsgroups.group_name%type,
v_group_code lab_water_pointsgroups.group_code%type,
v_lab_points_ids lab_water_pointsgroups.lab_points_ids%type
And I suggest to use merge instead of this update / insert, but it's not what you asked for :)
Your error is in that you don't make difference between variable containing comma separated numbers and actual enumeration in the 'in' operator. After your code analize and preparation to execution your statement will be like .. id in ('1,2,3') instead of ..id in (1,2,3), did you notice differnce ? So you need to transform comma separated values into array or in this case into collection. Your code should be like this:
select t.*
from table_name t
where t.point_id in
(select regexp_substr(YOUR_VARCHAR2_COLUMN_VALUE, '[^,]+', 1, level)
from dual
connect by regexp_substr(YOUR_VARCHAR2_COLUMN_VALUE, '[^,]+', 1, level) is not null)

How to fill some column with constant string followed by variable number

I need to fill a table column (in Oracle database) with string values that have variable part, e. g. AB0001, AB0002,...,AB0112...,AB9999, where AB is constant string part, 0001 -9999 is variable number part. i've tried the following solution in SQL for a table with 2 columns:
create table tbl1
(seq1 number(8),
string1 varchar(32));
declare
tst number(8) :=0;
begin
for cntr in 1..100
loop
tst := cntr;
insert into TBL1 values (someseq.nextval, concat('AB',tst));
end loop;
end;
But in this case I get STRING1 filled with values AB1,AB2,...,AB10,.. which is not exactly what I need.
How should I modify my script to insert values like AB0001,...,AB0010?
Either pad the number with zeros, or format it with leading zeros:
insert into TBL1
values (someseq.nextval, concat('AB', to_char(tst, 'FM0000'));
The 'FM' format modifier prevents a space being added (to allow for a minus sign).
For your specific example you don't need a PL/SQL block; you could use a hierarchical query to generate the data for the rows:
insert into tbl1(seq1, string1)
select someseq.nextval, concat('AB', to_char(level, 'FM0000'))
from dual
connect by level <= 100;
use the lpad function
select lpad(1, 4, '0') from dual
--> '0001'
try this one
INSER INTO table_name(code)
VALUES(CONCAT('AB', LPAD('99', 4, '0'));
or You can Update on the basis of PK after insertion
UPDATE table_name SET code = CONCAT('AB', LPAD(PK_Column_Name, 4, '0') ;
or You Can Use Triggers
CREATE TRIGGER trgI_Terms_UpdateTermOrder
ON DB.table_name
AFTER INSERT
AS
BEGIN
UPDATE t
SET code = CONCAT('AB', LPAD(Id, 4, '0')
FROM DB.table_name t INNER JOIN inserted i ON t.Id = I.Id
END;
GO

SQL to insert only if a certain value is NOT already present in the table?

How to insert a number into the table, only if the table does not already have that number in it?
I am looking for specific SQL code, not really sure how to approach this. Tried several things, nothing's working.
EDIT
Table looks like this:
PK ID Value
1 4 500
2 9 3
So if I am trying to INSERT (ID, Value) VALUES (4,100) it should not try to do it!
If ID is supposed to be unique, there should be a unique constraint defined on the table. That will throw an error if you try to insert a value that already exists
ALTER TABLE table_name
ADD CONSTRAINT uk_id UNIQUE( id );
You can catch the error and do whatever you'd like if an attempt is made to insert a duplicate key-- anything from ignoring the error to logging and re-raising the exception to raising a custom exception
BEGIN
INSERT INTO table_name( id, value )
VALUES( 4, 100 );
EXCEPTION
WHEN dup_val_on_index
THEN
<<do something>>
END;
You can also code the INSERT so that it inserts 0 rows (you would still want the unique constraint in place both from a data model standpoint and because it gives the optimizer more information and may make future queries more efficient)
INSERT INTO table_name( id, value )
SELECT 4, 100
FROM dual
WHERE NOT EXISTS(
SELECT 1
FROM table_name
WHERE id = 4 )
Or you could code a MERGE instead so that you update the VALUE column from 500 to 100 rather than inserting a new row.
Try MERGE statement:
MERGE INTO tbl USING
(SELECT 4 id, 100 value FROM dual) data
ON (data.id = tbl.id)
WHEN NOT MATCHED THEN
INSERT (id, value) VALUES (data.id, data.value)
INSERT INTO YOUR_TABLE (YOUR_FIELD)
SELECT '1' FROM YOUR_TABLE YT WHERE YT.YOUR_FIELD <> '1' LIMIT 1
Of course, that '1' will be your number or your variable.
You can use INSERT + SELECT to solve this problem.