Why is this check constraint not working when it checks length? - oracle

create table test
(
id varchar2(10) check( length(trim(id)) > 0),
primary key(id)
)
insert into test values (' '); -- this works
I expected this check constraint to stop this insert. But, it gets inserted none the less. Why ?

Problem is TRIM(' ') returns NULL and LENGTH(NULL) also gives NULL (not 0)
Try NVL(LENGTH(TRIM(' ')), 0) > 0 or
Try TRIM(ID) IS NOT NULL
Snippet stating that constraints can return null aka 'unknown' and considered to be NOT VIOLATING the constraint. Only a return of false violates a constraint. At least as per Oracle 12c
See Oracle Concept page about constraints

Explanation for what you saw:
If str is made up entirely of spaces, then trim(str) is the empty string.
Oracle treats the "empty string" the same as null (of varchar2 data type, when that matters), in flagrant violation of the SQL standard. Oracle is not even consistent in this - there are (very few) exceptions where the empty string is, in fact, seen as "empty string" (for example, in concatenations).
By definition, the length of null is null (in particular, not zero).
In SQL, a condition like null > 0 evaluates to unknown (in the three-valued logic needed to accommodate null in such conditions).
In check constraints unknown is treated the same as true. This is different from the treatment in other conditions (in SQL statements - in where clauses, join conditions etc.), where unknown is treated the same as false. This is documented, for example, here: https://docs.oracle.com/cd/B19306_01/server.102/b14200/clauses002.htm - see the first paragraph in the Check Constraints section.
The correct solution to your problem:
Checking if something is null should be done DIRECTLY, with the is null or is not null conditions. Don't use length for that.
Like this:
check( trim(id) is not null )

Related

How to translate regexp_replace from Oracle to Snowflake?

I'm migrating some queries from Oracle to Snowflake and I got stuck with regexp_replace(column, '\\W', null).
In Oracle it replaces empty strings with null and it removes some characters, i.e. 218.55 becomes 21855
In Snowflake it replaces every single value with null and I need it to do the same like in Oracle.
What is the equivalent function I can use in Snowflake?
So JNevill did most the work. But given it very much seems to be a concatenation problem, why not use the replace of empty string to get what seems like broken Oracle behavior (if you floats are all the same decimal places this is a indirect why to covert to fixed point). And then use NULLIF to convert empties to NULL
SELECT '*'||column1||'*' as input,
'*' || null || '*' as null_test,
'*'||regexp_replace(column1, '\\W', null)||'*' as reg_null,
'*'||regexp_replace(column1, '\\W', '')||'*' as reg_empty,
nullif(regexp_replace(column1, '\\W', ''),'') as nullif
FROM VALUES
('218.55'),
('');
gives:
INPUT
NULL_TEST
REG_NULL
REG_EMPTY
NULLIF
218.55
null
null
21855
21855
**
null
null
**
null
extra stars there so strings could be seen.

How can I decline an INSERT when column is set to NOT NULL

From the documentation, you have to put a NOT NULL modifier in the column definition to mark it as such, just like for other SQL databases.
Consider this table:
CREATE TABLE test (
name String NOT NULL,
isodate DateTime('Europe/Berlin') NOT NULL
) ENGINE = MergeTree()
ORDER BY (isodate)
If I would try to insert NULL for both columns (or at least one), the expected behaviour is that Clickhouse declines insertion since the columns are marked as NOT NULL. Instead, Clickhouse creates a new row, where isodate is 1970-01-01 01:00:00 and name is an empty string, which are the default values for those data types apparently.
What do I have to do so that Clickhouse declines such inserts?
My Clickhouse server version is 21.12.3.
In ClickHouse, NULL and NOT NULL do change the behavior of the data type, but not in the way other relational databases - it is syntactically compatible with other relational database but not semantically (a Int32 NULL is the same as a Nullable(Int32), as a Int32 NOT NULL is the same as a Int32). A column defined as NOT NULL does not mean it will refuse to insert fields whose values are NULL in the insert statement - it means ClickHouse will use the default expression for the column type (or if it is not specified in the column definition, the default value for the data type). This behavior is expected in ClickHouse when input_format_null_as_default is enabled (the default for Clickhouse 21.12.3).
To throw exceptions for such invalid values you need to change the system setting input_format_null_as_default to 0. If you use clickhouse-client, you can disable it while connecting to clickhouse:
clickhouse-client -h ... --input_format_null_as_default 0
or after:
clickhouse> SET input_format_null_as_default=0
This way, a statement like insert into test (name, isodate) values (NULL, NULL); will behave more likely most relational databases.
Clickhouse behaviour with Not Null constraints is not compatible with other databases.
You can overcome it using check constraints https://clickhouse.com/docs/en/sql-reference/statements/create/table/#constraints
CREATE TABLE test (
name String NOT NULL,
isodate DateTime('Europe/Berlin') NOT NULL,
CONSTRAINT isodate_not_null CHECK isodate <> toDateTime(0, 'Europe/Berlin')
) ENGINE = MergeTree()
ORDER BY (isodate)
insert into test(name) values ('x');
DB::Exception: Constraint `isodate_not_null` for table default.test (f589312a-1592-426a-b589-312a1592b26a) is violated at row 1. Expression: (isodate != toDateTime(0)). Column values: isodate = 0. (VIOLATED_CONSTRAINT)
insert into test values ('x', now());
OK.
The reason is performance, in OLAP databases need to ingest data as fast as possible.

Oracle unique index with condition

When I am learning oracle index, I encountered with the situation that I am not able to understand.
create unique index u_index1 on table_a(case when code is not null then company_id end, code)
I am not able to understand the purpose of this code, Does this index mean that, if code is not null then index on code, company_id is created? or if code is null then index is created on code column only? or what?
The meaning of this index(ultimately unique constraint) is:
When you insert NULL into code then uniqueness will be checked on (NULL, CODE). Ok but code is also NULL so index will be on (NULL, NULL). means on nothing.
When code is not null then index will be on (COMPANY_ID, CODE)
Now, let's take this in this way:
COMPANY_ID can have multiple duplicates with NULL CODE but for a single COMAPNY_ID, There must be a unique CODE
Cheers!!

Before update trigger is not creating history for null values

My before update trigger is not creating any history for changing null value to any other value. It is creating history for changing any other values.
My main logic is like this:
if nvl(:old.place,null) <> nvl(:new.place,null) then
insert into table (place)
values(:old.place)
end if;
The NVL() function is used to substitute a value in place of null. You're replacing null with... null, which is pointless. You're then trying to compare two null values with the (in)equality operator, and as null is neither equal to or not equal to anything including itself, the result is unknown, and your condition is always false if either the old or new value is null.
You could use a fixed dummy value that you know will never actually exit:
if nvl(:old.place,'**fake**') <> nvl(:new.place,'**fake**') then
But for clarity I'd generally prefer to explicitly test for nulls:
if (:old.place is null and :new.place is not null)
or (:old.place is not null and :new.place is null)
or (:old.place != :new.place) then

Trigger not executing correctly in Oracle PL/SQL

I have a trigger which executes on AFTER UPDATE. It doesn't work as i want it to.
How would i check if a value has changed on a field which is of nullable type? I have the following fields which are of nullable type:
FRM_DATE DATE
FRM_TIME DATE
THE_DATE DATE
THE_TIME NUMBER(4,2)
THE_BOOL NUMBER(2)
I would like to execute a set of logic only if the value for the above fields have actually changed. If the values are the same then i do not want to code to execute. So from the UI, lets say if one of the fields had a value and the user removes it(it now becomes NULL) and hits submit button, i want my logic to execute because a change has been made.
I tried the following but it doesn't execute the logic i want:
IF (nvl(:old.FRM_DATE, '') <> nvl(:new.FRM_DATE,'')) THEN
--My logic
END IF;
I also tried
IF (nvl(:old.FRM_DATE, NULL) <> nvl(:new.FRM_DATE,NULL)) THEN
--My logic
END IF;
Any ideas?
Kind Regards,
In Oracle, null and the empty string '' are effectively equivalent:
Oracle Database currently treats a character value with a length of zero as null. However, this may not continue to be true in future releases, and Oracle recommends that you do not treat empty strings the same as nulls.
... so the two checks are really the same. In both cases you're doing nvl(something, null), which doesn't make much sense - you're saying "if the value is null then make it null", which is redundant and doesn't do any real transformation. So if either the old or new value is null you're still trying to compare null with either itself or a non-null value; and as San said, you can't compare null with anything using equality conditions.
You could use a magic value, again as San shows, but you have to be sure that can never actually appear in the data. It may be safer, and make the intent clearer, if you explicitly check with is null:
IF (:old.FRM_DATE IS NULL AND :new.FRM_DATE IS NOT NULL)
OR (:old.FRM_DATE IS NOT NULL AND :new.FRM_DATE IS NULL)
OR :old.FRM_DATE != :new.FRM_DATE
THEN
...
You cannot compare null with null or null with any other value, change the logic to:
IF (nvl(:old.FRM_DATE, to_date('01-01-1900', 'dd-mm-yyyy')) <> nvl(:new.FRM_DATE,to_date('01-01-1900', 'dd-mm-yyyy'))) THEN
--My logic
END IF;
Just in case, additional recommendations. Check type of trigger you use?
I mean, row trigger or statement trigger.
You can also see an example of a trigger on the column:
Oracle SQL trigger on update of column
While it is true that Oracle considers (for the time being) an empty string and NULL to be equivalent, the nice people at Oracle have provided us with an easy way to catch a change -- but it may not be what you want.
The updating system function returns true if the column appears on the left side of the "=" in the set clause. So
update table1
set col1 = null,
col2 = 42
where ...;
Within the update trigger, updating( 'col1' ) and updating( 'col2' ) will both return true. But they will return true even if col1 started out with a null value (or '') or col2 was already 42.
So it would appear you have two options: either you can, like Oracle, consider NULL and '' to be equivalent and don't flag them as a change, or you can flag any attempt to make a change in the field even if it turns out the same value was actually being rewritten to the field.
Unless there is an objective business requirement mandating one over the other, it really doesn't matter which way you (or whoever makes these decisions) choose, as long as you publicize the behavior to everyone who needs to know.

Resources