Related
Trying to
create trigger that is called on INSERT & sets originId = id (AUTO_INCREMENT),
I've used SQL suggested here in 1st block:
CREATE TRIGGER insert_example
BEFORE INSERT ON notes
FOR EACH ROW
SET NEW.originId = (
SELECT AUTO_INCREMENT
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'notes'
);
Due to information_schema caching I have also set
information_schema_stats_expiry = 0
in my.cnf file. Now information gets updated almost instantly on every INSERT, as I've noticed..
But, performing "direct" INSERTs via console with ~2min intervals, I keep getting not updated AUTO_INCREMENT values in originId.
(They shoud be equal to id fields)
While explicit queries, fetching AUTO_) result in updated correct values.
Thus I suspect that the result of SELECT AUTO_INCREMENT... subquery gets somehow.. what? cached?
How can one get around this?
Thank you.
Edit 1
I intended to implement sort of VCS this way:
User creates new Note, app marks it as 'new' and performs an INSERT in MySQL table. It is the "origin" note.
Then user might edit this Note (completely) in UI, app will mark is as 'update' and INSERT it in MySQL table as a new row, again. But this time originId should be filled with an id of "origin" Note (by app logics). And so on.
This allows PARTITIONing by originId on SELECT, fetching only latest versions to UI.
initial Problem:
If originId of "origin" Note is NULL, MySQL 8 window function(s) in default (and only?) RESPECT_NULL mode perform(s) framing not as expected ("well, duh, it's all about your NULLs in grouping-by column").
supposed Solution:
Set originId of "origin" Notes to id on their initial and only INSERT, expecting 2 benefits:
Easily fetch "origin" Notes via originId = id,
perform correct PARTITION by originId.
resulting Problem:
id is AUTO_INCREMENT, so there's no way (known to me) of getting its new value (for the new row) on INSERT via backend (namely, PHP).
supposed Solution:
So, I was hoping to find some MySQL mechanism to solve this (avoiding manipulations with id field) and TRIGGERs seemed a right way...
Edit 2
I believed automated duplicating id AUTO_INCREMENT field (or any field) within MySQL to be extra fast & super easy, but it totally doesn't appear so now..
So, possibly, better way is to have vcsGroupId UNSIGNED INT field, responsible for "relating" Note's versions:
On create and "origin" INSERT - fill it with MAX(vcsGroupId) + 1,
On edit and "version" INSERT - fill it with "sibling"/"origin" vcsGroupId value (fetched with CTE),
On view and "normal" SELECT - perform framing with Window Function by PARTITION BY vcsGroupId, ORDER BY id or timestamp DESC, then just using 1st (or ascending order by & using last) row,
On view and "origin" SELECT - almost the same, but reversed..
It seems easier, doesn't it?
What you are doing is playing with fire. I don't know exactly what can go wrong with your trigger (beside that it doesn't work for you already), but I have a strong feeling that many things can and will go wrong. For example: What if you insert multiple rows in a single statement? I don't think, that the engine will update the information_schema for each row. And it's going to be even worse if you run an INSERT ... SELECT statement. So using the information_schema for this task is a very bad idea.
However - The first question is: Why do you need it at all? If you need to save the "origin ID", then you probably plan to update the id column. That is already a bad idea. And assuming you will find a way to solve your problem - What guarantees you, that the originId will not be changed outside the trigger?
However - the alternative is to keep the originId column blank on insert, and update it in an UPDATE trigger instead.
Assuming this is your table:
create table vcs_test(
id int auto_increment,
origin_id int null default null,
primary key (id)
);
Use the UPDATE trigger to save the origin ID, when it is changed for the first time:
delimiter //
create trigger vcs_test_before_update before update on vcs_test for each row begin
if new.id <> old.id then
set new.origin_id = coalesce(old.origin_id, old.id);
end if;
end;
delimiter //
Your SELECT query would then be something like this:
select *, coalesce(origin_id, id) as origin_id from vcs_test;
See demo on db-fiddle
You can even save the full id history with the following schema:
create table vcs_test(
id int auto_increment,
id_history text null default null,
primary key (id)
);
delimiter //
create trigger vcs_test_before_update before update on vcs_test for each row begin
if new.id <> old.id then
set new.id_history = concat_ws(',', old.id_history, old.id);
end if;
end;
delimiter //
The following test
insert into vcs_test (id) values (null), (null), (null);
update vcs_test set id = 5 where id = 2;
update vcs_test set id = 4 where id = 5;
select *, concat_ws(',', id_history, id) as full_id_history
from vcs_test;
will return
| id | id_history | full_id_history |
| --- | ---------- | --------------- |
| 1 | | 1 |
| 3 | | 3 |
| 4 | 2,5 | 2,5,4 |
View on DB Fiddle
Hopelessly stuck at the following and up until now none of my programming speed dial buddies has been able to help out (most of them not MySQL experts):
I have different tables where the column names and datatypes are auto generated from the 'import table data wizard' using a CSV file, and the table does not contain an AUTO INCREMENT column (yet). This particular table consists of approx: 30.000 rows It starts at row=id(1) from a table that looks like this:
I am trying to correct values in one column that are comma delimited using one 'corrections' table. And to do this I am writing a stored procedure containing a WHILE loop to interate through the corrections table row for row, and check wheter or not an Alias is found in the table that was imported.
| id | material | alias01 | alias02 | alias03 | *up to 12
1 Katoen Cotton Supima Pima
2 Polyester Polyster
3 Lyocell Lycocell Lyocel
4 Linnen Linen
5 Viscose Visose Viskose Viscoe Voscose
6 Scheerwol
7 Polyamide
8 Nylon
9 Leer Leder Lamsleder Varkensleder
10 Polyurethaan Polyurethan PU Polyuretaan
For testing purposes to test any kind of results i am only using alias01 for now ( it needs to check alias01, then 02 etc... but i'll try to solve that at a later time).
It needs to compare `Length' ( alias_string_length = found_string_length) to make sure that a string that consist of 'wo' is not found in 'wool' or 'wol'.
The values from the column that need corrections look like this (the comma's dont need to be there it's just what i was given to work with):
| material |
,Katoen,Elastaan,Voering,Acetaat,Polyester
,Nylon,Polyester,Elastaan
,Katoen
,Leder,in,Leder,Loopzool,Leder
,Polyester
,Polyester,Elastaan,Voering,Polyester
Update
Thanks to Drew's tip i changed the procedure. I added a tmp table that holds materials AND a unique id for each row, and iterate through each one with the alias01. It takes around 11 seconds to do 9000 rows but 0 row(s) affected,. Any tips on increasing speed are most welcome, but insight in what might be the issue would help alot more.
CREATE DEFINER=`root`#`localhost` PROCEDURE `replace_materials`()
BEGIN
set #rownumber = 1;
set #totalrows = 28;
set #um ='';
set #cm ='';
set #corrected ='';
set #correctme ='';
TRUNCATE TABLE tmp;
INSERT INTO tmp (material) SELECT material FROM vantilburgonline.productinfo;
WHILE (#rownumber < #totalrows) DO
SET #um = (SELECT alias01 FROM vantilburgonline.materials WHERE id=#rownumber);
-- gives 'um' value from column alias01, from table materials, row(X)
SET #cm = (SELECT material FROM vantilburgonline.materials WHERE id=#rownumber);
-- gives 'cm' value from column material, from table materials, row(X)
set #tmprow = 1;
set #totaltmprow =9000;
WHILE (#tmprow < #totaltmprow) DO
SET #correctme = (SELECT material FROM vantilburgonline.tmp WHERE id = #tmprow);
-- gives the value from column material from table tmp to correctme(X).
SET #correctme = REPLACE(#correctme,#um,#cm);
-- should run through column material from table productinfo and replace 'alias01' with correct 'material'.
SET #tmprow = #tmprow +1;
END WHILE;
SET #rownumber = #rownumber +1;
END WHILE;
END
though i'm certain alias01 contains strings it should've found in the materials. Also Workbench was using 9GB at this point and i was only able to counter that by restarting..
I would recommend an alteration from your materials table which is unwieldy with multiple columns (alias01 .. alias12). A transition to a normalized, extensible system. It would have a materials table and a materials_alias table. As it sits alongside your current table that you created, I named them with a 2.
Schema
drop table if exists materials2;
create table materials2
( material varchar(100) primary key, -- let's go with a natural key
active bool not null -- turn it LIVE and ON for string replacement of alias back to material name
-- so active is TRUE for ones to do replacement, or FALSE for skip
-- facilitates your testing of your synonyms, translations, slangs, etc
)engine=INNODB;
insert materials2 (material,active) values
('KARTON',true),
('Polyester',false),
('Lyocell',false),
('Linnen',true),
('Viscose',true),
('Scheerwol',false),
('Nylon',false),
('Leer',true),
('Polyurethaan',true),
('Polyacryl',true),
('Acryl',false),
('Modal',true),
('Acetaat',true),
('Papier',false),
('Wol',true),
('Zijde',true),
('Temcal',false),
('Polyamide',true),
('Wol-Merino',true),
('Elastan',true),
('Elastomultiester',true);
-- 21 rows
-- a few rows were skipped. The intent of them read as gibberish to me. Please review.
-- we need to restructure the materials2_alias table (after the first attempt)
-- 1. it might need special handling when `alias` is a legitimate substring of `material` (those 2 columns)
-- 2. it needs a unique composite index
drop table if exists materials2_alias;
create table materials2_alias
( id int auto_increment primary key,
material varchar(100) not null,
alias varchar(100) not null,
ais bool not null, -- Alias is Substring (alias is a legitimate substring of material, like Wo and Wol, respectively)
unique key(material,alias), -- Composite Index, do not allow dupe combos (only 1 row per combo)
foreign key `m2alias_m2` (material) references materials2(material)
)engine=INNODB;
insert materials2_alias (material,alias,ais) values
('KARTON','Cotton',false),('KARTON','Katoen',false),('KARTON','Pima',false),
('Polyester','Polyster',false),
('Lyocell','Lycocell',false),('Lyocell','Lyocel',false),
('Linnen','Linen',false),
('Viscose','Visose',false),('Viscose','Viskose',false),('Viscose','Viscoe',false),('Viscose','Voscose',false),
('Leer','Leder',false),('Leer','Lamsleder',false),('Leer','Varkensleder',false),('Leer','Schapenleder',false),('Leer','Geitenleder',false),
('Polyurethaan','Polyurethan',false),('Polyurethaan','PU',false),('Polyurethaan','Polyuretaan',false),('Polyurethaan','Polyurathane',false),('Polyurethaan','Polyurtaan',false),('Polyurethaan','Polyueretaan',false),
('Polyacryl','Polyacrylic',false),
('Acetaat','Leder',false),('Acetaat','Lamsleder',false),
('Wol','Schuurwol',false),('Wol','Wool',false),('Wol','WO',false),('Wol','Scheerwol',false),
('Zijde','Silk',false),('Zijde','Sede',false),
('Polyamide','Polyamie',false),('Polyamide','Polyamid',false),('Polyamide','Poliamide',false),
('Wol-Merino','Merino',false),
('Elastan','Elastaan',false),('Elastan','Spandex',false),('Elastan','Elataan',false),('Elastan','Elastane',false),
('Elastomultiester','elastomutltiester',false),('Elastomultiester','Elasomultiester',false);
-- this cleans up the above, where false should have been true
update materials2_alias
set ais=true
where instr(material,alias)>0;
-- 4 rows
There are several alter table statements and other things. I will try to document them or link to them. I am merely trying to capture something to share considering it is several hundred lines of code from you. But mine comes down to a simple chunk of code you would put in a loop.
The Update put in a loop:
UPDATE productinfo pi
join materials2_alias ma
on instr( pi.material, concat(',',ma.alias,',') )>0
join materials2 m
on m.material=ma.material and m.active=true
set pi.material=replace(lower(pi.material),lower(ma.alias),lower(ma.material)),
pi.touchCount=pi.touchCount+1;
A few notes on the update:
-- Note, pi.material starts and ends with a comma.
-- I forced that during the ETL. But `ma.alias` does not contain commas.
-- So add the commas with a concat() within the "Update with a Join" pattern shown
--
-- Note that the commas solved the problem with the Wol - Wo
Well, the following 4 in particular.
select * from materials2_alias
where ais=true
order by material,alias;
+----+------------+----------+-----+
| id | material | alias | ais |
+----+------------+----------+-----+
| 6 | Lyocell | Lyocel | 1 |
| 33 | Polyamide | Polyamid | 1 |
| 28 | Wol | WO | 1 |
| 35 | Wol-Merino | Merino | 1 |
+----+------------+----------+-----+
-- instr() is not case sensitive except for binary strings
-- REPLACE(str,from_str,to_str); -- case sensitive
-- http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_replace
--
-- so the update uses lower() or this won't work due to replace() case sensitivity
--
Stored Procedure:
DROP PROCEDURE if exists touchCounts;
DELIMITER $$
CREATE PROCEDURE touchCounts()
BEGIN
select touchCount,count(*) as rowCount
from productinfo
group by touchCount
order by touchCount;
END $$
DELIMITER ;
When that stored procedure returns the same count of rows on a successive call (the next call), you are done modifying the material column via the update.
That stored procedure could naturally return an out parameter for the rowcount. But it is late and time to sleep.
For your last data set from your side, the update statement would need to be called 4 times. That is like 13 seconds on my mediocre laptop. The idea is naturally flexible, for hundreds of aliases per material if you want.
I parked it up on github as it is too much otherwise.
I have a table with a enum column called action. The permitted values currently are:
act1,act2,act3,act4. I want act3 and act4 to be removed and my table's current state does not contain any rows with act3 or act4.
When I'm trying to modify the column with the new set of values it's throwing an error
Data Truncated for column action.
Please suggest how do I remove the required values.
Using ALTER TABLE for adding enum values is ok and described in the MySQL documentation.
However, for removing enum values the better option is to create a new column to do the change.
ALTER TABLE your_table ADD new_action_column ENUM('act1', 'act2') ... ;
UPDATE your_table SET new_action_column = action;
ALTER TABLE your_table DROP action;
ALTER TABLE your_table CHANGE new_action_column action ENUM('act1', 'act2') ... ;
Edit
By the way. Using ENUM is not the best idea, you should use INT instead.
8 Reasons Why MySQL's ENUM Data Type Is Evil
I suggest you to use a mapping like
+------+-----+
| ENUM | INT |
+======+=====+
| act1 | 0 |
+------+-----+
| act2 | 1 |
+------+-----+
First run a query.
UPDATE table_name SET action = '' WHERE action IN ( 'act3', 'act4' );
after this run this query.
ALTER TABLE table_name CHANGE action action ENUM( 'act1', 'act2' );
there is no need to drop your table or drop your field. but you are required to delete or update all data having the values, which you want to remove.
The other two answers already covered the question in larger detail, but here is a simple issue why you may not be able to simply do ALTER TABLE. If you have an ENUM('BAR','FOO','REMOVEME') and it gives an Error saying something along the lines of Data truncated somethingsomething, you might already have an entry set to the very Enum member you want to remove. So you'd first need to do something like
UPDATE yourtable SET enumrow='FOO' WHERE yourenumrow = 'REMOVEME';
This way, all entries that had REMOVEME will now be FOO and the table can be altered using
ALTER TABLE yourtable CHANGE yourenumrow yourenumrow ENUM('FOO','BAR') DEFAULT NULL;
I've just imported a bunch of data to a MySQL table and I have a column "GUID" that I want to basically fill down all existing rows with new and unique random GUID's.
How do I do this in MySQL ?
I tried
UPDATE db.tablename
SET columnID = UUID()
where columnID is not null
And just get every field the same
I had a need to add a guid primary key column in an existing table and populate it with unique GUID's and this update query with inner select worked for me:
UPDATE sri_issued_quiz SET quiz_id=(SELECT uuid());
So simple :-)
I'm not sure if it's the easiest way, but it works. The idea is to create a trigger that does all work for you, then, to execute a query that updates your table, and finally to drop this trigger:
delimiter //
create trigger beforeYourTableUpdate BEFORE UPDATE on YourTable
FOR EACH ROW
BEGIN
SET new.guid_column := (SELECT UUID());
END
//
Then execute
UPDATE YourTable set guid_column = (SELECT UUID());
And DROP TRIGGER beforeYourTableUpdate;
UPDATE
Another solution that doesn't use triggers, but requires primary key or unique index :
UPDATE YourTable,
INNER JOIN (SELECT unique_col, UUID() as new_id FROM YourTable) new_data
ON (new_data.unique_col = YourTable.unique_col)
SET guid_column = new_data.new_id
UPDATE once again:
It seems that your original query should also work (maybe you don't need WHERE columnID is not null, so all my fancy code is not needed.
The approved solution does create unique IDs but on first glance they look identical, only the first few characters differ.
If you want visibly different keys, try this:
update CityPopCountry set id = (select md5(UUID()));
MySQL [imran#lenovo] {world}> select city, id from CityPopCountry limit 10;
+------------------------+----------------------------------+
| city | id |
+------------------------+----------------------------------+
| A Coruña (La Coruña) | c9f294a986a1a14f0fe68467769feec7 |
| Aachen | d6172223a472bdc5f25871427ba64e46 |
| Aalborg | 8d11bc300f203eb9cb7da7cb9204aa8f |
| Aba | 98aeeec8aa81a4064113764864114a99 |
| Abadan | 7aafe6bfe44b338f99021cbd24096302 |
| Abaetetuba | 9dd331c21b983c3a68d00ef6e5852bb5 |
| Abakan | e2206290ce91574bc26d0443ef50fc05 |
| Abbotsford | 50ca17be25d1d5c2ac6760e179b7fd15 |
| Abeokuta | ab026fa6238e2ab7ee0d76a1351f116f |
| Aberdeen | d85eef763393862e5fe318ca652eb16d |
+------------------------+----------------------------------+
I'm using MySQL Server version: 5.5.40-0+wheezy1 (Debian)
select #i:=uuid();
update some_table set guid = (#i:=uuid());
Just a minor addition to make as I ended up with a weird result when trying to modify the UUIDs as they were generated. I found the answer by Rakesh to be the simplest that worked well, except in cases where you want to strip the dashes.
For reference:
UPDATE some_table SET some_field=(SELECT uuid());
This worked perfectly on its own. But when I tried this:
UPDATE some_table SET some_field=(REPLACE((SELECT uuid()), '-', ''));
Then all the resulting values were the same (not subtly different - I quadruple checked with a GROUP BY some_field query). Doesn't matter how I situated the parentheses, the same thing happens.
UPDATE some_table SET some_field=(REPLACE(SELECT uuid(), '-', ''));
It seems when surrounding the subquery to generate a UUID with REPLACE, it only runs the UUID query once, which probably makes perfect sense as an optimization to much smarter developers than I, but it didn't to me.
To resolve this, I just split it into two queries:
UPDATE some_table SET some_field=(SELECT uuid());
UPDATE some_table SET some_field=REPLACE(some_field, '-', '');
Simple solution, obviously, but hopefully this will save someone the time that I just lost.
Looks like a simple typo. Didn't you mean "...where columnId is null"?
UPDATE db.tablename
SET columnID = UUID()
where columnID is null
I faced mostly the same issue.
Im my case uuid is stored as BINARY(16) and has NOT NULL UNIQUE constraints.
And i faced with the issue when the same UUID was generated for every row, and UNIQUE constraint does not allow this. So this query does not work:
UNHEX(REPLACE(uuid(), '-', ''))
But for me it worked, when i used such a query with nested inner select:
UNHEX(REPLACE((SELECT uuid()), '-', ''))
Then is produced unique result for every entry.
MYsql
UPDATE tablename SET columnName = UUID()
oracle
UPDATE tablename SET columnName = SYS_GUID();
SQLSERVER
UPDATE tablename SET columnName = NEWID();;
UPDATE db.tablename SET columnID = (SELECT UUID()) where columnID is not null
// UID Format: 30B9BE365FF011EA8F4C125FC56F0F50
UPDATE `events` SET `evt_uid` = (SELECT UPPER(REPLACE(#i:=UUID(),'-','')));
// UID Format: c915ec5a-5ff0-11ea-8f4c-125fc56f0f50
UPDATE `events` SET `evt_uid` = (SELECT UUID());
// UID Format: C915EC5A-5FF0-11EA-8F4C-125FC56F0F50
UPDATE `events` SET `evt_uid` = (SELECT UPPER(#i:=UUID()));
I got this error when using mysql as sql_mode = "". After some testing, I decided that the problem was caused by this usage. When I tested on the default settings, I found that this problem was not there.
Note: Don't forget to refresh your connection after changing the mode.
SELECT CONCAT(SUBSTRING(REPLACE(UUID(),'-',''), 1, 5), SUBSTRING(UPPER(REPLACE(UUID(),'-','')), 4, 5), SUBSTRING('##$%(*&', FLOOR(RAND()*(1-8))+8, 1)) pass
I did this SELECT, five character lower case, five character upper case and one special character.
Don't know if this is possible for MySQL because I know that it doesn't support check constraints, but what I want is to make two columns unique. Before you answer with
ALTER TABLE <table_name> ADD UNIQUE(<col1>, <col2>);
That's not what I want. I would like to ensure that col1 and col2 have unique values so if they are INTs, number "1" can exist only once between both columns, which means if col1 contains "1", col2 cannot contain "1" and "1" can only appear once in col1. Hopefully that makes sense.
I know I can do it from a php level, but right now there is a lot of code, and if I miss a spot, I don't want to destroy data integrity; I rather throw an error from the database. Some ideas that I've come across is using triggers. If someone can give me an example of using triggers to accomplish this, that would be great.
UPDATE
It might help if you knew what I was doing, then maybe you can propose a better way of doing this:
I have two fields, email and new_email. When someone changes their email address, I store it into the new_email field until the accept the change. Since email is unique because it's used as their login, I HAVE to ensure that the email is unique across both fields.
Solution
Create a table MakeColsUnique with one column ColumnBoth
Create a Primary Key on ColumnBoth
All all values from Col1 and Col2 to this table (if you have existing duplicates, this will fail)
Add a trigger on OriginalTable on INSERT or UPDATE to insert the value from Col1 and Col2 into the new table MakeColsUnique
If the value has already been inserted, the insert or update will fail.
I think you should try to reorganize your database. Let's say currently you have this:
Table: users
id name email new_email
102 foo foo#mail.com foo2#mail.com
103 bar bar#mail.com bar2#mail.com
104 baz baz#mail.com NULL
This could be changed to:
Table: users
id name
102 foo
103 bar
104 baz
Table: emails
user_id is_new email
102 0 foo#mail.com
102 1 foo2#mail.com
103 0 bar#mail.com
103 1 bar2#mail.com
104 0 baz#mail.com
You can then add a unique index on the final table on the column email.
You can't enforce that with a key constraint. Honestly the requirement sounds a little bit odd, and I think you're probably better off extracting col1 and col2 into a separate table, say cols.
As per you update on the question a database constraint is not a valid option, because you want to store the email for a while in both the fields and then accept it once the user accepts it. It looks more like a logic that needs to be implemented in the application business logic than the database
Have you solved your problem? I have encountered this problem too, and finally I solve my problem with trigger, following your idea. Here is my sql:
delimiter |
CREATE TRIGGER unique_AB BEFORE INSERT ON test
FOR EACH ROW
BEGIN
DECLARE msg varchar(200);
DECLARE flag int;
set flag = (select count(*) from test where A = new.A or B = new.A or A = new.B or B = new.B);
IF flag > 0 THEN
set msg = "column duplicate!!!";
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = msg;
END IF;
END;
|
my table is as follow:
CREATE TABLE `test` (
`A` varchar(255) DEFAULT NULL,
`B` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
in the trigger, I made a select first and judge if there is already a value that is equals to my new line, and this worked for me.