MySQL conditional Insert for checking Single-value and Multi-value keys - mysql

I have a MySQL table with Columns (id, key, value). Keys are predefined strings. As an example key can be color, height, width, etc,...values can be single-valued or multi-valued.
Ex:-
height and width are single value keys
color is a multi-value key (For an object there can be multiple colors)
values for multi-value keys are stored in the database as a single string (with comma separated values)
My Problem is How can I enforce single value keys to be single-valued while inserting.
Ex:-
For the height key and width key value must be single-valued
For the color key value can be multi-valued
How Can I Enforce this condition while inserting? What will be the best way?
Edit :
Single valued attributes can be String ,Int Or Boolean.
Multi Valued attributes are Strings

To enforce this condition while inserting you can create BEFORE INSERT trigger and make your checks there. As far I understand your logic for "multi-valued" keys you can check whether they are in the database table and for "single-valued" you can check whether the value is a number.

I suppose you can add a CHECK constraint. A minimal example:
create table t(
id int,
`key` varchar(100),
`value` varchar(100),
check (case
when `key` in ('width', 'height') then `value` not like '%,%'
end = 1)
)
It is also possible to use REGEXP clause to check if numbers are in fact numbers.
DB<>Fiddle
Another improvement would be to store the values a JSON (boolean, string, number or array) then use json_type to check the type of value being saved.

Related

How to query a key in a SQL Server Json column if it could be a scalar value or an object?

I have a table that with a column Info VARCHAR(MAX) constrained to always be valid json. What is the best way to query a key with JSON_QUERY/JSON_VALUE if I don't know ahead of time if the value associated with the key is a scalar or not? Currently I am returning where either has a value as shown below.
SELECT ID, JSON_VALUE(Info, '$.Key') as json_val, JSON_QUERY(Info, '$.Key') as json_query
FROM TABLE
WHERE (JSON_VALUE(Info, '$.Key') IS NOT NULL OR JSON_QUERY(Info, '$.Key') IS NOT NULL)
and relying on the fact that the results are mutually exclusive.
The problem is the JSON_QUERY in the WHERE clause prevents any indexes on the virual column vKey AS JSON_VALUE(Info, '$.Key') from being used.
As suggested by #MartinSmith, you can add a computed column like this
ALTER TABLE YourTable
ADD YourColumn AS (ISNULL(JSON_VALUE(json, '$.key'), JSON_QUERY(json, '$.key')));
You can then index it, and the index will be used in queries automatically
CREATE INDEX IndexName ON YourTable (YourColumn) INCLUDE (OtherColumns)
Note that the index will normally be used even if you use the original expression rather than the new column name.
db<>fiddle

Alternatively setting Null values in columns

I am building a database and one of the tables contains the columns "sensor_id" and "station_id". When someone tries to insert a new row in the table we can have a NULL value in the "sensor_id" column but then we can not have a NULL value in the "station_id" under no circumstances. Vice versa, when the "station_id" is NULL, the "sensor_id" must not be NULL. If a value is entered at both columns that is not a problem.
I am currently working in MySQL Workbench and it seems that my choices are to set both columns as NN(Not NULL) which is too strict implementation as one of them is sufficient, to set just one of them as NN which means that one specific column must always be filled(not the case either) or set none of them NN which is too loose as at least one of both values must be given.
Visually the table looks like this(sorry for the Microsoft Word substitute but I have problems with MYSQL server and can not acces the database):
Alert_id is the primary key of the table, so duplicate values are allowed for the other two columns.
How could I implement this?
You should add contstraint to this table, like that:
CONSTRAINT CheckSensorStationNotNull CHECK (station_id is not null or sensor_id is not null)

How to avoid to rewrite attributes with AUTO_INCREMENT

If I have a table like this:
CREATE TABLE Person (id INT AUTO_INCREMENT PRIMARY KEY, age INT);
Is there a simpler method than rewriting the attributes of the column like that ?
INSERT INTO Person (age) VALUES (18);
I know that for the DEFAULT values there is the keyword DEFAULT, but is there a similar one for AUTO_INCREMENT? I work with pretty long tables and I don't want to rewrite all the column names each time I make an INSERT.
Thank you for your help.
https://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html says:
No value was specified for the AUTO_INCREMENT column, so MySQL assigned sequence numbers automatically. You can also explicitly assign 0 to the column to generate sequence numbers, unless the NO_AUTO_VALUE_ON_ZERO SQL mode is enabled. If the column is declared NOT NULL, it is also possible to assign NULL to the column to generate sequence numbers.
So any of the following will work:
INSERT INTO Person VALUES (0, 18);
INSERT INTO Person VALUES (NULL, 18);
INSERT INTO Person VALUES (DEFAULT, 18);
However, it's considered good practice to list all columns explicitly when you write any INSERT statement. If someone changes the order of columns in the table, your VALUES might not get inserted into the right columns unless you list the column names explicitly. Also if someone adds or drops a column.

How to replace null primary keys in MySql

I have to migrate an old Paradox database to MySql. The Paradox database contains composite primary keys with null values (composite keys are composed by 3,4 or 5 fields and it could have 1 or 2 fields with a Null value). The problem is null values in pk are not allowed in MySql. It is impossible to replace the null values directly in Paradox (5 millions of lines in some tables), so how could we do ?
Thanks in advance for your solutions.
Assuming nulls in PK means they aren't needed to be unique, they could be any value, so assign an arbitrary value to nulls.
To do this, create a trigger on the MySQL table like this:
delimiter //
create trigger trig_pk_col_1 before insert on mytable
for each row
begin
set new.pk_col_1 = coalesce(new.pk_col_1, 999);
set new.pk_col_2 = coalesce(new.pk_col_2, 999);
-- etc for other
end;//
delimiter ;
I've chosen 999 as the arbitrary non-null value, but you can chose anything.
If the logic needs to be more sophisticated, you can code it as you like.
Also, I used coalesce() for brevity, but you can use the more verbose if instead if you prefer:
if new.pk_col_1 is null then
set new.pk_col_1 = 999;
end if;
-- etc
I think you have two choices in the MySQL DB,
(a) use a PK, which requires to replace null values / ignore such rows
(b) use a UNIQUE-constraint, which still allows null values, instead of a PK.
For option (a),
I'd suggest to disable the primary key constraint and import the data; then do all necessary corrections and reactivate the primary key. If you want to get rid of rows with null values in the potential PK columns, you could also make use of the IGNORE-keyword, which skips rows violating PK-constraints when inserting (cf. primary key and unique index constraints)
Option (b),
should allow to import the data as is. You can then do corrections or leave the data as is.

SQL coalesce(): what type does the combined column have?

Lets say I use coalesce() to combine two columns into one in select and subsequently a view constructed around such select.
Tables:
values_int
id INTEGER(11) PRIMARY KEY
value INTEGER(11)
values_varchar
id INTEGER(11) PRIMARY KEY
value VARCHAR(255)
vals
id INTEGER(11) PRIMARY KEY
value INTEGER(11) //foreign key to both values_int and values_varchar
The primary keys between values_int and values_varchar are unique and that allows me to do:
SELECT vals.id, coalesce(values_int.value, values_varchar.value) AS value
FROM vals
JOIN values_int ON values_int.id = vals.value
JOIN values_varchar ON values_varchar.id = vals.value
This produces nice assembled view with ID column and combined value column that contains actual values from two other tables combined into single column.
What type does this combined column have?
When turned into view and then queried with a WHERE clause using this combined "value" column, how is that actually handled type-wise? I.e. WHERE value > 10
Som rambling thoughts in the need (most likely wrong):
The reason I am asking this is that the alternative to this design have all three tables merged into one with INT values in one column and VARCHAR in the other. That would of course produce a lots of NULL values in both columns but saved me the JOINs. For some reason I do not like that solution because it would require additional type checking to choose the right column and deal with the NULL values but maybe this presented design would require the same too (if the resulting column is actually VARCHAR). I would hope that it actually passes the WHERE clause down the view to the source (so that the column does NOT have a type per se) but I am likely wrong about that.
You query should be explicit to be clear, In this case mysql is using varchar.
I would write this query like this to be clear
coalesce(values_int.value,cast(values_varchar.value as integer), 0)
or
coalesce(cast(values_int.value as varchar(20)),values_varchar.value,'0')
you should put in that last value unless you want the column to be null if both columns are null.
Returns the data type of expression with the highest data type precedence. If all expressions are nonnullable, the result is typed as nonnullable.
So in your case the type will be VARCHAR(255)
Lets say I use coalesce() to combine two columns into one
NO, that's not the use of COALESCE function. It's used for choosing a provided default value if the column value is null. So in your case, if values_int.value IS NULL then it will select the value in values_varchar.value
coalesce(values_int.value, values_varchar.value) AS value
If you want to combine the data then use concatenation operator (OR) CONCAT() function rather like
concat(values_int.value, values_varchar.value) AS value
Verify it yourself. An easy way to check in MySQL is to DESCRIBE a VIEW you create to capture your dynamic column:
mysql> CREATE VIEW v AS
-> SELECT vals.id, coalesce(values_int.value, values_varchar.value) AS value
-> FROM vals
-> JOIN values_int ON values_int.id = vals.value
-> JOIN values_varchar ON values_varchar.id = vals.value;
Query OK, 0 rows affected (0.01 sec)
Now DESCRIBE v will show you what's what. Note that under MySQL 5.1, I see the column as varbinary(255), but under 5.5 I see varchar(255).