Either OR non-null constraints in MySQL - sql

What's the best way to create a non-NULL constraint in MySQL such that fieldA and fieldB can't both be NULL. I don't care if either one is NULL by itself, just as long as the other field has a non-NULL value. And if they both have non-NULL values, then it's even better.

This isn't an answer directly to your question, but some additional information.
When dealing with multiple columns and checking if all are null or one is not null, I typically use COALESCE() - it's brief, readable and easily maintainable if the list grows:
COALESCE(a, b, c, d) IS NULL -- True if all are NULL
COALESCE(a, b, c, d) IS NOT NULL -- True if any one is not null
This can be used in your trigger.

#Sklivvz: Testing with MySQL 5.0.51a, I find it parses a CHECK constraint, but does not enforce it. I can insert (NULL, NULL) with no error. Tested both MyISAM and InnoDB. Subsequently using SHOW CREATE TABLE shows that a CHECK constraint is not in the table definition, even though no error was given when I defined the table.
This matches the MySQL manual which says: "The CHECK clause is parsed but ignored by all storage engines."
So for MySQL, you would have to use a trigger to enforce this rule. The only problem is that MySQL triggers have no way of raising an error or aborting an INSERT operation. One thing you can do in the trigger to cause an error is to set a NOT NULL column to NULL.
CREATE TABLE foo (
FieldA INT,
FieldB INT,
FieldA_or_FieldB TINYINT NOT NULL;
);
DELIMITER //
CREATE TRIGGER FieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SET NEW.FieldA_or_FieldB = NULL;
ELSE
SET NEW.FieldA_or_FieldB = 1;
END IF;
END//
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
You also need a similar trigger BEFORE UPDATE.

MySQL 5.5 introduced SIGNAL, so we don't need the extra column in Bill Karwin's answer any more. Bill pointed out you also need a trigger for update so I've included that too.
CREATE TABLE foo (
FieldA INT,
FieldB INT
);
DELIMITER //
CREATE TRIGGER InsertFieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
END IF;
END//
CREATE TRIGGER UpdateFieldABNotNull BEFORE UPDATE ON foo
FOR EACH ROW BEGIN
IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
END IF;
END//
DELIMITER ;
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
UPDATE foo SET FieldA = NULL; -- gives error

This is the standard syntax for such a constraint, but MySQL blissfully ignores the constraint afterwards
ALTER TABLE `generic`
ADD CONSTRAINT myConstraint
CHECK (
`FieldA` IS NOT NULL OR
`FieldB` IS NOT NULL
)

I've done something similar in SQL Server, I'm not sure if it will work directly in MySQL, but:
ALTER TABLE tableName ADD CONSTRAINT constraintName CHECK ( (fieldA IS NOT NULL) OR (fieldB IS NOT NULL) );
At least I believe that's the syntax.
However, keep in mind that you cannot create check constraints across tables, you can only check the columns within one table.

I accomplished this using a GENERATED ALWAYS column with COALESCE ... NOT NULL:
DROP TABLE IF EXISTS `error`;
CREATE TABLE IF NOT EXISTS `error` (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
left_id BIGINT UNSIGNED NULL,
right_id BIGINT UNSIGNED NULL,
left_or_right_id BIGINT UNSIGNED GENERATED ALWAYS AS (COALESCE(left_id, right_id)) NOT NULL,
when_occurred TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
message_text LONGTEXT NOT NULL,
INDEX id_index (id),
INDEX when_occurred_index (when_occurred),
INDEX left_id_index (left_id),
INDEX right_id_index (right_id)
);
INSERT INTO `error` (left_id, right_id, message_text) VALUES (1, 1, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (null, 1, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (1, null, 'Some random text.'); -- Ok.
INSERT INTO `error` (left_id, right_id, message_text) VALUES (null, null, 'Some random text.'); -- ER_BAD_NULL_ERROR: Column 'left_or_right_id' cannot be null
on MySQL version 8.0.22

Related

Postgresql tuple constraints about NOT NULL

I am building a database in POSTGRESQL, and I would like to create NOT NULL constraints for my columns, where one and only one column would be NOT NULL.
I have two columns in my table, site_id and buffer_result_id. Only one of these columns will have values.
alter table dt.analysis_result
add constraint ar_check check (site_id NOT NULL OR buffer_result_id NOT NULL);
The above code is just some pseudo-code to show my idea. How can I achieve this function?
You could use XOR expressed as:
alter table dt.analysis_result
add constraint ar_check check (
(site_id IS NOT NULL OR buffer_result_id IS NOT NULL)
AND NOT(site_id IS NOT NULL AND buffer_result_id IS NOT NULL)
);
db<>fiddle demo
More info: Exclusive OR - Equivalences
Demo:
CREATE TABLE analysis_result(site_id INT, buffer_result_id INT);
INSERT INTO analysis_result VALUES (NULL, NULL);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (1, 2);
-- ERROR: new row for relation "analysis_result" violates check constraint "ar_check"
INSERT INTO analysis_result VALUES (NULL, 2);
INSERT INTO analysis_result VALUES (1, NULL);
SELECT * FROM analysis_result
In Postgres, you can do this with a check constraint. I think the simplest method is to count the number of not null values:
alter table dt.analysis_result add constraint ar_check
check ( (site_id is not null)::int + (buffer_result_id is not null)::int = 1
);

Setting value of row to NULL if another value is not present in Oracle

I am using an Oracle database and trying to add a constraint that sets the chemical_value to NULL if there is a chemical_outlier present vice versa. I have been searching the Oracle docs for a possible solution but stuck
Create table chemical
(
chemical_id char(3) not null,
chemical_name varchar2(50) not null,
chemical_value numeric,
checmical_outlier varchar(50),
constraint checkChemical <DONT KNOW HOW TO APPROACH THIS>
)
Just need some kind of direction is approaching this. I know NULL values can be bad but just want them there instead of an empty row
A check constraint won't set anything to null, but it will prevent invalid values being entered:
create table chemical
( chemical_id varchar2(3) not null
, chemical_name varchar2(50) not null
, chemical_value numeric
, chemical_outlier varchar(50)
, constraint chem_value_or_outlier_chk
check (not (chemical_value is not null and chemical_outlier is not null)
and (chemical_value is not null or chemical_outlier is not null))
);
Now these fail:
insert into chemical (chemical_id, chemical_name, chemical_value, chemical_outlier)
values ('x', 'xyz', 123, 'outlier');
ORA-02290: check constraint (XXX.CHEM_VALUE_OR_OUTLIER_CHK) violated
insert into chemical (chemical_id, chemical_name, chemical_value, chemical_outlier)
values ('x', 'xyz', null, null);
ORA-02290: check constraint (XXX.CHEM_VALUE_OR_OUTLIER_CHK) violated
But these succeed:
insert into chemical (chemical_id, chemical_name, chemical_value, chemical_outlier)
values ('x', 'xyz', null, 'outlier');
insert into chemical (chemical_id, chemical_name, chemical_value, chemical_outlier)
values ('x', 'xyz', 123, null);
If you call an INSERT without a column, that column is set with NULL by default. So, if you can control the INSERT's, just call them with 3 columns, the 2 mandatory and 1 of the other ones. In this case you need a check to guarante that anyone will call the INSERT with the 4 columns like that:
CONSTRAINT "CONSTRAINT_NAME"
CHECK ((chemical_value IS NULL AND checmical_outlier IS NOT NULL)
OR (chemical_value IS NOT NULL AND checmical_outlier IS NULL)) ENABLE
So, if someone try to define both columns, the constraint don't accept the INSERT.
But, if you have a form or something else that generate the INSERT query, and even when you don't fill a column, the INSERT is generated with that column with value "" (empty string), you need a trigger to convert this to a NULL value like that:
CREATE OR REPLACE TRIGGER "TRIGGER_NAME"
BEFORE INSERT OR UPDATE ON chemical
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
IF :new.chemical_value = ''
THEN :new.chemical_value := NULL;
END IF;
IF :new.checmical_outlier = ''
THEN :new.checmical_outlier := NULL;
END IF;
END;
Please note that this trigger don't check the condition "one is filled and the other one isn't", the trigger just convert the empty string to NULL. You can use both, trigger and constraint, to guarante the condition and the conversion.

PostgreSQL constraint using prefixes

Let's say I have the following PostgreSQL table:
id | key
---+--------
1 | 'a.b.c'
I need to prevent inserting records with a key that is a prefix of another key. For example, I should be able to insert:
'a.b.b'
But the following keys should not be accepted:
'a.b'
'a.b.c'
'a.b.c.d'
Is there a way to achieve this - either by a constraint or by a locking mechanism (check the existance before inserting)?
This solution is based on PostgreSQL user-defined operators and exclusion constraints (base syntax, more details).
NOTE: more testing shows this solution does not work (yet). See bottom.
Create a function has_common_prefix(text,text) which will calculate logically what you need. Mark the function as IMMUTABLE.
CREATE OR REPLACE FUNCTION
has_common_prefix(text,text)
RETURNS boolean
IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT position ($1 in $2) = 1 OR position ($2 in $1) = 1
$$;
Create an operator for the index
CREATE OPERATOR <~> (
PROCEDURE = has_common_prefix,
LEFTARG = text,
RIGHTARG = text,
COMMUTATOR = <~>
);
Create exclusion constraint
CREATE TABLE keys ( key text );
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key WITH <~> );
However, the last point produces this error:
ERROR: operator <~>(text,text) is not a member of operator family "text_ops"
DETAIL: The exclusion operator must be related to the index operator class for the constraint.
This is because to create an index PostgreSQL needs logical operators to be bound with physical indexing methods, via entities calles "operator classes". So we need to provide that logic:
CREATE OR REPLACE FUNCTION keycmp(text,text)
RETURNS integer IMMUTABLE STRICT
LANGUAGE SQL AS $$
SELECT CASE
WHEN $1 = $2 OR position ($1 in $2) = 1 OR position ($2 in $1) = 1 THEN 0
WHEN $1 < $2 THEN -1
ELSE 1
END
$$;
CREATE OPERATOR CLASS key_ops FOR TYPE text USING btree AS
OPERATOR 3 <~> (text, text),
FUNCTION 1 keycmp (text, text)
;
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
EXCLUDE ( key key_ops WITH <~> );
Now, it works:
INSERT INTO keys SELECT 'ara';
INSERT 0 1
INSERT INTO keys SELECT 'arka';
INSERT 0 1
INSERT INTO keys SELECT 'barka';
INSERT 0 1
INSERT INTO keys SELECT 'arak';
psql:test.sql:44: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(arak) conflicts with existing key (key)=(ara).
INSERT INTO keys SELECT 'bark';
psql:test.sql:45: ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(bark) conflicts with existing key (key)=(barka).
NOTE: more testing shows this solution does not work yet: The last INSERT should fail.
INSERT INTO keys SELECT 'a';
INSERT 0 1
INSERT INTO keys SELECT 'ac';
ERROR: conflicting key value violates exclusion constraint "keys_cannot_have_common_prefix"
DETAIL: Key (key)=(ac) conflicts with existing key (key)=(a).
INSERT INTO keys SELECT 'ab';
INSERT 0 1
You can use ltree module to achieve this, it will let you to create hierarchical tree-like structures. Also will help you to prevent from reinventing the wheel, creating complicated regular expressions and so on. You just need to have postgresql-contrib package installed. Take a look:
--Enabling extension
CREATE EXTENSION ltree;
--Creating our test table with a pre-loaded data
CREATE TABLE test_keys AS
SELECT
1 AS id,
'a.b.c'::ltree AS key_path;
--Now we'll do the trick with a before trigger
CREATE FUNCTION validate_key_path() RETURNS trigger AS $$
BEGIN
--This query will do our validation.
--It'll search if a key already exists in 'both' directions
--LIMIT 1 because one match is enough for our validation :)
PERFORM * FROM test_keys WHERE key_path #> NEW.key_path OR key_path <# NEW.key_path LIMIT 1;
--If found a match then raise a error
IF FOUND THEN
RAISE 'Duplicate key detected: %', NEW.key_path USING ERRCODE = 'unique_violation';
END IF;
--Great! Our new row is able to be inserted
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER test_keys_validator BEFORE INSERT OR UPDATE ON test_keys
FOR EACH ROW EXECUTE PROCEDURE validate_key_path();
--Creating a index to speed up our validation...
CREATE INDEX idx_test_keys_key_path ON test_keys USING GIST (key_path);
--The command below will work
INSERT INTO test_keys VALUES (2, 'a.b.b');
--And the commands below will fail
INSERT INTO test_keys VALUES (3, 'a.b');
INSERT INTO test_keys VALUES (4, 'a.b.c');
INSERT INTO test_keys VALUES (5, 'a.b.c.d');
Of course I did not bother creating primary key and other constraints for this test. But do not forget to do so. Also, there is much more on ltree module than I'm showing, if you need something different take a look on its docs, perhaps you'll find the answer there.
You can try below trigger. Please note that key is sql reserve word. So I would suggest you avoid using that as column name in your table.
I have added my create table syntax also for testing purpose:
CREATE TABLE my_table
(myid INTEGER, mykey VARCHAR(50));
CREATE FUNCTION check_key_prefix() RETURNS TRIGGER AS $check_key_prefix$
DECLARE
v_match_keys INTEGER;
BEGIN
v_match_keys = 0;
SELECT COUNT(t.mykey) INTO v_match_keys
FROM my_table t
WHERE t.mykey LIKE CONCAT(NEW.mykey, '%')
OR NEW.mykey LIKE CONCAT(t.mykey, '%');
IF v_match_keys > 0 THEN
RAISE EXCEPTION 'Prefix Key Error occured.';
END IF;
RETURN NEW;
END;
$check_key_prefix$ LANGUAGE plpgsql;
CREATE TRIGGER check_key_prefix
BEFORE INSERT OR UPDATE ON my_table
FOR EACH ROW
EXECUTE PROCEDURE check_key_prefix();
Here is a CHECK - based solution - it may satisfy your needs.
CREATE TABLE keys ( id serial primary key, key text );
CREATE OR REPLACE FUNCTION key_check(text)
RETURNS boolean
STABLE STRICT
LANGUAGE SQL AS $$
SELECT NOT EXISTS (
SELECT 1 FROM keys
WHERE key ~ ( '^' || $1 )
OR $1 ~ ( '^' || key )
);
$$;
ALTER TABLE keys
ADD CONSTRAINT keys_cannot_have_common_prefix
CHECK ( key_check(key) );
PS. Unfortunately, it fails in one point (multi - row inserts).
SQL is a very powerful language. Usually you can do most of the things by plain select statements. I.e. if you do not like triggers, you can use a this method for your inserts.
The only assumption is there exists at least 1 row in the table. (*)
The table:
create table my_table
(
id integer primary key,
key varchar(100)
);
Because of the assumption, we'll have at least 1 row.(*)
insert into my_table (id, key) values (1, 'a.b.c');
Now the magic sql. The trick is replace the p_key value by your key value to insert. I have, intentionally, not put that statement into a stored procedure. Because I want it to be straight forward if you want to carry it to your application side. But usually putting sql into stored procedure is better.
insert into my_table (id, key)
select (select max(id) + 1 from my_table), p_key
from my_table
where not exists (select 'p' from my_table where key like p_key || '%' or p_key like key || '%')
limit 1;
Now the tests:
-- 'a.b.b' => Inserts
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.b'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.b' || '%' or 'a.b.b' like key || '%')
limit 1;
-- 'a.b' => does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b'
from my_table
where not exists (select 'p' from my_table where key like 'a.b' || '%' or 'a.b' like key || '%')
limit 1;
-- 'a.b.c' => does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.c'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.c' || '%' or 'a.b.c' like key || '%')
limit 1;
-- 'a.b.c.d' does not insert
insert into my_table (id, key)
select (select max(id) + 1 from my_table), 'a.b.c.d'
from my_table
where not exists (select 'p' from my_table where key like 'a.b.c.d' || '%' or 'a.b.c.d' like key || '%')
limit 1;
(*) If you wish you can get rid of this existence of the single row by introducing an Oracle like dual table. If you wish modifying the insert statement is straight forward. Let me know if you wish to do so.
One possible solution is to create a secondary table that holds the prefixes of your keys, and then use a combination of unique and exclusion constraints with an insert trigger to enforce the uniqueness semantics you want.
At a high level, this approach breaks each key down into a list of prefixes and applies something similar to readers-writer lock semantics: any number of keys may share a prefix as long as none of the keys equals the prefix. To accomplish that, the list of prefixes includes the key itself with a flag that marks it as a terminal prefix.
The secondary table looks like this. We use a CHAR rather than a BOOLEAN for the flag because later on we’ll be adding a constraint that doesn’t work on boolean columns.
CREATE TABLE prefixes (
id INTEGER NOT NULL,
prefix TEXT NOT NULL,
is_terminal CHAR NOT NULL,
CONSTRAINT prefixes_id_fk
FOREIGN KEY (id)
REFERENCES your_table (id)
ON DELETE CASCADE,
CONSTRAINT prefixes_is_terminal
CHECK (is_terminal IN ('t', 'f'))
);
Now we’ll need to define a trigger on insert into your_table to also insert rows into prefixes, such that
INSERT INTO your_table (id, key) VALUES (1, ‘abc');
causes
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'a', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'ab', ‘f’);
INSERT INTO prefixes (id, prefix, is_terminal) VALUES (1, 'abc', ’t’);
The trigger function might look like this. I’m only covering the INSERT case here, but the function could be made to handle UPDATE as well by deleting the old prefixes and then inserting the new ones. The DELETE case is covered by the cascading foreign-key constraint on prefixes.
CREATE OR REPLACE FUNCTION insert_prefixes() RETURNS TRIGGER AS $$
DECLARE
is_terminal CHAR := 't';
remaining_text TEXT := NEW.key;
BEGIN
LOOP
IF LENGTH(remaining_text) <= 0 THEN
EXIT;
END IF;
INSERT INTO prefixes (id, prefix, is_terminal)
VALUES (NEW.id, remaining_text, is_terminal);
is_terminal := 'f';
remaining_text := LEFT(remaining_text, -1);
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
We add this function to the table as a trigger in the usual way.
CREATE TRIGGER insert_prefixes
AFTER INSERT ON your_table
FOR EACH ROW
EXECUTE PROCEDURE insert_prefixes();
An exclusion constraint and a partial unique index will enforce that a row where is_terminal = ’t’ can't collide with another row of the same prefix regardless of its is_terminal value, and that there's only one row with is_terminal = ’t’:
ALTER TABLE prefixes ADD CONSTRAINT prefixes_forbid_conflicts
EXCLUDE USING gist (prefix WITH =, is_terminal WITH <>);
CREATE UNIQUE INDEX ON prefixes (prefix) WHERE is_terminal = 't';
This allows new rows that don’t conflict but prevents ones that do conflict, including in multi-row INSERTs.
db=# INSERT INTO your_table (id, key) VALUES (1, 'a.b.c');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (2, 'a.b.b');
INSERT 0 1
db=# INSERT INTO your_table (id, key) VALUES (3, 'a.b');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (4, 'a.b.c');
ERROR: duplicate key value violates unique constraint "prefixes_prefix_idx"
db=# INSERT INTO your_table (id, key) VALUES (5, 'a.b.c.d');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"
db=# INSERT INTO your_table (id, key) VALUES (6, 'a.b.d'), (7, 'a');
ERROR: conflicting key value violates exclusion constraint "prefixes_forbid_conflicts"

NULL constraint after attribute in CREATE TABLE?

I'm new to SQL and I'm trying to figure out what this NULL is doing. Here a simple example:
CREATE TABLE test (
bla VARCHAR NULL
);
So I tried to figure out wether this is set to be the default value, but it is null as default wether I put it there or not, right?
Also I wondered if it has to stay null (for whatever reason) but when I tried to insert a value it was possible anyway. So does it do anything?
From CREATE TABLE:
NULL
The column is allowed to contain null values. This is the default.
You could write:
INSERT INTO test(bla)
VALUES (NULL);
-- it holds NULL
INSERT INTO test(bla)
VALUES (default);
-- it holds NULL
INSERT INTO test(bla)
VALUES ('a');
-- it holds 'a'
You could also omit column:
CREATE TABLE test2(bla VARCHAR NULL, col2 INT NOT NULL);
INSERT INTO test2(col2) VALUES (1);
-- it contains NULL, 1
If you specify column as:
CREATE TABLE test(bla VARCHAR NOT NULL);
INSERT INTO test(bla) VALUES (NULL);
-- error
EDIT:
You don't have to specify NULL explicitly.
CREATE TABLE test(bla VARCHAR);
is the same as:
CREATE TABLE test (bla VARCHAR NULL);
You are parameterizing the column can be NULL

SQL Column definition: default value and not null redundant?

I've seen many times the following syntax which defines a column in a create/alter DDL statement:
ALTER TABLE tbl ADD COLUMN col VARCHAR(20) NOT NULL DEFAULT "MyDefault"
The question is: since a default value is specified, is it necessary to also specify that the column should not accept NULLs? In other words, doesn't DEFAULT render NOT NULL redundant?
DEFAULT is the value that will be inserted in the absence of an explicit value in an insert / update statement. Lets assume, your DDL did not have the NOT NULL constraint:
ALTER TABLE tbl ADD COLUMN col VARCHAR(20) DEFAULT 'MyDefault'
Then you could issue these statements
-- 1. This will insert 'MyDefault' into tbl.col
INSERT INTO tbl (A, B) VALUES (NULL, NULL);
-- 2. This will insert 'MyDefault' into tbl.col
INSERT INTO tbl (A, B, col) VALUES (NULL, NULL, DEFAULT);
-- 3. This will insert 'MyDefault' into tbl.col
INSERT INTO tbl (A, B, col) DEFAULT VALUES;
-- 4. This will insert NULL into tbl.col
INSERT INTO tbl (A, B, col) VALUES (NULL, NULL, NULL);
Alternatively, you can also use DEFAULT in UPDATE statements, according to the SQL-1992 standard:
-- 5. This will update 'MyDefault' into tbl.col
UPDATE tbl SET col = DEFAULT;
-- 6. This will update NULL into tbl.col
UPDATE tbl SET col = NULL;
Note, not all databases support all of these SQL standard syntaxes. Adding the NOT NULL constraint will cause an error with statements 4, 6, while 1-3, 5 are still valid statements. So to answer your question: No, they're not redundant.
Even with a default value, you can always override the column data with null.
The NOT NULL restriction won't let you update that row after it was created with null value
My SQL teacher said that if you specify both a DEFAULT value and NOT NULLor NULL, DEFAULT should always be expressed before NOT NULL or NULL.
Like this:
ALTER TABLE tbl ADD COLUMN col VARCHAR(20) DEFAULT "MyDefault" NOT NULL
ALTER TABLE tbl ADD COLUMN col VARCHAR(20) DEFAULT "MyDefault" NULL
I would say not.
If the column does accept null values, then there's nothing to stop you inserting a null value into the field. As far as I'm aware, the default value only applies on creation of a new row.
With not null set, then you can't insert a null value into the field as it'll throw an error.
Think of it as a fail safe mechanism to prevent nulls.
In other words, doesn't DEFAULT render NOT NULL redundant ?
No, it is not redundant. To extended accepted answer. For column col which is nullable awe can insert NULL even when DEFAULT is defined:
CREATE TABLE t(id INT PRIMARY KEY, col INT DEFAULT 10);
-- we just inserted NULL into column with DEFAULT
INSERT INTO t(id, col) VALUES(1, NULL);
+-----+------+
| ID | COL |
+-----+------+
| 1 | null |
+-----+------+
Oracle introduced additional syntax for such scenario to overide explicit NULL with default DEFAULT ON NULL:
CREATE TABLE t2(id INT PRIMARY KEY, col INT DEFAULT ON NULL 10);
-- same as
--CREATE TABLE t2(id INT PRIMARY KEY, col INT DEFAULT ON NULL 10 NOT NULL);
INSERT INTO t2(id, col) VALUES(1, NULL);
+-----+-----+
| ID | COL |
+-----+-----+
| 1 | 10 |
+-----+-----+
Here we tried to insert NULL but get default instead.
db<>fiddle demo
ON NULL
If you specify the ON NULL clause, then Oracle Database assigns the DEFAULT column value when a subsequent INSERT statement attempts to assign a value that evaluates to NULL.
When you specify ON NULL, the NOT NULL constraint and NOT DEFERRABLE constraint state are implicitly specified.
In case of Oracle since 12c you have DEFAULT ON NULL which implies a NOT NULL constraint.
ALTER TABLE tbl ADD (col VARCHAR(20) DEFAULT ON NULL 'MyDefault');
ALTER TABLE
ON NULL
If you specify the ON NULL clause, then Oracle Database assigns the
DEFAULT column value when a subsequent INSERT statement attempts to
assign a value that evaluates to NULL.
When you specify ON NULL, the NOT NULL constraint and NOT DEFERRABLE
constraint state are implicitly specified. If you specify an inline
constraint that conflicts with NOT NULL and NOT DEFERRABLE, then an
error is raised.