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
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;
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;