cateof
cateof

Reputation: 6758

Adding constraints in the MySQL database to guarantee uniqueness

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:

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

enter image description here

(from Comment) I cannot create a second table. Eveything should be kept in one table.

Upvotes: 0

Views: 241

Answers (4)

Rick James
Rick James

Reputation: 142208

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?

Upvotes: 3

reaanb
reaanb

Reputation: 10066

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.

Upvotes: 0

Bill Karwin
Bill Karwin

Reputation: 562240

If you can't create a second table to model the constraint properly, then you will have to resort to serializing inserts:

  1. LOCK TABLES new_table WRITE;
  2. Use a SELECT to check if the family id exists in the table.
  3. If the family id is not present, INSERT your new data.
  4. 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.

Upvotes: 2

johannespartin
johannespartin

Reputation: 501

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.

Upvotes: 6

Related Questions