I am having the below index defined on one of the tables in Oracle.
CASE WHEN C IS NOT NULL THEN A||','||B||','||C END
Initially, I thought the combination of A,B,C separated by commas should be unique when C is not null, but seems like I'm wrong. I did a bit of a research on this as well but could not find a good explanation.
Could someone kindly help me with this?
Thanks in Advance!!
Yes, it can be unique.
But don't forget - uniqueness is an additional cost.
There will be little benefit from such an index, since there will be duplicates of rows for fields A & B when C is NULL.
I suppose it might be worth changing the index function as :
a||','||b||case when c is not null then ','||c end
This will control duplicates if only columns A and B are used.
Of course, unless your table has other unique indexes using columns A and B.
For demonstration - a short example, with a unique functional index.
N.B. Please, when describing the problem /task, provide a description of the relevant objects (tables,indexes, procedures...etc.).)
For a test,try:
--ddl
drop table test_tab;
create table test_tab
( a varchar2(3) not null,
b varchar2(3) not null,
c varchar2(3),
idx varchar2(12) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
--
create unique index test_tab_i1 on test_tab(idx);
--dml
insert into test_tab(a,b,c) values('a1','b1','c1');
insert into test_tab(a,b,c) values('a1','b2','c2');
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
select * from test_tab;
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--dubt
insert into test_tab(a,b,c) values('a1','b1',null);
commit;
--
ANALYZE TABLE test_tab COMPUTE STATISTICS FOR ALL INDEXES;
--pass2
select * from test_tab;
--get data of idx
select INDEX_NAME,NUM_ROWS,DISTINCT_KEYS from user_indexes i where i.table_name=upper('test_tab');
--pass3
--dubt
insert into test_tab(a,b,c) values('a1','b1','c1');
Result:
Table created.
Index created.
1 row(s) inserted.
1 row(s) inserted.
1 row(s) inserted.
Statement processed.
Result Set 31
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
Download CSV
3 rows selected.
Statement processed.
Result Set 32
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
1 row(s) inserted.
Statement processed.
Statement processed.
Result Set 33
A B C IDX
a1 b1 c1 a1,b1,c1
a1 b2 c2 a1,b2,c2
a1 b1 - -
a1 b1 - -
Download CSV
4 rows selected.
Result Set 34
INDEX_NAME NUM_ROWS DISTINCT_KEYS
TEST_TAB_I1 2 2
Download CSV
ORA-00001: unique constraint (SQL_XKMHFTSRXNSKMGVEDOYSVXZGL.TEST_TAB_I1) violated ORA-06512: at "SYS.DBMS_SQL", line 1721
Even if your question is vague, it is interesting. I will try to provide you a possible explanation.
A||','||B is unique if you can guarantee that for 2 distinct values of (A,B), the function A||','||B is not returning the same value.
You need to guarantee that TO_CHAR(A) ||','|| TO_CHAR(B) is unique.
I am considering that A and B are NOT NULL.
A||','||B is unique if A and B are VARCHAR but it is not the case if you have DATE or TIMESTAMP datatypes. This is not TRUE because TO_CHAR function has default parameters that depend on nls_parameters from your session.
Testcase - executing the same queries but changing the nls_date_format and try to create a unique index
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop index test;
truncate table test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
commit;
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
alter session set nls_date_format= 'dd/mm/yyyy';
truncate table test;
drop index test;
insert into test(a,b,c) values(sysdate+(1/60),'1','c1');
insert into test(a,b,c) values(sysdate+(1/160),'1','c1');
insert into test(a,b,c) values(sysdate+(1/80),'2','c2');
insert into test(a,b,c) values(sysdate+(1/180),'2','c2');
create unique index test on test (case when c is not null then a||','||b||','||c end);
select (case when c is not null then a||','||b||','||c end) valfn from test order by B;
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
Index TEST created.
VALFN
------------------------
15/05/2021 08:40:35,1,c1
15/05/2021 08:25:35,1,c1
15/05/2021 08:34:35,2,c2
15/05/2021 08:24:35,2,c2
output with nls_date_format= 'dd/mm/yyyy'
ORA-01452: cannot CREATE UNIQUE INDEX; duplicate keys found
VALFN
---------------
15/05/2021,1,c1
15/05/2021,1,c1
15/05/2021,2,c2
15/05/2021,2,c2
Then we can see what happened when the indexes were created. We will query the index definition to explain this behavior.
output with nls_date_format= 'dd/mm/yyyy hh24:mi:ss'
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ---------------------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
output with nls_date_format= 'dd/mm/yyyy'
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10));
create unique index test on test (case when c is not null then a||','||b||','||c end);
select index_name, column_expression from all_ind_expressions where table_name = 'TEST'
INDEX_NAME COLUMN_EXPRESSION
---------- ----------------------------------------------------------------------------------
TEST CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
As you can see, the index is not the same because it depends on your nls_date_format when the index was created. I always suggest to validate what was the expression used by Oracle when creating the object.
This is the same when creating virtual columns
alter session set nls_date_format= 'dd/mm/yyyy hh24:mi:ss';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
---------------------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy hh24:mi:ss')||','||"B"||','||"C" END
alter session set nls_date_format= 'dd/mm/yyyy';
drop table test;
create table test (a DATE, b VARCHAR2(10), C VARCHAR2(10)
,idx varchar2(50) generated always as (case when c is not null then a||','||b||','||c end) virtual
);
select DATA_DEFAULT from all_tab_cols where table_name = 'TEST' and column_name = 'IDX';
DATA_DEFAULT
----------------------------------------------------------------------------------
CASE WHEN "C" IS NOT NULL THEN TO_CHAR("A",'dd/mm/yyyy')||','||"B"||','||"C" END
Now, you can add C to this equation and validate why in your case : CASE WHEN C IS NOT NULL THEN A||','||B||','||C END is not unique
Most of the time, by default the nls_date_format is not including the time.
I have two tables Table1 and Table2 both with the same columns TestResult and Testcounts. Table1 has testresult as varchar and Table2 has testresult as number.
I have a string .for eg "Oracle" as value for testresult of varchar type for Table1 which needs to be inserted to testresult of number type of Table2 as null.How can i do this? Any suggestions will be highly appreciated :)
EDIT
I have table1 with columns as TestResult varchar2(50) and Testcount number with values as "0.5","0.6","0.8","Oracle" for TestResult and 1,2,3,4 for Testcount.
Now i have another table Table2 as TestResult number and Testcount number with no values, in other words its empty.. I would like to insert all data from table1 to table2 with "Oracle" being inserted as "null"
The following will do what you've asked for:
INSERT INTO TABLE2 (TESTRESULT, TESTCOUNTS)
SELECT CASE
WHEN LENGTH(REGEXP_SUBSTR(TESTRESULT, '[0-9.]*')) = LENGTH(TESTRESULT) THEN TESTRESULT
ELSE NULL
END,
TESTCOUNTS
FROM TABLE1
SQLFiddle here
If you only have a single string value that you can't convert to a number, and you want to set that to null, you can just use a case expression to supply the null:
insert into table2 (testresult, testcounts)
select case when testresult = 'Oracle' then null else to_number(testresult) end,
testcounts
from table1;
Demo:
create table table1 (testresult varchar2(10), testcounts number);
insert into table1
select '0.5', 1 from dual
union all select '0.6', 2 from dual
union all select '0.8', 3 from dual
union all select 'Oracle', 4 from dual;
create table table2 (testresult number, testcounts number);
insert into table2 (testresult, testcounts)
select case when testresult = 'Oracle' then null else to_number(testresult) end,
testcounts
from table1;
select * from table2;
TESTRESULT TESTCOUNTS
---------- ----------
.5 1
.6 2
.8 3
4
db<>fiddle
If you are using Oracle 12c Release 2 (or above) you could also just try to convert the string to a number and use the default ... on conversion error clause to substitute null for that, or any other, non-numeric value:
insert into table2 (testresult, testcounts)
select to_number(testresult default null on conversion error), testcounts
from table1;
select * from table2;
TESTRESULT TESTCOUNTS
---------- ----------
.5 1
.6 2
.8 3
4
In earlier versions you could do the same thing with a user-defined function that wraps the real to_number() call and returns null on error. Or a regex/translate check similar to what #BobJarvis has shown.
Having multiple rows with null would make the data hard to interpret though, so hopefully you only have this one fixed value...
I have a table in oracle which has column recid which is of number Datar type. The table is partition table and it has partition index on it.
When I query the partition like
select * from table partition (abc)
I am able to see value for rec id =50. But when I query
select * from table partition(abc) where rec id =50,
It doesn’t give any record .
If I do type casting as
select * from table partition(abc) where cast (recid as number ) =50
I am getting records.
Please let me know what might be the issue .?
The issue exist only for one partition and rest of the partition working normal.
If it's not behaving as a number, then it's not stored as a number.
Run a DESCRIBE (DESC) on your table in either SQL Developer, SQLcl, or SQL*Plus. It will show how the REC_ID column is defined.
If it's stored as a VARCHAR2, you wil get an error on your WHERE CLAUSE predicate for REC_ID, if not every REC_ID could be treated as also a number:
ORA-01722: invalid number
Like so:
SQL> DESC employees
Name Null? Type
EMPLOYEE_ID NOT NULL NUMBER(6)
FIRST_NAME VARCHAR2(20)
LAST_NAME NOT NULL VARCHAR2(25)
EMAIL NOT NULL VARCHAR2(25)
PHONE_NUMBER VARCHAR2(20)
HIRE_DATE NOT NULL DATE
JOB_ID NOT NULL VARCHAR2(10)
SALARY NUMBER(8,2)
COMMISSION_PCT NUMBER(2,2)
MANAGER_ID NUMBER(6)
DEPARTMENT_ID NUMBER(4)
SQL>
SQL> SELECT * FROM employees WHERE first_name = 50;
Error starting at line : 4 in command -
SELECT * FROM employees WHERE first_name = 50
Error report -
ORA-01722: invalid number
SQL> select * from emps_copy_num where first_name = 50;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
100 50 King SKING 515.123.4567 17-JUN-87 AD_PRES 24000 90
The first query fails - because not every value in that column can be simplicity cast as a number by the database.
The second query works, because I created a copy of the table where all of the first_name strings were values that COULD be cast as a number.
You probably have spaces in there somewhere, eg
SQL> create table t ( should_have_been_numeric varchar2(30));
Table created.
SQL>
SQL> insert into t values ('50 ');
1 row created.
SQL> insert into t values (' 50 ');
1 row created.
SQL> insert into t values (' 50');
1 row created.
SQL>
SQL> select * from t where should_have_been_numeric = '50';
no rows selected
SQL> select * from t where cast(should_have_been_numeric as number) = 50;
SHOULD_HAVE_BEEN_NUMERIC
------------------------------
50
50
50
3 rows selected.
but as already mentioned, if you are treating strings as numbers, then there is problems ahead in terms of spurious errors, not to mention potential performance issues because the optimizer also doesn't know that these are really numbers.
in SQL I have these table
create table employee(
emp_ssn number(10),
first_name varchar2(10) not null ,
second_name varchar2(10),
last_name varchar2(10) not null ,
address varchar2(20) ,
birthdate date not null ,
super_ssn number(10),
job_no number (2),
constraint employee_pk primary key (emp_ssn));
create table job (
job_no number (2),
job_name varchar2(11) ,
constraint job_pk primary key (job_no ));
and I write a query that display how many employee in each
job using group by
SELECT job_no, job_name,COUNT(emp_ssn)
FROM job j JOIN employee e
ON (j.job_no = e.job_no)
GROUP BY j.job_no, j.job_name;
and the output is
SELECT job_no, job_name,COUNT(emp_ssn)
FROM job j JOIN employee e
ON (j.job_no = e.job_no)
GROUP BY j.job_no, j.job_name;
SELECT job_no, job_name,COUNT(emp_ssn)
ERROR at line 1: ORA-00918: column ambiguously defined
can you help me please !
SELECT j.job_no --<-- you are missing alias here
, j.job_name --<-- here
,COUNT(e.emp_ssn) --<-- and here
FROM job j JOIN employee e
ON (j.job_no = e.job_no)
GROUP BY j.job_no, j.job_name;
I just came across NULL values in NOT-NULL fields in our test database. How could they get there? I know that NOT-NULL constraints can be altered with NOVALIDATE clause, but that would change table's last_ddl_time in USER_OBJECTS. And that time is less than the date that those records were created. Is there something else I'm overlooking? Or is that someone's manual work for sure?
Table is partitioned and index-organized if that is relevant.
Oracle version is 9.2
The NOT NULL column condition is not like other constraints: you can disable a NOT NULL constraint but the column won't be considered NOT NULL if you reenable the constraint with NOVALIDATE. Let's build a small example:
SQL> CREATE TABLE tt (ID NUMBER NOT NULL);
Table created
SQL> SELECT column_name, nullable FROM user_tab_columns WHERE table_name = 'TT';
COLUMN_NAME NULLABLE
------------------------------ --------
ID N
now if I disable the constraint and reenable it with NOVALIDATE, the column won't be considered NOT NULLABLE by Oracle:
SQL> SELECT constraint_name, search_condition
2 FROM user_constraints WHERE table_name = 'TT';
CONSTRAINT_NAME SEARCH_CONDITION
------------------------------ ----------------------------
SYS_C00786538 "ID" IS NOT NULL
SQL> ALTER TABLE tt MODIFY CONSTRAINT SYS_C00786538 DISABLE;
Table altered
SQL> ALTER TABLE tt MODIFY CONSTRAINT SYS_C00786538 ENABLE NOVALIDATE;
Table altered
SQL> SELECT column_name, nullable FROM user_tab_columns WHERE table_name = 'TT';
COLUMN_NAME NULLABLE
------------------------------ --------
ID Y
So, I would say that if you have NULL values in a NOT NULLABLE column (as per my last query) you have a bug (contact support?)
Check If the constraint are suspended / Disabled
And you're sure these columns are really null? In other words:
SELECT field
FROM your_table
WHERE not_null_field IS NULL;
returns rows? Perhaps they've just got non-displayable data...