Unique index with one nullable key and foreign key - mysql

We have the following table and we ran into an issue that our unique key is not working (as expected).
CREATE TABLE `documentation_photos` (
`commissionID` smallint(5) unsigned NOT NULL,
`periodID` smallint(5) unsigned NOT NULL,
`carreirID` int(11) NOT NULL,
`groupID` smallint(5) unsigned DEFAULT NULL,
`order` tinyint(4) NOT NULL,
`file` varchar(200) NOT NULL,
UNIQUE KEY `idZak_UNIQUE` (`commissionID`,`periodID`,`carreirID`,`groupID`,`order`),
KEY `ncis & obdobi & idZak` (`commissionID`,`periodID,`carreirID``),
KEY `fotodoc_skupina` (`idSkup`),
CONSTRAINT `fotodoc_` FOREIGN KEY (`groupID`) REFERENCES `groups` (`groupID`),
CONSTRAINT `fotodoc_zaknos` FOREIGN KEY (`commissionID`, `periodID`, `carreirID`) REFERENCES `carrier_of_commission` (`commissionID`, `periodID`, `carreirID`) ON DELETE CASCADE
);
I saw the issue, that describes that NULL is considered unique every time it occurs, but it causes a lot of problems for us. The only one I found was for Postgres...
|--------------|----------|-----------|---------|-------|---------|
| commissionID | periodID | carrierID | groupID | order | file |
|--------------|----------|-----------|---------|-------|---------|
| 1140 | 117 | 2235 | null | 1 | photo-1 |
|--------------|----------|-----------|---------|-------|---------|
| 1140 | 117 | 2235 | null | 1 | photo-1 | -- This is duplicate
|--------------|----------|-----------|---------|-------|---------|
| 1140 | 117 | 2235 | null | 2 | photo-2 |
|--------------|----------|-----------|---------|-------|---------|
| 1140 | 117 | 2235 | null | 1 | photo-1 | -- This is duplicate
|--------------|----------|-----------|---------|-------|---------|
I want to treat null as a single value.
We would like to have working unique/primary key and keep the foreign key if possible.
Is there some known solution for this in MySQL?

Each NULL is unique (two NULLs are not equal) - the row which contains NULL in index expression is not checked for uniqueness, the row which contains NULL in FK expressions is not checked for reference integrity.
Is there some known solution for this in MySQL?
Use generated column which replaces NULL with some definite but logically impossible literal value, use this column in index/FK expression instead of nullale column.

Related

Optimize MySQL query with ORDER BY and INNER JOIN (select where user following)

I need to optimize the following query:
SELECT a.*
FROM Activity AS a
JOIN users_following AS f1
ON f1.userId = a.originatorId
AND f1.followerId = 1
ORDER
BY a.time DESC
LIMIT 10
The idea is to get all activity originated by users some user (in this case, user 1) is following, sorted by time. This query as written is very slow (~5s), though it's very quick if either a) the join is omitted or b) the order by clause is omitted.
Things I've tried:
WHERE ... IN as opposed to INNER JOIN
Here are the CREATE TABLE and EXPLAIN definitions.
CREATE TABLE `Activity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`time` int(11) NOT NULL,
`userId` int(11) NOT NULL,
`voteId` int(11) DEFAULT NULL,
`commentId` int(11) DEFAULT NULL,
`achievementId` int(11) DEFAULT NULL,
`challengeId` int(11) DEFAULT NULL,
`followerId` int(11) DEFAULT NULL,
`acknowledged` int(11) NOT NULL DEFAULT '0',
`type` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`isPrivate` int(11) NOT NULL DEFAULT '0',
`portalId` int(11) DEFAULT NULL,
`postId` int(11) DEFAULT NULL,
`portalMemberId` int(11) DEFAULT NULL,
`originatorId` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_55026B0C1CC880D8` (`portalMemberId`),
KEY `IDX_55026B0C1D79C36A` (`challengeId`),
KEY `IDX_55026B0CE7A069D0` (`achievementId`),
KEY `IDX_55026B0CB6FEC0EE` (`voteId`),
KEY `IDX_55026B0C6690C3F5` (`commentId`),
KEY `IDX_55026B0C64B64DCC` (`userId`),
KEY `IDX_55026B0CF542AA03` (`followerId`),
KEY `IDX_55026B0C57076B1F` (`portalId`),
KEY `IDX_55026B0CE094D20D` (`postId`),
KEY `IDX_55026B0C162E014D` (`originatorId`),
KEY `activity_time_idx` (`time`),
KEY `activity_filter_idx` (`type`,`originatorId`,`userId`,`isPrivate`),
KEY `acknowledged_idx` (`acknowledged`),
KEY `idx1` (`time`,`originatorId`),
KEY `idx2` (`originatorId`,`userId`,`postId`,`challengeId`,`commentId`,`time`),
CONSTRAINT `FK_55026B0C162E014D` FOREIGN KEY (`originatorId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_55026B0C1CC880D8` FOREIGN KEY (`portalMemberId`) REFERENCES `PortalMember` (`id`),
CONSTRAINT `FK_55026B0C1D79C36A` FOREIGN KEY (`challengeId`) REFERENCES `Challenge` (`id`),
CONSTRAINT `FK_55026B0C57076B1F` FOREIGN KEY (`portalId`) REFERENCES `Portal` (`id`),
CONSTRAINT `FK_55026B0C64B64DCC` FOREIGN KEY (`userId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_55026B0C6690C3F5` FOREIGN KEY (`commentId`) REFERENCES `Comment` (`id`),
CONSTRAINT `FK_55026B0CB6FEC0EE` FOREIGN KEY (`voteId`) REFERENCES `Vote` (`id`),
CONSTRAINT `FK_55026B0CE094D20D` FOREIGN KEY (`postId`) REFERENCES `Post` (`id`),
CONSTRAINT `FK_55026B0CE7A069D0` FOREIGN KEY (`achievementId`) REFERENCES `UserAchievement` (`id`),
CONSTRAINT `FK_55026B0CF542AA03` FOREIGN KEY (`followerId`) REFERENCES `ProseUser` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4097200 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ;
CREATE TABLE `users_following` (
`userId` int(11) NOT NULL,
`followerId` int(11) NOT NULL,
PRIMARY KEY (`userId`,`followerId`),
KEY `IDX_17C2F70264B64DCC` (`userId`),
KEY `IDX_17C2F702F542AA03` (`followerId`),
KEY `idx1` (`userId`,`followerId`),
KEY `idx2` (`followerId`,`userId`),
CONSTRAINT `FK_17C2F70264B64DCC` FOREIGN KEY (`userId`) REFERENCES `ProseUser` (`id`),
CONSTRAINT `FK_17C2F702F542AA03` FOREIGN KEY (`followerId`) REFERENCES `ProseUser` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
EXPLAIN
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
| 1 | SIMPLE | f1 | NULL | ref | PRIMARY,IDX_17C2F70264B64DCC,IDX_17C2F702F542AA03,idx1,idx2 | IDX_17C2F702F542AA03 | 4 | const | 145 | 100.00 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | a | NULL | ref | IDX_55026B0C162E014D,idx2 | IDX_55026B0C162E014D | 5 | prose_2_24_2021.f1.userId | 38 | 100.00 | NULL |
+----+-------------+-------+------------+------+-------------------------------------------------------------+----------------------+---------+---------------------------+------+----------+----------------------------------------------+
Let's make an example: user #1 follows users #2 and #3. Here are activities ordered by users, then time:
+------+-------+
| user | time |
+------+-------+
| 1 | 10:00 |
| 1 | 11:00 |
| 2 | 10:00 |
| 2 | 12:00 |
| 3 | 09:00 |
| 3 | 13:00 |
| 4 | 10:00 |
+------+-------+
We can quickly find the followed users' activities, but get the times unordered:
+------+-------+
| user | time |
+------+-------+
| 2 | 10:00 |
| 2 | 12:00 |
| 3 | 09:00 |
| 3 | 13:00 |
+------+-------+
This means that we must sort them in order to get the top n sorted. And if these were not four but thousands or millions of activities that would take awfully long.
If on the other hand the data were available ordered by time descending, then user:
+------+-------+
| user | time |
+------+-------+
| 3 | 13:00 |
| 2 | 12:00 |
| 1 | 11:00 |
| 1 | 10:00 |
| 2 | 10:00 |
| 4 | 10:00 |
| 3 | 09:00 |
+------+-------+
We would have to read the whole data sequentially until we found the n top activities. No further ordering necessary. If we are lucky the first n rows are matches and that's it. If we are unlucky we read the whole table (or index for that matter).
So, there is no guarantee to get this quick. The first approach gets the data quick, but sorting can take long. The second approach needs no sorting, but reading can take long.
I like the second approach a tad better, but it all depends on the data of course. Are there many users? Is the majority followed by user #1 or only few? Are there many activities? Many by few users or few activities per user? ... Anyway, I'd provide this index:
create index idx1 on activity (time desc, originatorid);
As an index is just an offer to the DBMS we might just as well provide the other index for the case the DBMS wants to follow the other route:
create index idx2 on activity (originatorid, time desc);
And this is how I'd probably write the query:
SELECT a.*
FROM activity AS a
WHERE EXISTS
(
SELECT NULL
FROM users_following AS f1
WHERE f1.userId = a.originatorId
AND f1.followerId = 1
)
ORDER BY a.time DESC
LIMIT 10;

Index not being used when column is part of composite key

I have two huge tables from which i have select huge amount of data.
Tables store Purchase Order Details and product information.
PURCHASE_ORDER_DETAILS.
CREATE TABLE `PURCHASE_ORDER_DETAILS` (
`PURCHASE_ORDER_NUMBER_PF` INT(20) NOT NULL,
`PRODUCT_CODE_PF` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`ORDER_QUANTITY` INT(8) DEFAULT NULL,
`UNIT_PRICE` DECIMAL(12,2) DEFAULT NULL,
`ORDER_FULLFILLMENT_DUE_DATE` DATETIME DEFAULT NULL,
`DELIVERY_ADDRESS` VARCHAR(64) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`DELIVERY_CITY` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`DELIVERY_ZIP` BIGINT(10) DEFAULT NULL,
`other columns`
PRIMARY KEY (`PURCHASE_ORDER_NUMBER_PF`,`PRODUCT_CODE_PF`),
KEY `RMAPWBTX_PUCH_ORDE_DLST_INDX` (`DELIVERY_STATE_ID_FK`),
KEY `RMAPWBTX_PUCH_ORDE_DLTY_INDX` (`DELIVERY_TYPE_FK`),
KEY `RMAPWBTX_PUCH_ORDE_TACO_INDX` (`TAX_CODE_FK`),
KEY `RMAPWBMS_PUOR_DETL_PDCO_FK` (`PRODUCT_CODE_PF`),
KEY `RMAPWBTX_PUOR_DETL_TACO_FK` (`TAX_CODE_FK`),
KEY `CREATED_DATE_INDX` (`CREATED_DATE`),
KEY `MODIFIED_DATE_INDX` (`MODIFIED_DATE`),
CONSTRAINT `RMAPWBMS_PUOR_DETL_PDCO_FK` FOREIGN KEY (`PRODUCT_CODE_PF`)
REFERENCES `PRODUCT` (`PRODUCT_CODE_PK`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `RMAPWBMS_PUOR_DETL_PONU_FK` FOREIGN KEY
(`PURCHASE_ORDER_NUMBER_PF`) REFERENCES `PURCHASE_ORDER`
(`PURCHASE_ORDER_NUMBER_PK`),
CONSTRAINT `RMAPWBTX_PO_DETL_DSID_FK` FOREIGN KEY
(`DELIVERY_STATE_ID_FK`) REFERENCES `STATE` (`STATE_ID_PK`),
CONSTRAINT `RMAPWBTX_PUOR_DETL_TACO_FK` FOREIGN KEY (`TAX_CODE_FK`)
REFERENCES `TAX` (`TAX_CODE_PK`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=INNODB DEFAULT CHARSET=latin1;
PRODUCT
CREATE TABLE `PRODUCT` (
`PRODUCT_CODE_PK` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
`PRODUCT_DESC` VARCHAR(256) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`FEE_BILL_CODE` VARCHAR(32) CHARACTER SET latin1 COLLATE latin1_bin DEFAULT NULL,
`other columns`
PRIMARY KEY (`PRODUCT_CODE_PK`),
KEY `CREATED_DATE_INDX` (`CREATED_DATE`),
KEY `MODIFIED_DATE_INDX` (`MODIFIED_DATE`),
KEY `PRODUCT_EXCO_FK` (`EXPENSE_CODE_ID_FK`),
KEY `FK_PRODUCT_ENTITY_TYPE` (`ENTITY_TYPE_CODE_FK`),
CONSTRAINT `FK_PRODUCT_ENTITY_TYPE` FOREIGN KEY (`ENTITY_TYPE_CODE_FK`) REFERENCES `ENTITY_TYPE` (`ENTITY_TYPE_CODE_PK`)
) ENGINE=INNODB DEFAULT CHARSET=latin1
Below query is taking ~10min to get ~1M records.
EXPLAIN SELECT * FROM
PURCHASE_ORDER_DETAILS POD
JOIN PRODUCT PRD ON POD.PRODUCT_CODE_PF=PRD.PRODUCT_CODE_PK;
+----+-------------+-------+------+----------------------------+-----------------
-----------+---------+-----------------------------------------------+-------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+----------------------------+----------------------------+---------+-----------------------------------------------+-------+-------+
| 1 | SIMPLE | PRD | ALL | PRIMARY | NULL | NULL | NULL | 14283 | NULL |
| 1 | SIMPLE | POD | ref | RMAPWBMS_PUOR_DETL_PDCO_FK | RMAPWBMS_PUOR_DETL_PDCO_FK | 34 | REALREMIT_PROD_ALTISOURCE.PRD.PRODUCT_CODE_PK | 40 | NULL |
+----+-------------+-------+------+----------------------------+----------------------------+---------+-----------------------------------------------+-------+---
Edit1:
Above query was an example, below is the actual query where i am trying to fetch 1M records (main table POD has 22M records).
SELECT `some columns`
FROM `REALREMIT_PPIPFC_MIG`.MIGR_ORDER_DENORM MPO
INNER JOIN PURCHASE_ORDER_DETAILS POD
ON MPO.PURCHASE_ORDER_NUMBER_PK=POD.PURCHASE_ORDER_NUMBER_PF
INNER JOIN PRODUCT PRD
ON POD.PRODUCT_CODE_PF=PRD.PRODUCT_CODE_PK
INNER JOIN EXPENSE_CODE EXP
ON PRD.EXPENSE_CODE_ID_FK=EXP.EXPENSE_CODE_ID_PK
WHERE MPO.BATCH_ID=1;
Explain Output for the above query
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
| 1 | SIMPLE | PRD | ALL | PRIMARY,PRODUCT_EXCO_FK | NULL | NULL | NULL | 14283 | NULL |
| 1 | SIMPLE | EXP | eq_ref | PRIMARY | PRIMARY | 4 | REALREMIT_PROD_ALTISOURCE.PRD.EXPENSE_CODE_ID_FK | 1 | NULL |
| 1 | SIMPLE | POD | ref | PRIMARY,RMAPWBMS_PUOR_DETL_PDCO_FK | RMAPWBMS_PUOR_DETL_PDCO_FK | 34 | REALREMIT_PROD_ALTISOURCE.PRD.PRODUCT_CODE_PK | 40 | NULL |
| 1 | SIMPLE | MPO | ref | MIGR_PO_NBR_INDX,MIGR_BATCH_ID_INDX | MIGR_PO_NBR_INDX | 4 | REALREMIT_PROD_ALTISOURCE.POD.PURCHASE_ORDER_NUMBER_PF | 1 | Using where |
+----+-------------+-------+--------+-------------------------------------+----------------------------+---------+--------------------------------------------------------+-------+-------------+
4 rows in set (0.20 sec)
Both database have same charset
Columns used in join have the same collate
Both tables have same charset
I have created new table in which primary key is concatenated column - PURHCASE_ORDER_NUMBER_PF and PRODUCT_CODE_PF and then i have added a new index on PRODUCT_CODE_PF
Will index be used in this case and/or is this the best way to make use of index in join.
Thanks
These may help:
MPO: INDEX(BATCH_ID, PURCHASE_ORDER_NUMBER_PK) -- in this order
EXP: INDEX(EXPENSE_CODE_ID_PK) -- unless it is the PRIMARY KEY
But, without knowing what is in "some columns", I can't predict how much they will help. And it would help to have SHOW CREATE TABLE for MPO and EXP.
How much RAM do you have? What is the value of innodb_buffer_pool_size? I ask because you may be thrashing.

mysql insert from two tables into one using inner join and max()

I checked the suggested posts but couldn't find an answer. I am making a CRUD application with a many to many relationship and three MySQL tables. One table is called "pm":
CREATE TABLE `pm` (
`pm_id` int(11) NOT NULL AUTO_INCREMENT,
`pm_name` varchar(45) NOT NULL,
`pm_address` varchar(45) DEFAULT NULL,
`valid_through` int(11) NOT NULL,
PRIMARY KEY (`pm_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8
+-------+---------+------------------------+---------------+
| pm_id | pm_name | pm_address | valid_through |
+-------+---------+------------------------+---------------+
| 1 | Alpha | http://www.alpha.com | 190303 |
| 2 | Bravo | http://www.bravo.com | 200506 |
| 3 | Charlie | http://www.charlie.com | 190708 |
| 4 | Delta | http://www.delta.com | 210509 |
| 5 | Echo | http://www.echo.com | 230416 |
| 6 | Foxtrot | http://www.foxtrot.com | 181011 |
+-------+---------+------------------------+---------------+
Another is called "searchwords":
CREATE TABLE `searchwords` (
`searchword_id` int(11) NOT NULL AUTO_INCREMENT,
`searchword` varchar(45) NOT NULL,
PRIMARY KEY (`searchword_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
+---------------+------------+
| searchword_id | searchword |
+---------------+------------+
| 1 | apples |
| 2 | oranges |
| 3 | pears |
| 4 | bananas |
| 5 | grapes |
+---------------+------------+
The third is join_table:
CREATE TABLE `join_table` (
`id_join_table` int(11) NOT NULL AUTO_INCREMENT,
`pm_id` int(11) DEFAULT NULL,
`searchword_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id_join_table`),
KEY `FK1_idx` (`pm_id`),
KEY `FK2_idx` (`searchword_id`),
CONSTRAINT `FK1` FOREIGN KEY (`pm_id`) REFERENCES `pm` (`pm_id`) ON DELETE
NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK2` FOREIGN KEY (`searchword_id`) REFERENCES `searchwords`
(`searchword_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8
+---------------+-------+---------------+
| id_join_table | pm_id | searchword_id |
+---------------+-------+---------------+
| 8 | 6 | NULL |
| 9 | 6 | NULL |
| 10 | 6 | NULL |
+---------------+-------+---------------+
Below is the SQL I have now. With for instance "oranges" at the end I want to get - into one new row of "join_table" - the max pm_id from "pm" into the pm_id column and the searchword_id that corresponds to oranges from "searchwords" into the searchword_id column.
INSERT INTO join_table (pm_id, searchword_id)
SELECT (SELECT MAX(pm_id) FROM pm), (SELECT DISTINCT
searchwords.searchword_id FROM searchwords
INNER JOIN join_table ON
searchwords.searchword_id = join_table.searchword_id
WHERE searchword = "oranges");
This SQL has magically worked sometimes but usually not (how is that kind of inconsistency even possible?). The pm_id value always gets in there so I tend to get a row with the correct value as pm_id and searchword as NULL as seen above.
Help would be much appreciated!!
Actually I just solved it, using:
INSERT INTO join_table (pm_id, searchword_id)
SELECT MAX(pm_id), searchword_id
FROM pm, searchwords
WHERE searchword="bananas"
Inner joins are so 2017 anyway, right?
But now I'm having problems when trying to delete a post and got an error:
Fatal error: Uncaught PDOException: SQLSTATE[23000]: Integrity constraint
violation: 1451 Cannot delete or update a parent row: a foreign key
constraint fails (`database`.`join_table`, CONSTRAINT `FK1`
FOREIGN KEY (`pm_id`) REFERENCES `pm` (`pm_id`)
Sigh

Adding Foreign Key to Foreign Key Constrained Table

I have this tables:
quotes
+---------------------+-----------------------+------+-----+---------+---+
| Field | Type | Null | Key | Default
+---------------------+-----------------------+------+-----+---------+---+
| id | int(11) unsigned | NO | PRI | NULL
... (More Columns)
+---------------------+-----------------------+------+-----+-------------+
quotesPackagesInfo
+---------------+---------------------+------+-----+---------+------------+
| Field | Type | Null | Key | Default
+---------------+---------------------+------+-----+---------+------------+
| id | int(11) unsigned | NO | PRI | NULL
| quoteId | int(11) unsigned | NO | | NULL
... (More Columns)
+---------------+---------------------+------+-----+---------+------------+
quotesFlightsInfo
+---------------------------+---------------------+------+-----+----------+
| Field | Type | Null | Key | Default
+---------------------------+---------------------+------+-----+----------+
| id | int(11) unsigned | NO | PRI | NULL
| quoteId | int(11) unsigned | NO | MUL | NULL
| packageId | int(11) unsigned | YES | MUL | NULL ... (More Columns)
+---------------------------+---------------------+------+-----+----------+
So basically there're quotes which are the main key, after there're packages within a quote, and a package can contain flights, so i need that when a package is removed, all flights related get deleted, so i added a foreign key to flights as follows:
ALTER TABLE quotesFlightsInfo
ADD CONSTRAINT fk_quotesFlightsInfo_packageId
FOREIGN KEY (packageId) REFERENCES quotesPackagesInfo(id)
ON DELETE CASCADE
now im trying to add a foreign key to packages so when a quote gets deleted also the package deletes and so the flights but isnt working :(, here the query:
ALTER TABLE quotesPackagesInfo
ADD CONSTRAINT fk_quotesPackagesInfo_quoteId
FOREIGN KEY (quoteId) REFERENCES quotes(id)
ON DELETE CASCADE
Error: Cannot add or update a child row: a foreign key constraint fails (??????????.#sql-312_2, CONSTRAINT fk_quotesPackagesInfo_quoteId FOREIGN KEY (quoteId) REFERENCES quotes (id) ON DELETE CASCADE)
i deleted the quotesPackagesInfo table and created it again adding the foreign key from start and it worked, but still dont know whats was wrong before

Create table of unique foreign keys

I am using MySQL and I have two tables of the form :
mysql> describe ing_categories;
+-------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+----------------+
| ID_Category | int(10) unsigned | NO | PRI | NULL | auto_increment |
| category | varchar(64) | NO | UNI | NULL | |
+-------------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> describe ing_titles;
+----------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+------------------+------+-----+---------+----------------+
| ID_Title | int(10) unsigned | NO | PRI | NULL | auto_increment |
| title | varchar(128) | NO | UNI | NULL | |
+----------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
I have a third table containing foreign key records, the foreign keys are to the above tables:
mysql> describe ing_title_categories;
+-------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+------------------+------+-----+---------+----------------+
| ID_Title_Category | int(10) unsigned | NO | PRI | NULL | auto_increment |
| ID_Title | int(10) unsigned | NO | MUL | NULL | |
| ID_Category | int(10) unsigned | NO | MUL | NULL | |
+-------------------+------------------+------+-----+---------+----------------+
How do I create the ing_titles_categories table so that the pair of foreign keys is unique?
Here is my SQL statement:
CREATE TABLE ing_title_categories
(
ID_Title_Category INTEGER UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
ID_Title INTEGER UNSIGNED NOT NULL,
ID_Category INTEGER UNSIGNED NOT NULL,
FOREIGN KEY fk_title(ID_Title)
REFERENCES ing_titles(ID_Title)
ON UPDATE CASCADE
ON DELETE RESTRICT,
FOREIGN KEY fk_category(ID_Category)
REFERENCES ing_categories(ID_Category)
ON UPDATE CASCADE
ON DELETE RESTRICT
) ENGINE=InnoDB;
I've searched other questions and they involve making an index for the foreign keys; but I don't want to index by the foreign keys, I want the constraint to be unique for the pair of foreign keys.
Tools:
MySQL Server version: 5.6.26
Notes:
I am not using PHP, but C++ via MySQL C++ Connector, so please no PHP
examples.
You add a unique or primary key constraint. Here is an example:
CREATE TABLE ing_title_categories
(
ID_Title_Category INTEGER UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
ID_Title INTEGER UNSIGNED NOT NULL,
ID_Category INTEGER UNSIGNED NOT NULL,
FOREIGN KEY fk_title(ID_Title)
REFERENCES ing_titles(ID_Title)
ON UPDATE CASCADE
ON DELETE RESTRICT,
FOREIGN KEY fk_category(ID_Category)
REFERENCES ing_categories(ID_Category)
ON UPDATE CASCADE
ON DELETE RESTRICT,
UNIQUE (ID_Title, ID_Category)
) ENGINE=InnoDB;
The implementation of the unique constraint does create an index. However, that index is needed to enforce the constraint. It will also be used for queries, where appropriate.
Note that this is equivalent to:
create unique index idx_ing_title_categories_2 on ing_title_categories(ID_Title, ID_Category);
One advantage of putting the constraint in the CREATE TABLE statement is that you can use the CONSTRAINT keyword to give the constraint a meaningful name -- useful when you want to figure out what is happening when the constraint is violated.
You can define UNIQUE KEY constraint on both columns ID_Title and ID_Category as composite unique (UNIQUE(ID_Title , ID_Category)) key which will make sure that combination of the said key columns are always unique.
ALTER TABLE ing_title_categories
ADD CONSTRAINT uniqueconstraint UNIQUE(`ID_Title` , `ID_Category`);