Adding constraints in the MySQL database to guarantee uniqueness - mysql

My SQL table has the following DDL
CREATE TABLE `new_table` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`family_id` int(11) NOT NULL,
`name` varchar(45) NOT NULL,
PRIMARY KEY (`id`)
)
I want to hold the family names in this simple table. In order to do this I have a microservice where the caller sends via JSON the family details:
{
"family_id" : 1,
"names": ["name1", "name2"]
}
The id is generated via auto increment from MySQL.
So the above JSON will finally trigger two insert statements:
insert (family_id, name) values ( 1, name1)
insert (family_id, name) values ( 1, name2)
The problem arises when a new request comes with a family_id that exists in the table. This should not be allowed, and I am doing a query in order to search if the family_id exists or not. If it exists an exception is raised. How can I avoid this query? The table schema can be altered if needed. Would it be OK if a could add something like a "request id", or a guid to establish uniqueness per request?
All data should be on the same table.
Below an example of the table with some data
(from Comment) I cannot create a second table. Eveything should be kept in one table.

You should normalize your schema and use two tables.
Family and (I assume) Person. Then you can use a UNIQUE constraint for the family_id and add the family_id as foreign key into the Person table.

You need two tables.
CREATE TABLE Families (
family_id MEDIUMINT UNSIGNED AUTO_INCREMENT,
...
PRIMARY KEY(family_id)
);
CREATE TABLE FamilyNames (
family_id MEDIUMINT UNSIGNED, -- not auto-inc here
name VARCHAR(66) NOT NULL,
...
PRIMARY KEY(family_id, name) -- note "composite"
);
A PRIMARY KEY is a UNIQUE KEY is a KEY.
You say you cannot add a second table. But why? You mention needing to generate a particular JSON? Can't that simply be done via a JOIN of the two tables if necessary?

If you can't create a second table to model the constraint properly, then you will have to resort to serializing inserts:
LOCK TABLES new_table WRITE;
Use a SELECT to check if the family id exists in the table.
If the family id is not present, INSERT your new data.
UNLOCK TABLES;
It's necessary to lock the table because otherwise you will have a race condition. Two sessions could check if the family id exists, both find that it does not exist, and then both proceed with their INSERT. If you lock the table, then one session will acquire the lock and do its work, while the other session must wait for the lock, and by the time it acquires the lock, its check will find that the family id has been inserted by the first session.
This method is usually considered bad for concurrency, which can limit your throughput if you have many requests. But if you have infrequent requests, the impact to throughput will be minimal.

This is a workaround for a design problem, but I figured I might as well post it. You can generate a query such as the following:
INSERT INTO `new_table` (`family_id`, `name`)
SELECT * FROM (
SELECT 1, 'name1'
UNION ALL
SELECT 1, 'name2'
) x
LEFT JOIN `new_table` n ON n.family_id = 1
WHERE n.family_id IS NULL
Then check the number of rows affected to determine if it was successful or not.

Related

MySQL auto assign foreign key ID

I have a main table called results. E.g.
CREATE TABLE results (
r_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
r_date DATE NOT NULL,
system_id INT NOT NULL,
FOREIGN KEY (system_id) REFERENCES systems(s_id) ON UPDATE CASCADE ON DELETE CASCADE
);
The systems table as:
CREATE TABLE systems (
s_id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
system_name VARCHAR(50) NOT NULL UNIQUE
);
I'm writing a program in Python with MySQL connector. Is there a way to add data to the systems table and then auto assign the generated s_id to the results table?
I know I could INSERT into systems, then do another call to that table to see what the ID is for the s_name, to add to the results table but I thought there might be quirk in SQL that I'm not aware of to make life easier with less calls to the DB?
You could do what you describe in a trigger like this:
CREATE TRIGGER t AFTER INSERT ON systems
FOR EACH ROW
INSERT INTO results SET r_date = NOW(), system_id = NEW.s_id;
This is possible only because the columns of your results table are easy to fill in from the data the trigger has access to. The auto-increment fills itself in, and no additional columns need to be filled in. If you had more columns in the results table, this would be harder.
You should read more about triggers:
https://dev.mysql.com/doc/refman/8.0/en/create-trigger.html
https://dev.mysql.com/doc/refman/8.0/en/triggers.html

Change the ids in my SQL Server table so they would be a sequence

I have a table with a unique auto-incremental primary key. Some entries have been deleted from the table, so there are gaps in the ids, it is not a sequence.
I need a query that will make it a sequence. the table is not connected with any other table, so I can temporarily remove the pk and auto-increment from the id column (until the ids will be a sequence).
I use SQL server
If possible, I want to run the query starting from specific id
You cannot update identity column values, nor you can remove the identity from the column, update the values and set it back. You must create a new table with the same schema, copy the data from the old table, but for ID generate a new value, drop the old table and rename the new one to keep the same name.
use [tempdb]
go
if OBJECT_ID('TestTable') is not null
drop table TestTable
go
create table TestTable (
ID int not null primary key clustered identity(1,1)
, Name varchar(50)
)
insert into TestTable(Name) values
('Row #1'),('Row #2'),('Row #3'),
('Row #4'),('Row #5'),('Row #6'),
('Row #7'),('Row #8'),('Row #9')
delete from TestTable where ID in (3, 4, 8) -- Make some gaps
create table TestTable_NEW (
ID int not null primary key clustered identity(1,1)
, Name varchar(50)
)
insert into TestTable_NEW(Name)
select Name
from TestTable
order by ID -- Preserve the rows order
drop table TestTable
exec sp_rename N'TestTable_NEW', N'TestTable'
However, I will not recommend doing this at all. Identity values are supposed to be immutable. They should never change. If you have problems with gaps, then do not allow deletion of existing records. Also, identity do not guarantee that there will be no gaps in the sequence. Only that the values will be unique and increasing. So you should definitely reconsider your database and/or application design, because it is flawed.

Does SELECT keep consistency during concurrent UPDATE or INSERT?

I'm using MySQL 5.6 with innodb. Let's say we have the following two tables:
create table order_head (
id int not null,
version int not null,
order_detail_count int not null,
primary key (id)
);
create table order_detail (
id int not null,
product_id int not null,
qty int not null,
order_head_id int not null,
primary key (id),
foreign key (order_head_id)
references order_head (id)
);
Consider a situation that many concurrent INSERT and UPDATE for both two tables are executing all the time. The application which runs those transactions is well-designed with optimistic locking so concurrent executions don't make any inconsistent data.
Under that situation, I have a concern about issuing the following query:
SELECT
*
FROM
order_head h
JOIN
order_detail d ON (h.id = d.order_head_id);
Does this query always ensure that it will return consistent results? in other words, does this query never mix data of multiple distinct transactions? For example, I don't expect inconsistent results such as the count of records is 4 while order_head.order_detail_count is 3.
I think I don't have good understanding of transactions, so any pointers to good references (e.g. books about transactions) would be also greatly appreciated.
That is a basic principle on any RDBMS.
The ACID Rules (https://en.wikipedia.org/wiki/ACID) that any RDBMS must acomplish, in this case the ISOLATION where each Query to the database should not be interfered by another query that is taking place at the same time.

Inserting into two MySQL related tables with surogate primary keys

In my database, all Primary Keys are surogate. There are some Unique keys, but not always, so the most safe way to access specific row is Primary Key. Many of them use AUTO_INCREMENT. Do I have to lock access to database when inserting into two related table? For example.
create table foo
(
foo_id numeric not null auto_increment,
sth varchar,
PRIMARY KEY(foo_id)
)
create table bar
(
bar_id numeric not null auto_increment,
foo_id numeric not null,
PRIMARY KEY(bar_id),
FOREIGN KEY (foo_id) REFERENCES foo(foo_id)
)
First I insert sth to foo, and then I need foo_id value to insert related stuff into bar. This value I can get from INFORMATION_SCHEMA.TABLES. But what if somebody will add new row into foo before I get the auto_increment value? If all these steps are in stored procedure is there implicitly started transactions which locks all needed resources for one procedure call? Or maybe I have to use explicitly START TRANSACTION. What if I dont use procedure - just sequence of inserts and selects?
Instead of looking in INFORMATION_SCHEMA.TABLE, I would suggest that you use LAST_INSERT_ID.
From the MySQL documentation: The ID that was generated is maintained in the server on a per-connection basis. This means that the value returned by the function to a given client is the first AUTO_INCREMENT value generated for most recent statement affecting an AUTO_INCREMENT column by that client.
This imply that an insert done at the same time on a different connection will not change the value that is returned on your current connection.
Run queries in that sequence:
INSERT INTO foo (sth) VALUES ('TEST');
Than:
INSERT INTO bar (foo_id) VALUES (SELECT LAST_INSERT_ID());

sample sequence table in mysql

I have decided to use mysql sequence table, since I am using spring jdbc batch insert (can't get primary key with this feature), where I will be pass generated key while inserting each row, I have googled long time now, didnt get proper way of creating sequence table.
I have created a sequence table
create table table_sequence (value int not null) ENGINE = MYISAM;
but I feel it seems to be very basic, since I need to have max value, and cache limit for each instance.
I have many tables, do I need to have one sequence table for each table?
I have very less idea about db sequence, so suggestion are helpful to me. thanks
this may help you:
http://dev.mysql.com/doc/refman/5.0/en/example-auto-increment.html
CREATE TABLE animals (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM;
INSERT INTO animals (name) VALUES
('dog'),('cat'),('penguin'),
('lax'),('whale'),('ostrich');
SELECT * FROM animals;