MySQL: Expression of generated column contains disallowed function? CONCAT? - mysql

I have a table with a virtual generated column that concatenates five other columns (int and char) using CONCAT_WS(). This table contains 200-odd records and is never updated - it's just used as a lookup table. Recently, after months of untroubled processing, when I update records in a child table during which a SELECT is performed on this table, I sometimes see this error (ignore the "ITEM UPDATE FAILED" - that's me):
I am in development with a many changes every day, so it is impossible for me to determine if there is a correlating change. I have recently added "created" and "lastmodified" datetime fields to several tables with CURRENT_TIMESTAMP for DEFAULT or ON UPDATE, but not to this table.
Here's the table:
{EDIT} --- adding table definition:
CREATE TABLE `cpct_fixedfield` (
`id` int(11) UNSIGNED NOT NULL,
`name` varchar(256) NOT NULL,
`label` varchar(50) NOT NULL,
`field` int(11) NOT NULL,
`start` int(11) NOT NULL,
`rectype` int(11) NOT NULL ,
`mediatype` char(1) NOT NULL DEFAULT '' ,
`length` int(11) NOT NULL,
`userdefined` tinyint(1) NOT NULL,
`defaultval` varchar(5) NOT NULL,
`helpcode` varchar(10) NOT NULL,
`mandatory` varchar(2) NOT NULL ,
`idx` varchar(20) GENERATED ALWAYS AS (concat_ws('.',`field`,`rectype`,`mediatype`,`start`,`length`)) VIRTUAL NOT NULL)
ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
The length of the data in field never exceeds 11chars. I can view the entire table in pma or Mysql Workbench and the virtual field materialises in all records without complaint, which suggests to me that there is nothing wrong with either the expression for the virtual column or the data in the columns that expression draws on.
The error occurs in several contexts when I am updating a child table. All the updates occur in Stored Procedures/Functions. One section of code that seems to trigger the error is this:
SET idxvar = CONCAT_WS(".", SUBSTRING(tmpfldkey,3,1), rectype, ptype, position, "%") COLLATE utf8mb4_general_ci;
SELECT id INTO ffid FROM cpct_fixedfield WHERE idx LIKE idxvar AND idx != "0.0..6.2";
All the variables involved are varchars or ints. utf8mb4_general_ci is used throughout the database.
I cannot find any reference in MYSQL documentation to CONCAT or CONCAT_WS being unsafe, and none of the columns referenced has a default using a non-deterministic function. All the other questions I can find in this forum and elsewhere about this error have arisen because of the use of non-deterministic functions like CURRENT_TIMESTAMP() in the virtual field, or a component of the field.
I replaced the SELECT on the table with a (large) CASE statement and all was well, and in fact, after I did this then reverted to the SELECT I had no errors for many hours. But it just happened again (so I'm back to the case statement).
I have run out of ideas - I'm hoping someone has some knowledge/experience that can help.
Thanks

Related

What object differences would cause DataGrip MariaDB 10.3 driver to detect a change diff using schema comparison?

I am using the compare schema function in DataGrip for two databases in MariaDB, a dev and a production database, to perform a migration.
Upon pressing compare, I see several pre-selected columns that suggest that there is a difference between two columns in the schema of the two database tables. However, I cannot detect a difference.
In the following image, I see the types of the columns matching along with all the characteristics. But everything seems to match - name, non-null quality, etc.
Generally this function works correctly, but in this particular example, I'm unsure of what the difference is, why only two of the 20 or so columns are selected, and what else could cause there to be a diff.
The DDL diff even suggests they are the same:
create table beneficiaries
(
id int unsigned auto_increment
primary key,
user_id int unsigned not null,
phone varchar(191) null,
cellphone varchar(191) null,
email varchar(191) null,
deleted_at timestamp null,
message_history longtext collate utf8mb4_bin default '[]' not null,
activity_history longtext collate utf8mb4_bin default '[]' not null,
admin_id int unsigned null,
created_at timestamp default current_timestamp() not null,
updated_at timestamp null on update current_timestamp()
)
collate = utf8mb4_unicode_ci;
This should be fixed in the upcoming release 2022.1

Pulling a random value out of a table is returning a null value

I have a stored procedure that I've used to 'de-identify' client information when I want to use it in a test environment. I am replacing actual names and addresses with random values. I have database tables in a database called dict (for dictionary) for female names, male names, last names, and addresses.
Each of these has a field called f_row_id that is a sequential number from 1 to x, one for each record in the table.
We recently upgraded to mySQL 8 and the stored procedure quit working. I ended up with NULL for every field where I tried filling in a random value out of the other table. In trying to find what will now work, I'm unable to get the following query to work as I expect:
SELECT
f_enroll_id,
(SELECT f_name FROM dict.dummy_female_first_name fn WHERE fn.f_row_id = (FLOOR(RAND() * 850) + 1) LIMIT 1)
FROM
t_enroll
My data table (that I eventually want to have contain random names) is called t_enroll. There is an ID field in that (f_enroll_id) I want to get a list of each ID and a random first name for each record in that table.
There are 850 records in the table of random first names (dummy_female_first_name) (in my stored procedure this is a session variable that I compute at the start of the procedure).
When I first tried running this I got an error that my sub-query returned more than one value. I don't understand why it would do that since (FLOOR(RAND() * 850) + 1) should return a single integer. So I added the LIMIT 1. But when I run this, about half of the returned rows have NULL for the first name.
I have verified that all the rows in my first name table have a row ID, that the row ID is unique, and there not any gaps in the numbers.
What do you think is causing this?
Thanks in advance!
Here is the schema for the table that I'm updating:
CREATE TABLE `t_enroll` (
`f_enroll_id` int(15) NOT NULL AUTO_INCREMENT,
`f_status` int(2) DEFAULT NULL,
`f_date_enrolled` date NOT NULL DEFAULT '0000-00-00',
`f_first_name` varchar(20) DEFAULT NULL,
`f_mi` char(1) DEFAULT NULL,
`f_last_name` varchar(20) NOT NULL DEFAULT '',
`f_maiden_name` varchar(20) DEFAULT NULL,
`f_dob` date NOT NULL DEFAULT '0000-00-00',
`f_date_fee_received` date NOT NULL DEFAULT '0000-00-00',
`f_gender` int(11) NOT NULL DEFAULT '2',
`f_address_1` varchar(40) DEFAULT NULL,
`f_address_2` varchar(20) DEFAULT NULL,
`f_quadrant` char(2) DEFAULT NULL,
`f_city` varchar(25) DEFAULT NULL,
`f_state` char(2) NOT NULL DEFAULT '',
`f_county` varchar(3) NOT NULL,
`f_zip_code` varchar(10) DEFAULT NULL,
PRIMARY KEY (`f_enroll_id`),
KEY `f_date_enrolled` (`f_date_enrolled`),
KEY `f_last_name` (`f_last_name`),
KEY `f_first_name` (`f_first_name`),
KEY `f_dob` (`f_dob`),
KEY `f_gender` (`f_gender`)
ENGINE=InnoDB AUTO_INCREMENT=532 DEFAULT CHARSET=latin1 COMMENT='InnoDB free: 15360 kB';
Here is the schema for the dictionary table where I pull names from:
CREATE TABLE `dummy_female_first_name` (
`f_row_id` int(11) NOT NULL,
`f_name` varchar(25) NOT NULL,
PRIMARY KEY (`f_row_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
As I mentioned in my comment, I have found an alternate approach using the ORDER BY RAND() LIMIT 1 variation. But I am still curious as to what is going on that prevented my original method to fail. This is something that changed in the more recent mySQL version because it used to work.
Thanks again.
It is a much more expensive approach, but you can use:
SELECT f_enroll_id,
(SELECT f_name FROM dict.dummy_female_first_name fn ORDER BY rand() LIMIT 1)
FROM t_enroll;
You can make this more efficient using:
SELECT f_enroll_id,
(SELECT f_name
FROM dict.dummy_female_first_name fn
WHERE rand() < 0.01
ORDER BY rand() LIMIT 1
)
FROM t_enroll;
The where clause means that about 8 rows will filter through so the sorting will be much faster.

Create Table and assign a query as default value to a field

CREATE TABLE `williamhill` (
`id` VARCHAR(50) NULL,
`nick` VARCHAR(50) NULL,
`password` VARCHAR(50) NULL DEFAULT (SELECT default_password FROM person p WHERE p.id = id),
`Colonna 4` VARCHAR(50) NULL
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
How can i do something like that in MYSQL? I'm trying to assign a default value from another table if no value has been specified during the creation of new row.
Is it possible like that or should i use a trigger/procedure?
MySQL is quite explicit that this cannot be done. To begin with, the syntax for default is called default value. A value is expected.
Here is how the documentation describes this:
The DEFAULT value clause in a data type specification indicates a
default value for a column. With one exception, the default value
must be a constant; it cannot be a function or an expression.
(The one exception is CURRENT_TIME which most people probably don't even realize is a function.)
Many databases do allow function calls here. None -- as far as I know -- allow query expressions. However, that can often be emulated in a user-defined function.
The work-around is to use a trigger. Looking up a value in another table is a fairly common use of triggers.

Setting up MySQL trigger syntax correctly and carefully

I've been doing a lot of new learning about MySQL and triggers. I think I understand the concept and I realise there are a LOT of possible dangers in using them. However I believe the limited use of them is correct for the function I want to perform.
I have 9 tables which correspond to 9 different web based Ajax engined forms. I've worked hard on these, being my first time using Ajax, and I'm reasonably happy with them. Each time a user makes a change to whichever form they are filling out, the change is Ajaxed back to the DB and they get a confirmation or error response. Fairly straight forward. Each forms respective table has a "status" field, a "lastModified" field and a field I call "agRef" which is sort of like status but is null until the form reaches a certain stage, further along the process.
I have an additional table called "records" which is where all entries in any of the other tables, is listed so we can easily see what forms have been started, when their last changes were made and what status's they have. So here is where I believe the trigger part should work, so that I don't have to make updates to the "records" table in my php on every single transaction.
The "records" table is set out like this:
`uaID` int(11) NOT NULL AUTO_INCREMENT,
`uID` int(11) NOT NULL,
`appNo` int(11) NOT NULL,
`applicationKey` varchar(8) NOT NULL,
`appID` int(11) DEFAULT NULL,
`applicationName` varchar(64) NOT NULL,
`agRef` varchar(32) DEFAULT NULL,
`status` varchar(32) NOT NULL,
`dateStarted` int(11) NOT NULL,
`lastModified` int(11) NOT NULL,
Now all of these fields are populated at the same time the matching entry is inserted into which ever one of the other 9 tables the form connects to. A small example of one of the other 9 tables would look like this:
`appID` int(11) NOT NULL AUTO_INCREMENT,
`uID` int(11) NOT NULL,
`uaID` int(11) NOT NULL,
`status` varchar(32) NOT NULL DEFAULT 'Data Acquisition',
`agRef` varchar(32) DEFAULT NULL,
`groupName` varchar(64) DEFAULT NULL,
`shortTitle` varchar(64) DEFAULT NULL,
`recipient` varchar(64) DEFAULT NULL,
`partOfValCh` varchar(64) DEFAULT NULL,
`sector` varchar(64) DEFAULT NULL,
`subSector` varchar(64) DEFAULT NULL,
`topic` varchar(64) DEFAULT NULL,
<snip because this can go on for a lot of lines>
`dateStarted` int(11) NOT NULL,
`lastModified` int(11) NOT NULL,
agRef on both tables remain null for now, appID is null on the records table initially at the point of creation but is updated immediately as soon as the corresponding entry is made into the second table, where it is generated by auto increment and then a call is made back to my records table to insert the appID there.
The three things that will change from any of the data tables are the three fields "status", "agRef", "lastModified".
So I'm trying to create a trigger that will do this after each alteration/update to the data table, so that the data in my records table is consistent and accurate.
This is my first ever trigger set up attempt:
DELIMITER $$
CREATE TRIGGER `dataTableOne_to_records_sync` AFTER UPDATE ON `dataTableOne`
FOR EACH ROW BEGIN
UPDATE records (agRef, status, lastModified) VALUES (NEW.agRef, NEW.status, NEW.lastModified) WHERE appID = OLD.appID;
END$$
DELIMITER ;
I am trying to set this up through phpmyadmin, but it is returning an error telling me I have a syntax problem within my UPDATE line. I feel that it is an issue with the WHERE part - the appID is the one common element that ties the row in "records" to the row being updated/changed in "dataTableOne". How do I set this up correctly? Is my error something more serious, and am I running the risk of creating a huge mess, like a never ending loop? I'm a bit paranoid about doing this for the first time. Thanks in advance for help and advice.
UPDATE I have now tried a few other trigger attempts but although MySQL will accept them as being valid trigger syntax, they always seem to break the entire DB functionality. Can anyone help me with my trigger syntax to get it to work correctly? In the demo tables above, if the SECOND table gets updated at all, I want the three fields copied over into the FIRST table by the trigger. The three values I want copied across are "status", "agRef", and "lastModified".
My most recent failed attempt was this:
CREATE TRIGGER AIGltInq_sync AFTER INSERT ON app_AIGltInq
FOR EACH ROW
UPDATE records r
SET r.agRef = NEW.agRef
, r.status = NEW.status
, r.lastModified = NEW.lastModified
WHERE uaID = NEW.uaID;
I'm not at all familiar with that form of the UPDATE statement.
To change values of columns in rows, we'd typically write an UPDATE statement like this:
UPDATE records r
SET r.agRef = NEW.agRef
, r.status = NEW.status
, r.lastModified = NEW.lastModified
WHERE r.appId = OLD.appID
Reference: https://dev.mysql.com/doc/refman/5.5/en/update.html
Question withdrawn. Triggers are seriously best to avoid, as they tend to cause more breakages than they fix! Most recommendations tend towards handling the function with whatever scripting language you are using to talk with the DB. In my case this is PHP and PHP is now performing all of the functionality I was hoping to short-cut by using triggers. Lesson? Don't take short-cuts when wanting to do the job right. :)

MYSQL allowing me to insert nulls in PK and FK

I have been testing a database i am doing right now and i am noticing that it is letting me insert null values into fields that are part of a primary key, despite stating in the script that the value of the field should be NOT NULL. I am using MAC's MySQL Workbench, and I have been googling around and can't figure out why this is happening. (Maybe I am too brain-fried right now... I am even starting to doubt myself)
Part of the script of the database creation (these are the tables I have tested..):
DROP DATABASE IF EXISTS solytierra ;
CREATE DATABASE IF NOT EXISTS solytierra DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci ;
USE solytierra ;
DROP TABLE IF EXISTS solytierra.Cliente ;
CREATE TABLE IF NOT EXISTS solytierra.Cliente (
CIF VARCHAR(25) NOT NULL,
Nombre VARCHAR(100) NULL,
EmailGeneral VARCHAR(45) NULL,
Web VARCHAR(45) NULL,
Notas VARCHAR(150) NULL,
insertado Timestamp,
CONSTRAINT pk_Cliente PRIMARY KEY (CIF)
) ENGINE=InnoDB;
DROP TABLE IF EXISTS solytierra.PersonaContacto ;
CREATE TABLE IF NOT EXISTS solytierra.PersonaContacto (
Cliente_CIF VARCHAR(25) NOT NULL,
Nombre VARCHAR(50) NOT NULL,
Apellidos VARCHAR(100) NOT NULL,
Notas VARCHAR(150) NULL,
CONSTRAINT pk_PersonaContacto PRIMARY KEY (Cliente_CIF , Nombre , Apellidos),
CONSTRAINT fk_PersonaContacto_Cliente FOREIGN KEY (Cliente_CIF)
REFERENCES solytierra.Cliente (CIF)
ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB;
...
It will let me create Clients without CIF, "PersonaContacto" without Cliente_CIF or without "Nombre"....
I have also tested other databases that i already had that used to work and it is happening the same in an all them.
Got it!!
I don't know what sql mode i was running on by default, but with this:
SET sql_mode = TRADITIONAL;
It is now running perfectly! I didn't know that there were different sql modes! Thanks a lot to everyone for your time and efforts! It really helped me to see that the problem was in my workbench, not the code and look for the answer accordingly! I hope this thread will be useful for future beginners like me!
If the value being stored in the column CIF is actually a NULL, then the expression LENGTH(CIF) should also return NULL. (If it's a zero length string, then LENGTH(CIF) will return 0.
To verify:
SELECT c.CIF, LENGTH(c.CIF) FROM solytierra.Cliente c ;
SELECT c.CIF FROM solytierra.Cliente c WHERE c.CIF IS NULL;
If you are running an INSERT statement, I can't explain the behavior you are observing, either MySQL allowing a NULL value to be stored or MySQL providing an implicit default value.)
If it's a zero length string being stored, that's the behavior we would expect if the columns were not explicitly declared to be NOT NULL but were later declared to part of the primary key. It's also the behavior we'd expect if the column were defined NOT NULL DEFAULT ''.
When the NOT NULL is omitted from the column declaration and the column is later declared to be part of the PRIMARY KEY, MySQL will use an an implicit default value based on the datatype of the column (zero length string for VARCHAR, zero for an integer, etc.)
But I'm not able to reproduce the problem you report, with the table definitions you've posted.
I recommend you check the table definition by getting the output from:
SHOW CREATE TABLE solytierra.Cliente;