How to enforce different primary keys in 2 different tables? - mysql

I may be missing something simple here but I can't seem to find an answer.
I have two entities that are inherited from another and I want to enforce that the child entities cannot have the same key as each other in the database rather than with queries.
For example I want credit card or paypal to have the same primary key as payment, but I only want that to be in either credit card or paypal, not both. So a payment is a credit card or a paypal but not both.
I though it would be simple constraint, something like:
check (cc.transaction != pp.transaction)
But this doesn't seem to work in mysql workbench. Any ideas?
I have found out that in fact checks don't work at all in mySQL, and so I need to use a trigger, but there it no way to just cancel the insert so it has to throw an error to exit instead. I am using mySQL Workbench which supports triggers but it will not accept any I try, eg:
CREATE DEFINER = CURRENT_USER TRIGGER `ddi`.`tcredit_card_BEFORE_INSERT`
BEFORE INSERT ON `tcredit_card` FOR EACH ROW
BEGIN
IF EXISTS (SELECT Paypal_ID FROM tPaypal WHERE Paypal_ID = NEW.Card_ID) THEN
SIGNAL SQLSTATE '02000' SET MESSAGE_TEXT = 'Cannot have multiple payments.';
END IF;
END;
It always says there's and error no matter what I do, including attempting to change the delimiter which also come up as an error. Now short of smashing the spinning jenny and using paper records and a horse and cart surely there must be a way for this to work? (without changing the db schema)

You can have a super-type and sub-types arrangement in the database and it's really easy to keep them separate. Just declare the subtype as part of the super key then enforce type values in the sub-tables.
Whenever possible, let the constraints and checks built into the underlying system enforce whatever design you implement.
create table Super(
ID int not null auto_increment,
SubType char( 1 ) not null check( SubType in( 'A', 'B' ) ),
...
constraint PK_Super primary key( ID, SubType )
);
create table subA(
SubAID int not null,
SubType char( 1 ) not null default 'A' check( SubType = 'A' ),
... -- data specific to Type = A
constraint PK_SubA primary key( SubAID, SubType ),
constraint FK_SubA_Super foreign key( SubAID, SubType )
references Super( ID, SubType )
);
create table subB(
SubBID int not null,
SubType char( 1 ) not null default 'B' check( SubType = 'B' ),
... -- data specific to Type = B
constraint PK_SubB primary key( SubBID, SubType ),
constraint FK_SubB_Super foreign key( SubBID, SubType )
references Super( ID, SubType )
);
It may seem redundant to make Super.SubType part of the PK since Super.ID is a surrogate key all by itself, but look at what you get.
The Super table contains the data common to all subtypes (transaction date, type (credit/debit), amount, etc.).
With the proper constraint (you could use another table with defined subtypes) no entry is possible in Super that is not a properly defined subtype.
The subtype value tells you which sub-table contains the type-specific data.
No entry can be made in a sub-table that is not first made in the super table. And, once defined, the subtype cannot be changed -- entries in A can have nothing but 'A' in SubType field. Nor, once a FK connection is made, can an 'A' entry in Super be changed to 'B' or vice versa.

If you want to enforce this logic in the database, you might need another table.
How about this, if I've understood your problem:
payment(paymentID, date, amount)
paymentType(paymentTypeID,
paymentID)
creditCardPaymentType(creditCardPaymentTypeID, paymentTypeID, cc#,
CVV2, expiration)
paypalPaymentType(paypalPaymentTypeID,
paymentTypeID, paypalname, paypalotherinfowhatever)
And then make paymentType(paymentID) unique.
But it could be more performant to enforce the logic in the application.

Related

mysql ignoring check constraint

I have this code and I want to implement it in MySQL but CHECK is not supported. How would I do it?
CREATE TABLE Fare (
type_of_passenger varchar(20),
price FLOAT,
PRIMARY KEY (type_of_passenger),
CHECK (lower(type_of_passenger)='child' OR lower(type_of_passenger)='student' OR lower(type_of_passenger)='senior')
);
Are you sure you need that check? As the primary key is type_of_passenger you will only ever be able to have three rows.. this seems like over-kill
You could just INSERT those 3 rows and move on. If you reference this field in a foreign key, you'll be restricted to the values in the table anyway
In fact as soon as you reference each value with a foreign key that uses ON UPDATE RESTRICT and ON DELETE RESTRICT you won't be able to change them anyway
The only valid concern I can see here is that you want to allow a DB user to change the price but not the type_of_passenger
If you INSERT the correct (or stub) data to start with, you can then control table and column access via permissions
N.B. I would use a surrogate unsigned integer primary here and unique the string description, thus if I do need to change the string I can do it without worry, and without the performance hit of updating all the tables that reference it
It looks like you're really trying to implement an ENUM:
type_of_passenger ENUM('child', 'student', 'senior')
By default MySQL doesn't validate ENUM values, so if you try to store something else it will store an empty string, but if strict SQL mode is enabled it will report an error.
Another alternative is to make this a foreign key to a table where you enter the valid values.
CREATE TABLE passenger_types (
type VARCHAR(20) NOT NULL PRIMARY KEY
);
INSERT INTO passenger_types (type) VALUES
('child'), ('student'), ('senior');
CREATE TABLE Fare (
type_of_passenger varchar(20),
price FLOAT,
PRIMARY KEY (type_of_passenger),
CONSTRAINT FOREIGN KEY (type_of_passenger) REFERENCES passenger_types (type)
);

SQL: Creating a Relational table with 2 different auto_increment

I have 2 tables, each with their own auto incremented IDs, which are of course primary keys.
When I want to create a 3rd table to establish the relation between these 2 tables, I always have an error.
First one is that you can have only 1 automatically-incremented column, the second one occurs when I delete the auto_increment statement from those 2, therefore AQL doesn't allow me to make them foreign keys, because of the type matching failure.
Is there a way that I can create a relational table without losing auto increment features?
Another possible (but not preferred) solution may be there is another primary key in the first table, which is the username of the user, not with an auto_increment statement, of course. Is it inevitable?
Thanks in advance.
1 Concept
You have misunderstood some basic concepts, and the difficulties result from that. We have to address the concepts first, not the problem as you perceive it, and consequently, your problem will disappear.
auto incremented IDs, which are of course primary keys.
No, they are not. That is a common misconception. And problems are guaranteed to ensue.
An ID field cannot be a Primary Key in the English or technical or Relational senses.
Sure, in SQL, you can declare any field to be a PRIMARY KEY, but that doesn't magically transform it into a Primary Key in the English, technical, or Relational senses. You can name a chihuahua "Rottweiller", but that doesn't transform it into a Rottweiller, it remains a chihuahua. Like any language, SQL simply executes the commands that you give it, it does not understand PRIMARY KEY to mean something Relational, it just whacks an unique index on the column (or field).
The problem is, since you have declared the ID to be a PRIMARY KEY, you think of it as a Primary Key, and you may expect that it has some of qualities of a Primary Key. Except for the uniqueness of the ID value, it provides no benefit. It has none of the qualities of a Primary Key, or any sort of Relational Key for that matter. It is not a Key in the English, technical, or Relational senses. By declaring a non-key to be a key, you will only confuse yourself, and you will find out that there is something terribly wrong only when the user complains about duplicates in the table.
2 Relational Model
2.1  Relational tables must have row uniqueness
A PRIMARY KEY on an ID field does not provide row uniqueness. Therefore it is not a Relational table containing rows, and if it isn't that, then it is a file containing records. It doesn't have any of the integrity, or power (at this stage you will be aware of join power only), or speed, that a table in a Relational database has.
Execute this code (MS SQL) and prove it to yourself. Please do not simply read this and understand it, and then proceed to read the rest of this Answer, this code must be executed before reading further. It has curative value.
-- [1] Dumb, broken file
-- Ensures unique RECORDS, allows duplicate ROWS
CREATE TABLE dumb_file (
id INT IDENTITY PRIMARY KEY,
name_first CHAR(30),
name_last CHAR(30)
)
INSERT dumb_file VALUES
( 'Mickey', 'Mouse' ),
( 'Mickey', 'Mouse' ),
( 'Mickey', 'Mouse' )
SELECT *
FROM dumb_file
Notice that you have duplicate rows. Relational tables are required to have unique rows. Further proof that you do not have a relational table, or any of the qualities of one.
Notice that in your report, the only thing that is unique is the ID field, which no user cares about, no user sees, because it is not data, it is some additional nonsense that some very stupid "teacher" told you to put in every file. You have record uniqueness but not row uniqueness.
In terms of the data (the real data minus the extraneous additions), the data name_last and name_first can exist without the ID field. A person has a first name and last name without an ID being stamped on their forehead.
The second thing that you are using that confuses you is the AUTOINCREMENT. If you are implementing a record filing system with no Relational capability, sure, it is helpful, you don't have to code the increment when inserting records. But if you are implementing a Relational Database, it serves no purpose at all, because you will never use it. There are many features in SQL that most people never use.
2.2  Corrective Action
So how do you upgrade, elevate, that dumb_file that is full of duplicate rows to a Relational table, in order to get some of the qualities and benefits of a Relational table ? There are three steps to this.
You need to understand Keys
And since we have progressed from ISAM files of the 1970's, to the Relational Model, you need to understand Relational Keys. That is, if you wish to obtain the benefits (integrity, power, speed) of a Relational Database.
In Codd's Relational Model:
a key is made up from the data
and
the rows in a table must be unique
Your "key" is not made up from the data. It is some additional, non-data parasite, caused by your being infected with the disease of your "teacher". Recognise it as such, and allow yourself the full mental capacity that God gave you (notice that I do not ask you to think in isolated or fragmented or abstract terms, all the elements in a database must be integrated with each other).
Make up a real key from the data, and only from the data. In this case, there is only one possible Key: (name_last, name_first).
Try this code, declare an unique constraint on the data:
-- [2] dumb_file fixed, elevated to table, prevents duplicate rows
-- still dumb
CREATE TABLE dumb_table (
id INT IDENTITY PRIMARY KEY,
name_first CHAR(30),
name_last CHAR(30),
CONSTRAINT UK
UNIQUE ( name_last, name_first )
)
INSERT dumb_table VALUES
( 'Mickey', 'Mouse' ),
( 'Minnie', 'Mouse' )
SELECT *
FROM dumb_table
INSERT dumb_table VALUES
( 'Mickey', 'Mouse' )
Now we have row uniqueness. That is the sequence that happens to most people: they create a file which allows dupes; they have no idea why dupes are appearing in the drop-downs; the user screams; they tweak the file and add an index to prevent dupes; they go to the next bug fix. (They may do so correctly or not, that is a different story.)
The second level. For thinking people who think beyond the fix-its. Since we have now row uniqueness, what in Heaven's name is the purpose of the ID field, why do we even have it ??? Oh, because the chihuahua is named Rotty and we are afraid to touch it.
The declaration that it is a PRIMARY KEY is false, but it remains, causing confusion and false expectations. The only genuine Key there is, is the (name_last, name_fist), and it is a Alternate Key at this point.
Therefore the ID field is totally superfluous; and so is the index that supports it; and so is the stupid AUTOINCREMENT; and so is the false declaration that it is a PRIMARY KEY; and any expectations you may have of it are false.
Therefore remove the superfluous ID field. Try this code:
-- [3] Relational Table
-- Now that we have prevented duplicate data, the id field
-- AND its additional index serves no purpose, it is superfluous,
-- like an udder on a bull. If we remove the field AND the
-- supporting index, we obtain a Relational table.
CREATE TABLE relational_table (
name_first CHAR(30),
name_last CHAR(30),
CONSTRAINT PK
PRIMARY KEY ( name_last, name_first )
)
INSERT relational_table VALUES
( 'Mickey', 'Mouse' ),
( 'Minnie', 'Mouse' )
SELECT *
FROM relational_table
INSERT relational_table VALUES
( 'Mickey', 'Mouse' )
Works just fine, works as intended, without the extraneous fields and indices.
Please remember this, and do it right, every single time.
2.3  False Teachers
In these end times, as advised, we will have many of them. Note well, the "teachers" who propagate ID columns, by virtue of the detailed evidence in this post, simply do not understand the Relational Model or Relational Databases. Especially those who write books about it.
As evidenced, they are stuck in pre-1970 ISAM technology. That is all they understand, and that is all that they can teach. They use an SQL database container, for the ease of Access, recovery, backup, etc, but the content is pure Record Filing System with no Relational Integrity, Power, or speed. AFAIC, it is a serious fraud.
In addition to ID fields, of course, there are several items that are key Relational-or-not concepts, that taken together, cause me to form such a grave conclusion. Those other items are beyond the scope of this post.
One particular pair of idiots is currently mounting an assault on First Normal Form. They belong in the asylum.
3  Solution
Now for the rest of your question.
3.1  Answers
Is there a way that I can create a relational table without losing auto increment features?
That is a self-contradicting sentence. I trust you will understand from my explanation, Relational tables have no need for AUTOINCREMENT "features"; if the file has AUTOINCREMENT, it is not a Relational table.
AUTOINCREMENT or IDENTITY is good for one thing only: if, and only if, you want to create an Excel spreadsheet in the SQL database container, replete with fields named A, B, and C, across the top, and record numbers down the left side. In database terms, that is the result of a SELECT, a flattened view of the data, that is not the source of data, which is organised (Normalised).
Another possible (but not preferred) solution may be there is another primary key in the first table, which is the username of the user, not with an auto increment statement, of course. Is it inevitable?
In technical work, we don't care about preferences, because that is subjective, and it changes all the time. We care about technical correctness, because that is objective, and it does not change.
Yes, it is inevitable. Because it is just a matter of time; number of bugs; number of "can't dos"; number of user screams, until you face the facts, overcome your false declarations, and realise that:
the only way to ensure that user rows are unique, that user_names are unique, is to declare an UNIQUE constraint on it
and get rid of user_id or id in the user file
which promotes user_name to PRIMARY KEY
Yes, because your entire problem with the third table, not coincidentally, is then eliminated.
That third table is an Associative Table. The only Key required (Primary Key) is a composite of the two parent Primary Keys. That ensures uniqueness of the rows, which are identified by their Keys, not by their IDs.
I am warning you about that because the same "teachers" who taught you the error of implementing ID fields, teach the error of implementing ID fields in the Associative Table, where, just as with an ordinary table, it is superfluous, serves no purpose, introduces duplicates, and causes confusion. And it is doubly superfluous because the two keys that provide are already there, staring us in the face.
Since they do not understand the RM, or Relational terms, they call Associative Tables "link" or "map" tables. If they have an ID field, they are in fact, files.
3.2  Lookup Tables
ID fields are particularly Stupid Thing to Do for Lookup or Reference tables. Most of them have recognisable codes, there is no need to enumerate the list of codes in them, because the codes are (should be) unique.
ENUM is just as stupid, but for a different reason: it locks you into an anti-SQL method, a "feature" in that non-compliant "SQL".
Further, having the codes in the child tables as FKs, is a Good Thing: the code is much more meaningful, and it often saves an unnecessary join:
SELECT ...
FROM child_table -- not the lookup table
WHERE gender_code = "M" -- FK in the child, PK in the lookup
instead of:
SELECT ...
FROM child_table
WHERE gender_id = 6 -- meaningless to the maintainer
or worse:
SELECT ...
FROM child_table C -- that you are trying to determine
JOIN lookup_table L
ON C.gender_id = L.gender_id
WHERE L.gender_code = "M" -- meaningful, known
Note that this is something one cannot avoid: you need uniqueness on the lookup code and uniqueness on the description. That is the only method to prevent duplicates in each of the two columns:
CREATE TABLE gender (
gender_code CHAR(2) NOT NULL,
name CHAR(30) NOT NULL
CONSTRAINT PK
PRIMARY KEY ( gender_code )
CONSTRAINT AK
UNIQUE ( name )
)
3.3  Full Example
From the details in your question, I suspect that you have SQL syntax and FK definition issues, so I will give the entire solution you need as an example (since you have not given file definitions):
CREATE TABLE user ( -- Typical Identifying Table
user_name CHAR(16) NOT NULL, -- Short PK
name_first CHAR(30) NOT NULL, -- Alt Key.1
name_last CHAR(30) NOT NULL, -- Alt Key.2
birth_date DATE NOT NULL -- Alt Key.3
CONSTRAINT PK -- unique user_name
PRIMARY KEY ( user_name )
CONSTRAINT AK -- unique person identification
PRIMARY KEY ( name_last, name_first, birth_date )
)
CREATE TABLE sport ( -- Typical Lookup Table
sport_code CHAR(4) NOT NULL, -- PK Short code
name CHAR(30) NOT NULL -- AK
CONSTRAINT PK
PRIMARY KEY ( sport_code )
CONSTRAINT AK
PRIMARY KEY ( name )
)
CREATE TABLE user_sport ( -- Typical Associative Table
user_name CHAR(16) NOT NULL, -- PK.1, FK
sport_code CHAR(4) NOT NULL, -- PK.2, FK
start_date DATE NOT NULL
CONSTRAINT PK
PRIMARY KEY ( user_name, sport_code )
CONSTRAINT user_plays_sport_fk
FOREIGN KEY ( user_name )
REFERENCES user ( user_name )
CONSTRAINT sport_occupies_user_fk
FOREIGN KEY ( sport_code )
REFERENCES sport ( sport_code )
)
There, the PRIMARY KEY declaration is honest, it is a Primary Key; no ID; no AUTOINCREMENT; no extra indices; no duplicate rows; no erroneous expectations; no consequential problems.
3.4  Relational Data Model
Here is the Data Model to go with the definitions.
As a PDF
If you are not used to the Notation, please be advised that every little tick, notch, and mark, the solid vs dashed lines, the square vs round corners, means something very specific. Refer to the IDEF1X Notation.
A picture is worth a thousand words; in this case a standard-complaint picture is worth more than that; a bad one is not worth the paper it is drawn on.
Please check the Verb Phrases carefully, they comprise a set of Predicates. The remainder of the Predicates can be determined directly from the model. If this is not clear, please ask.

Database Structure/Design

I couldn't think of a title for this and so didn't even know where to start researching for myself.
I have to make a database where I have a table for CD/DVDs but the type of entertainment on them requires different attributes in terms of metadata/information for example music CDs have artist, publisher, producer, CDNo. etc. Whereas a piece of software may have similarities but has some that music wont have and likely the same with movies and games. And so I'm not sure how this would work in terms of an E-R diagram, so far I decided on:
CD/DVDs being in the items table or stock table not sure on the name yet.
tbl_items -> item_id,
item_format(DVD or CD, maybe axpand to blu-ray or hd-dvd),
item_entertainment_type(Music, Movie etc.) <--- Maybe in another not sure.
foreign key to a metadata table, this is so that when deliveries for new CD/DVDs are made if the metadata already exists I just enter a new item and so its a one to many between metadata and items (items >-- meta).
The question I think is, is it bad practice to have null able foreign key fields and Just choose which to add a relation to, so musicMeta_id INT NULL, FOREIGN KEY musicMetaID REFERENCES tbl_musicMeta(musicMeta_id)
like that for each type? or somehow merge them, or is there a trick databaes have.
I'm using MySQL with php.
Thanks!
There is no general rule or Best Practice the foreign keys should not be nullable. Many times it makes perfect sense for an entity not to have a relationship with another entity. For example, you may have a table of artists you track but, at the moment, you have no CDs recorded by those artists.
As for having Media (CD, DVD, BluRay) that can be either music/audio or software, you can have a table with the information in common and then two foreign keys, one to each extension table (AudioData and SoftwareData), but one must be NULL. This presents a situation called, among other things, an exclusive arc. This is generally considered to be...problematic.
Think of a superclass and two derived classes in an OO language like Java or C++. One way to represent that in a relational schema is:
create table Media(
ID int not null, -- identity, auto_generated, generated always as identity...
Type char( 1 ) not null,
Format char( 1 ) not null,
... <other common data>,
constraint PK_Media primary key( ID ),
constraint FK_Media_Type foreign key( Type )
references MediaTypes( ID ), -- A-A/V, S-Software, G-Game
constraint FK_Media_Format foreign key( Format )
references MediaFormats( ID ) -- C-CD, D-DVD, B-BluRay, etc.
);
create unique index UQ_Media_ID_Type( ID, Type ) on Media;
create table AVData( -- For music and video
ID int not null,
Type char( 1 ) not null,
... <audio-only data>,
constraint PK_AVData primary key( ID ),
constraint CK_AVData_Type check( Type = 'A',
constraint FK_AVData_Media foreign key( ID, Type )
references Media( ID, Type )
);
create table SWData( -- For software, data
ID int not null,
Type char( 1 ) not null,
... <software-only data>,
constraint PK_SWData primary key( ID ),
constraint CK_SWData_Type check( Type = 'S',
constraint FK_SWData_Media foreign key( ID, Type )
references Media( ID, Type )
);
create table GameData( -- For games
ID int not null,
Type char( 1 ) not null,
... <game-only data>,
constraint PK_GameData primary key( ID ),
constraint CK_GameData_Type check( Type = 'G',
constraint FK_GameData_Media foreign key( ID, Type )
references Media( ID, Type )
);
Now if you are looking for a movie, you search the AVData table, then join with the Media table for the rest of the information and so on with software or games. If you have an ID value but don't know what kind it is, search the Media table and the Type value will tell you which of the three (or more) data tables to join with. The point is that the FK is referring to the generic table, not from it.
Of course, a movie or game or software can be released on more than one media type, so you can have intersection tables between the Media table and the respective data tables. Otoh, those are generally labeled with different SKUs so you may want to also treat them as different items.
The code, as you might expect, can get fairly complicated, though not too bad. Otoh, our design goal is not code simplicity but data integrity. This makes it impossible to mix, for instance, game data with a movie item. And you get rid of having a set of fields where only one must have a value and the others must be null.
My opinion: Get rid of the FOREIGN KEYs; just be sure you have suitable INDEXes.

LINQ to SQL: Composite FK only half nullable

In a multitenant application that uses composite keys
(e.g. SiteId, $(Table)Id)
for everything, I have the following two tables:
Owner (
SiteId bigint NOT NULL,
OwnerId bigint NOT NULL,
ThingId bigint NULL
)
Thing (
SiteId bigint NOT NULL,
ThingId bigint NOT NULL
)
and a foreign key from Owner (SiteId, ThingId) to Thing (SiteId, ThingId).
If I write a method like this:
void RemoveThing(Owner owner)
{
owner.Thing = null;
db.SubmitChanges();
}
I get the exception
System.InvalidOperationException: An attempt was made to remove a relationship between a
Thing and a Owner. However, one of the relationship's foreign keys (Owner.MultiId,
Owner.ThingId) cannot be set to null.
at System.Data.Linq.ChangeTracker.StandardChangeTracker.StandardTrackedObject.SynchDependentData()
at System.Data.Linq.ChangeProcessor.ValidateAll(IEnumerable`1 list)
at System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode)
at System.Data.Linq.DataContext.SubmitChanges()
at proj.Program.RemoveThing() in C:\proj\Program.cs:line 115
To fix this, I could add a second copy of SiteId (perhaps ThingSiteId) to the Owner table. But it's only ever going to be equal to the SiteId, so I'd be spending database size and schema complexity to work around my ORM.
How can I tell LINQ to SQL that it doesn't need to worry about that half of the foreign key relationship, and only set Owner.ThingId=NULL?
Set ThingId = null instead of Thing = null. Don't reuse your DataContext, otherwise you might get a "Foreign key is already set" exception.
LINQ is actively preventing this from being any easier. In ChangeTracker.SynchDependentData(), every column is checked to see if it is part of any associations, and then an exception is thrown if the column was
set to null
the association has ANY non-nullable columns(!)
This is a bug in LINQ-to-SQL, in my opinion.

Ensuring atomic updates on tables with specific logical constraints

I have a table like this (example):
id INT PRIMARY KEY
number BIGINT UNIQUE
type ENUM
user_id INT
number is the primary data for this table, representing a unique number of which only one can exist. These numbers can be of type A or B. A user can claim these numbers, in which case the user_id will be filled in (until then it's NULL).
The specific logical constraints on this are that a number can only be claimed once and a user can only claim one number of type A, but unlimited numbers of type B.
To ensure that a user can only claim one number of type A, a UNIQUE (type, user_id) constraint would be fine. That would prevent the user from claiming an unlimited number of B numbers though.
Currently I'm handling this on the application level like this:
SELECT COUNT(*) FROM `table` WHERE `type` = 'A' AND `user_id` = ?
If the count is not 0, abort, else:
UPDATE `table` SET `user_id` = ? WHERE `type` = 'A' AND `user_id` IS NULL LIMIT 1
But there's still a tiny chance of a race condition in here whereby a user would get two numbers of type A.
How can I formulate a constraint or atomic update that ensures that a user can only claim one number of type A? Would stored procedures, triggers and such help here at all? Is this possible in MySQL without restructuring the schema?
Just use SELECT ... **FOR UPDATE**
SELECT * FROM `table` WHERE `type` = 'A' AND `user_id` = ? FOR UPDATE
Not sure if it works for COUNT(*) but it is easy to check.
If it is ok to add another column - then you can add something like if_type_A and maintain it with trigger: if type is A - then 1, else - null. And apply unique constraint for user_id + if_type_A composite key
Slightly different approach. Use one table to record the numbers and their types, one table to record claimed numbers of type 'a', and one table to record claimed numbers of type 'b'. Type 'a' and type 'b' numbers have different logical constraints; it makes perfectly good sense to store them in different tables.
create table unique_numbers (
n integer primary key,
n_type char(1) default 'b' check (n_type in ('a', 'b')),
unique (n, n_type)
);
create table claimed_type_a_numbers (
n integer primary key,
n_type char(1) not null default 'a' check (n_type = 'a'),
user_id integer not null unique, -- references users, not shown
foreign key (n, n_type) references unique_numbers (n, n_type)
);
create table claimed_type_b_numbers (
n integer primary key,
n_type char(1) not null default 'b' check (n_type = 'b'),
user_id integer not null, -- references users, not shown
foreign key (n, n_type) references unique_numbers (n, n_type)
);
But MySQL doesn't support CHECK constraints. ("The CHECK clause is parsed but ignored by all storage engines.") So you'll probably want to implement these CHECK constraints with MySQL triggers.
If your schema for ENUM allows nulls, you won't have to restructure the schema, but the application will require updates for this solution:
Use null to represent "B"-type numbers. Then you can put a unique constraint on (type, user_id). MySQL will allow multiple (NULL, user_id) entries in an index.