Not able to use <> in Function based Indexes in Oracle - oracle

We are trying to create a function based index which has <> (not equal to) in the where clause but get an error that say
ORA-00907: missing right parenthesis
Is there any problem in using the <> clause ? Can it be made to work in someway.
CREATE INDEX IX_TEST_TABLE ON TEST_TABLE ((NVL(COL_A, 0) <> NVL(COL_B, 0));

You could use the result of that comparison in a case statement to come up with an actual value, with a supported data type, rather than a boolean - which has been noted already isn't supported y Oracle as an SQL data type. What that value is doesn't really matter as long as you're consistent; you could use Y/N, 0/1, etc.
Depending on your data spread and selectivity of how you'll query, you could use a bitmap index:
create bitmap index ix_test_table on test_table
(case when nvl(col_a, 0) <> nvl(col_b, 0) then 1 else 0 end);
And then query on the same case, of course:
select * from test_table
where case when nvl(col_a, 0) <> nvl(col_b, 0) then 1 else 0 end = 1;
select * from test_table
where case when nvl(col_a, 0) <> nvl(col_b, 0) then 1 else 0 end = 0;
Or if it's very selective only include the small subset of rows that you're interested in by utilising the fact that null values are not included in the index:
create index ix_test_table on test_table
(case when nvl(col_a, 0) <> nvl(col_b, 0) then 1 end);
select * from test_table
where case when nvl(col_a, 0) <> nvl(col_b, 0) then 1 end = 1;
You'd need to evaluate which is appropriate for your data.

In Oracle BOOLEAN is a PL/SQL-only type, so you can't use it in SQL, not in indexes either. the function in your index is resulting in either true or false and is therefore not allowed.

Related

Oracle - how to imitate bit columns and boolean AND/OR?

I come from MS SQL and My SQL. These DBMS provide a bit data type where the two boolean values are represented by 0 and 1.
I am now in a project with Oracle that is new to me. There is no bit type. It (somewhere) advises to use NUMBER(1) as bit - so values 0 and 1 would go, but it supports also values -9 to -1 and 2 to 9.
And we have to make an OR between two such columns.
I read also this article here. But what is best for OR (and AND) functionality, few boiler plate, readable for programmers (and performant)?
Here some test code to check it:
--drop table test_bool_number;
create table test_bool_number (
test_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
abool NUMBER(1),
bbool NUMBER(1),
cbool NUMBER(1)
PRIMARY KEY(test_id)
);
INSERT INTO test_bool_number (abool, bbool, cbool)
select 0, 0, null from dual
union all select 0, 1, null from dual
union all select 1, 0, null from dual
union all select 1, 1, null from dual;
SELECT * FROM test_bool_number ORDER BY test_id; -- to query the results
A team member proposed to use:
-- option A + for OR, * for AND
UPDATE test_bool_number
SET
cbool = abool + bbool;
I found this version works most probably, if cbool is deserialized in Java class boolean property, but it won't work always:
abool and bbool have to be only either 0 or 1
cbool as result does not fulfill above condition and is true if cbool > 0
and if 10 bool columns like that are summed, it results in 10 and with a number(1) leads to ORA-01438 error
So I found then this:
-- option B greatest for OR, least for AND
UPDATE test_bool_number
SET
cbool = greatest(abool, bbool);
Above works great, because the result is always either 0 or 1, but only:
abool and bbool have to be only either 0 or 1
And this works also for 10 and more columns OR'ed.
For AND the method least instead of greatest could be taken.
But if someone calculates intermediate boolean results in the first manner or different - the value in abool and bbool could be -9 ... -1 or 2 ... 9 too!
Assuming all values not being 0 are representation of true (like in e. g. C programming language)
What is the simplest code to get a boolean OR?
-- bool input values are not always only 1 in case of true!
UPDATE test_bool_number
SET abool = abool * -5, bbool = bbool * +5;
-- option C standard SQL always correct
UPDATE test_bool_number
SET cbool = case when ((abool <> 0) or (bbool <> 0)) then 1 else 0 end;
Above statement is fully correct, but a lot of boiler plate.
So I found another option:
-- option D ABS and SIGN
UPDATE test_bool_number
SET
cbool = SIGN(ABS(abool) + ABS(bbool));
Is shorter, always correct. It even works with abool = 9 and bbool = 7 and the intermediate result being SIGN(16) where 16 is outside of range of NUMBER(1), but performant?
Is there a simpler one, perhaps even performant one to deal with boolean values?
Or is it better to represent boolean values in an other fashion on Oracle table columns than NUMBER(1) with values 0 = false and 1 (or other) = true?
-- option E ABS + > 0
UPDATE test_bool_number
SET
cbool = case when ((ABS(abool) + ABS(bbool)) > 0) then 1 else 0 end;
So what about using binary or bitwise OR?
-- option F BITOR for OR
UPDATE test_bool_number
SET
cbool = BITOR(abool, bbool) <> 0 then 1 else 0 end;
Above only works for OR, BITAND cannot be used for AND functionality.
Only some magic idea, not at all easy to understand by the next code reader:
-- option G via text
UPDATE test_bool_number
SET
cbool = ABS(SIGN(TO_NUMBER(abool || bbool)));
The most inner could be read as boolean or like it is in a lot of programming languages (C#, Java, ...) but in Oracle it is a string concatenation operator!
So we end up with '00', '10' etc.
Finally it returns only 0 or 1, but only if (abool and) bbool are positive values or 0!
And I doubt the performance (number to text and text to number conversion).
So how are you working with boolean values in Oracle, and what is best suitable to be able to build an understandable OR (and AND) code for 2 and more other boolean columns, that is quite performant as well?
Do you use another column type to represent boolean values? char? '0' and '1' or 'y' and 'n'? How do you make OR (and AND) with these for 2... n columns?
Use the functions BITAND (from at least Oracle 11) and BITOR (from Oracle 21, although undocumented) and put a CHECK constraint on your columns:
SELECT abool,
bbool,
BITAND(abool, bbool),
BITOR(abool, bbool)
FROM test_bool_number
ORDER BY test_id;
Which, for the sample data:
create table test_bool_number (
test_id NUMBER GENERATED BY DEFAULT AS IDENTITY,
abool NUMBER(1) CHECK (abool IN (0, 1)),
bbool NUMBER(1) CHECK (bbool IN (0, 1))
PRIMARY KEY(test_id)
);
INSERT INTO test_bool_number (abool, bbool)
select 0, 0 from dual
union all select 0, 1 from dual
union all select 1, 0 from dual
union all select 1, 1 from dual;
Outputs:
ABOOL
BBOOL
BITAND(ABOOL,BBOOL)
BITOR(ABOOL,BBOOL)
0
0
0
0
0
1
0
1
1
0
0
1
1
1
1
1
Prior to Oracle 21, you can create a user-defined function for BITOR.
db<>fiddle here

Number of trailing zeros in a number

I have a column MONTHLY_SPEND in the table with data type of NUMBER. I am trying to write a query which will return number of zeros in the column.
e.g..
1000 will return 3
14322 will return 0
1230 will return 1
1254000.65 will return 0
I tried using mod operator and 10 but without the expected result. Any help is appreciated. Please note that database is Oracle and we can't create procedure/function.
select nvl(length(regexp_substr(column, '0+$')), 0) from table;
Here is one way to find
create table spend
(Monthly_spend NUMBER);
Begin
insert into spend values (1000)
insert into spend values (14322)
insert into spend values (1230)
insert into spend values (1254000.65)
End;
This query will for this data :
select Monthly_spend,REGEXP_COUNT(Monthly_spend,0)
from spend
where Monthly_spend not like '%.%' ;
if have one more data like 102 and if it should be zero , then try below query:
select Monthly_spend,case when substr(Monthly_spend,-1,1)=0 THEN REGEXP_COUNT(Monthly_spend,0) ELSE 0 END from spend;
Here is final query for value like 2300120 or 230012000
select Monthly_spend,
case when substr(Monthly_spend,-1,1)=0 and REGEXP_COUNT(trim (0 from Monthly_spend),0)<=0 THEN REGEXP_COUNT(Monthly_spend,0)
when REGEXP_COUNT(trim (0 from Monthly_spend),0)>0 THEN LENGTH(Monthly_spend) - LENGTH(trim (0 from Monthly_spend))
ELSE 0 END from spend;
Output :
1000 3
1254000.65 0
14322 0
1230 1
102 0
2300120 1
230012000 3
You can try this, a simple solution.
select length(to_char(col1))-length(rtrim(to_char(col1), '0')) no_of_trailing_zeros from dual;
select length(to_char('123.120'))-length(rtrim(to_char('123.120'), '0')) no_of_trailing_zeros from dual;

insert from one table to another table use procedure

I want to insert from one table to other with some edits in values. source table has 20,000,000 records and insert and commit them impossible . so i write a procedure to commit each 1000 insert in a loop. but it does not work. what is the problem?
CREATE OR REPLACE PROCEDURE CONVERT_INTO_K2 IS
batch_size number;
row_num number;
CURSOR trans IS
select rownum,KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, 1, 1, 1, 1, 1, 1, 1
from P912.KCTRNS t
where t.trprcod != 47
and rownum < 200;
BEGIN
batch_size := 0;
row_num:=0;
FOR rec IN trans LOOP
batch_size := batch_size + 1;
row_num := row_num + 1;
if MOD( row_num, 1000 ) != 0 then
insert into P912.KCTRNS2
(srcpan,rfrnnum, trnsid,swchcod, prswchcod,intrtrmid,
xtrntrmid,trmcod, aptrid,msgtypidnt,trntyp,
rspcod, msqrsn,rvrsflg,sttlsts,currcod,
amt,origamt,crdhldrcurrcod,feeamt,
crdhldrdiscamt, isurcrdinstid,acqrcrdinstid,
rcvrcrdinstid,trcnum,intrrfrnnum,
rcvdt,rspdt, prrcvdtsctn,prrcvtmsctn,
btchid, btchiopendt,firsacctnum,
scndacctnum,docnum,docdt, origdtelmt,
dditdat,dstpan, id, diag, mngcod,
funccod, sttlcod,trnres, custno,
crdlesstrcno,accttyp1,accttyp2,chnltyp)
Values
(KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, 0, 1, 1, 1, 1, 1, 1);
else
insert into P912.KCTRNS2
(srcpan,rfrnnum, trnsid,swchcod, prswchcod,intrtrmid,
xtrntrmid,trmcod, aptrid,msgtypidnt,trntyp,
rspcod, msqrsn,rvrsflg,sttlsts,currcod,
amt,origamt,crdhldrcurrcod,feeamt,
crdhldrdiscamt, isurcrdinstid,acqrcrdinstid,
rcvrcrdinstid,trcnum,intrrfrnnum,
rcvdt,rspdt, prrcvdtsctn,prrcvtmsctn,
btchid, btchiopendt,firsacctnum,
scndacctnum,docnum,docdt, origdtelmt,
dditdat,dstpan, id, diag, mngcod,
funccod, sttlcod,trnres, custno,
crdlesstrcno,accttyp1,accttyp2,chnltyp)
Values
(KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN),
t.TRPRRN, t.TRSIDE,t.switchcode,t.kcb_switchcode,
t.TERMID, t.TRTERMID,t.TRPOSCCOD,t.TRCAID,
t.TRMTI,t.TRPRCOD,t.TR87RSPCOD,t.TRMSGRSN,
t.TRREVFLG,t.TRSETTMD,t.TRCURCOD,t.TRAMNT,
t.TRORGAMNT,t.TRCHBLCUR,t.TRFEEAMNT,t.TRCHBLAMNT,
t.TRFRWIIC,t.TRACQIIC,t.TRRECIIC,t.PTRTRCNO,
t.BTRRN, t.TRTRACEDT, t.TRRSPDT, t.TRLDATE,
t.TRLTIME, t.BAID, t.BADATE,t.TRACNTID1,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
t.ATJHNO,t.ATJHDAT, t.TRORGDTELM,t.TRADDATA,
KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRACNTID2),
Trn_ID.Nextval, Trn_diag.Nextval,
1, 1, sttl_cod.Nextval, 1, 1, 1, 1, 1, 1);
end if;
IF batch_size = 10 THEN
begin
COMMIT;
end;
batch_size := 0;
end if;
END loop;
EXCEPTION
WHEN others THEN
ROLLBACK;
END CONVERT_INTO_K2;
The Values expression references t instead of rec the actual name of the cursor record. Try changing those t (only those in the Values expression, not those in the select) into rec.
Also make an alias for KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN) it will be easier. Otherwise you will have to double quote (") and upper case the column name in the Values clause.
Finally and most importantly at first sight your need would be better served by using a insert into … from (select …) syntax. Does this not work?
Failling that, a more robust approach would be to add some form of state column to your source (PSAM952.KCTRNS) table that would indicate that the entry has already been inserted into the destination (PSAM961.KCTRNS2) table. Say 0 for the default value, 1 meaning it will be part of next batch, and 2 to say it has been copied.
In your example, you are doing a mod() of row_num to provide some conditional logic. But you do not have an order-by on you cursor, so the row to which the row_num is applied is not deterministic. Of course one you apply and order-by, then you need to rethink rownum, since that is applied prior to an order-by. So you really need to re-evaluate the logic of what you are trying to do.
Having said that, you can perform conditional logic during the insert as shown below.
insert /*+ APPEND */ into PSAM961.KCTRNS2
select KWP_Trns_Utils.Get_Acnt_From_TRACNTID(t.TRPAN)
, t.TRPRRN
,etc
...
case when xxx=0 then stt1_cod.nextval else 0 end
,1
,1
,etc
from PSAM952.KCTRNS t
where t.trprcod != 47
and rownum < 200;

Database indices that cover only one value

I have huge table (millions of records) in which a few hundreds to a few thousands are marked by a boolean field (value = 1 instead of 0).
I only care for records which are true (value = 1). Is there a way to create an index which only 'indexes' these records? What kind of indices should I use?
select count(*)
from records
where boolean_field = 1
Environment: Oracle 10g (but I'm also interested in comments about other dbms)
Thank you!
If you could make your "false" value be null rather than 0, you would achieve the result you want.
Otherwise, you could create a function-based index like this:
create index idx on recors (case boolean_field when 1 then 1 end);
That would only index the 1s, but for Oracle to use it in your queries your queries would have to be like:
select * from records where case boolean_field when 1 then 1 end = 1;
This seems like a typical case for bitmap indexes in oracle.
create bitmap index bool_field_index on recors(boolean_field)
http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_5010.htm#i2062403

Oracle Cursor Is Not Working as Expected

Here is the procedure,
CREATE OR REPLACE PROCEDURE provsnXmlCmprsn (
encyNo SAS_PRO_CTL.AGENCYNO%TYPE, period SAS_PRO_CTL.PERIODE%TYPE) IS
xmlContent SAS_PRO_XML.XMLCONTENT%TYPE;
sasProvisionId SAS_PRO_CTL.SASPROVISIONID%TYPE;
CURSOR crsrXml IS
SELECT XMLCONTENT, c.SASPROVISIONID FROM SAS_PRO_XML x, SAS_PRO_CTL c
WHERE x.SASPROVISIONID = c.SASPROVISIONID AND c.PERIODE = period
AND c.AGENCYNO = agencyNo ORDER BY XMLLINENO;
BEGIN
DBMS_OUTPUT.put_line('Params: ' || agencyNo || ', ' || period);
OPEN crsrXml;
LOOP
FETCH crsrXml INTO xmlContent, sasProvisionId;
EXIT WHEN crsrXml%NOTFOUND;
DBMS_OUTPUT.put_line('XML Content Length: ' || LENGTH(xmlContent));
END LOOP;
CLOSE crsrXml;
END provsnXmlCmprsn;
The query in the cursor is retrieving 5 rows, whereas 1 row is expected, according to the condition and argument values. The same query results in 1 row, when run independently. And the surprising part is, the query in the cursor always return 5 rows no matter if the condition, c.PERIODE = period AND c.AGENCYNO = agencyNo, passed or not. Which clearly means that this query,
SELECT XMLCONTENT, c.SASPROVISIONID FROM SAS_PRO_XML x, SAS_PRO_CTL c
WHERE x.SASPROVISIONID = c.SASPROVISIONID AND c.PERIODE = period
AND c.AGENCYNO = agencyNo ORDER BY XMLLINENO;
and this query,
SELECT XMLCONTENT, c.SASPROVISIONID FROM SAS_PRO_XML x, SAS_PRO_CTL c
WHERE x.SASPROVISIONID = c.SASPROVISIONID ORDER BY XMLLINENO;
are behaving same inside the cursor. This, AND c.PERIODE = period AND c.AGENCYNO = agencyNo, part is not at all considered. Any idea what's going wrong?
One of your parameters has the same name as the column: AGENCYNO. Because of the way scoping works this evaluates to 1=1. This is why it is good practice to give parameters unique names, for example by prepending them with p_.
You should find that
AND c.PERIODE = p_period AND c.AGENCYNO = p_agencyNo
returns the desired one row. Strictly speaking you don't need to change the name of period to p_period because it is already distinguished from periode. But consistency is a virtue in software engineering.

Resources