MySQL enforce uniqueness across two columns - mysql

I'm trying to achieve something in MySQL that I have not heard is possible before (maybe it is, researching didn't help me much).
What I'm trying to do is enforce uniqueness in MySQL across two columns. What I mean by this is not setting UNIQUE(column1,column2) but the following two conditions:
If a value exists in column1, it cannot be repeated in column1 (same as setting UNIQUE(column1)).
If a value exists in either column, it cannot exist in the other column.
Hence, for the data set {column1,column2}, if {1,2}, {3,4}, {5,6} are data already present, then neither of the two columns can have any of the above data items for new data,i.e. new data item {x,y} where x=NOT{column1} AND y=NOT{column2} AND x!=y
Is this possible? Please help me out here. Thank you.

This might be an overkill, but you can store column1 and column2 in a separate table.
Let's say your table is
create table items (
id int primary key,
column1 int,
column2 int
);
with data:
id | column1 | column2
---|---------|--------
1 | 1 | 2
1 | 3 | 4
1 | 5 | 6
You can change your schema to
create table items (
id int primary key
);
create table item_columns (
item_id int,
position int,
val int,
primary key (item_id, position),
unique key (val),
foreign key (item_id) references items(id)
);
with data:
item_id | position | val
--------|----------|----
1 | 1 | 1
1 | 2 | 2
2 | 1 | 3
2 | 2 | 4
3 | 1 | 5
3 | 2 | 6
You can simulate the old schema with
select i.id, c1.val as column1, c2.val as column2
from items i
left join item_columns c1
on c1.item_id = i.id
and c1.position = 1
left join item_columns c2
on c2.item_id = i.id
and c2.position = 2
You can use it in a view if you like.
Demo: http://rextester.com/PPBT42478
To guaranty the integrity for the position column, you can make it a foreign key to a positions table, which will only contain the values 1 and 2. You could also use ENUM('1', '2'), but ENUM always allow an empty string as a value.

Related

Only allow column to have true once for each id?

I have this table:
ID | genre_id | is_best_in_genre | movie_name
--------------------------------
1 | 3 | 0 | Hateful Eight
2 | 3 | 0 | Django Unchained
2 | 3 | 1 | Inglorious B
2 | 3 | 0 | Once Upon A Time in Hollywood
is_best_in_genre can only be true (1) once for every genre_id. There can only be 1 best movie in each genre.
How would I make a constraint such as this?
Alternative idea:
The more proper, normalized way to handle this is probably to make a separate 'best_in_genre' table with a unique constraint on genre_id.
This is also easier to update, because you're not required to make sure that everything gets 0'd when selecting a new 'best'.
A better approach might be to "move" the column is_best_in_genre to a separate (joined) table. A simple table with two columns could do the job:
CREATE TABLE best (
idmovie int,
idgenre int,
PRIMARY KEY (idgenre))
This would need to be joined with the original table like:
SELECT m.*, CASE b.idmovie>0 THEN 1 ELSE 0 END best_in_genre
FROM movietable m
LEFT JOIN best b ON idmovie=id AND idgenre=genre_id
The PRIMARY KEY constraint in the table best will make sure that each genre can only appear once.

MySQL - how to optimize large set of conditions

My head is already spinning from this and I need your help.
MY DATABASE
imported CSV file: 22 columns and 11k rows
2 tables with the same data (both created from the CSV)
Added ID as PRIMARY KEY to both
All VARCHAR(60) Some columns are empty strings ' '
DB:
PID | CODE 1 | CODE 2 | CODE 3 | CODE 4 | CODE 5 | CODE X (up to 9) | ID
-------------------------------------------------------------------------
1 | a | b | c | | | | 1
2 | a | | b | d | | | 2
3 | x | | | | | y | 3
DB has 22 columns but I'm only including CODE columns (up to 9)
in which I might be interested in terms of SQL statement.
It'll be only read table - MyISAM engine then?
WHAT I'D LIKE TO DO
select PID = 1 from first table
and retrieve all PIDs from second table
IF
selected PID's column CODE 1
or
selected PID's column CODE 2 (which is b) etc (up to 9).
= any PID's CODE X
So I should get only PID 2.
edit: PID is not a ID, it's just an example code, it could be string: '002451' and I'm looking for other PIDs with the same CODES (e.g PID1 has code = a so it should find PID2 becasue one of its CODE columns contains a)
MY ATTEMPT
SELECT a.* FROM `TABLE1` a WHERE
(
SELECT * FROM `TABLE2` b WHERE b.`PID` = 1
AND
(
( b.`CODE 1` NOT IN ('') AND IN (a.`CODE 1`,a.`CODE 2`, A.`CODE 3`...) ) OR
( b.`CODE 2` NOT IN ('') AND (a.`CODE 1`,a.`CODE 2`, A.`CODE 3`...) ) OR...
I'd end up with large query - over 81 conditions. In terms of performance... well, it doesn't work.
I intuitively know that I should:
use INDEXES (on CODE 1 / CODE 2 / CODE 3 etc.?)
use JOIN ON (but I'm too stupid) - that's why I created 2 tables (let's assume I don't want TEMP. TABLES)
How to write the SQL / design the DB efficently?
The correct data structure is one row per pid and code. The simplest way is:
create table PCodes (
pid int not null,
code varchar(255),
constraint fk_PCodes_pid references p(pid)
);
Then you have the values in a single column and it is much simpler to check for matching codes.
In practice, you should have three tables:
create table Codes (
CodeId int not null auto_increment primary key,
Code varchar(255)
);
create table PCodes (
pid int not null,
codeid int not null,
constraint fk_PCodes_pid references p(pid),
constraint fk_PCodes_codeid references codes(codeid);
);
If the ordering of the codes is important for each "p", then include a priority or ordering column in the PCodes table.

How to optimize SELECT against a constant value?

Because I am using a data structure beyond my control, there is a table in my DB which will potentially have millions of Foreign-key => (key => value) pairs. Now, I know that one of the keys will be a certain value (in this case the key is related_content). Is it possible for MySQL to optimize the query so that it does not have to search the entire table for results?
Example table (called meta):
fk | key | value
====================================
1 | 'related_content' | '[2,3,4]'
1 | 'condiment' | 'mayo'
1 | 'condiment' | 'bananas'
29 | 'condiment' | 'ketchup'
29 | 'related_content' | '[1,7,9]'
95 | 'condiment' | 'mustard'
95 | 'related_content' | '[5,6,8]'
Example query:
SELECT value FROM meta WHERE fk = 29 AND key = 'related_content';
What I would like to do is:
ALTER TABLE `meta` ADD INDEX `meta_related` ON (`key`) WHERE `key` = 'related_content';
(Before anyone asks, the key column already has an index on it)
Add a 'composite' index
INDEX(fk, key)
what if you will add the key_index (int11) column that will represent the string in a key column like : related_content =1, condiment=2 and you will add index on key_index column, the index on int will be faster than on string, at the end you will select using two indexed integer field. It's gonna be fast

update if two fields exists, insert if not (MySQL)

This isn't an (exact) duplicate of this questions so I'Ve started a new one.
I have this table (ID is primary and auto increment)
ID | mykey | myfoo | mybar
============================
1 | 1.1 | abc | 123
2 | 1.1.1 | def | 456
3 | 1.2 | abc | 789
4 | 1.1 | ghi | 999
I would like to UPDATE row 1 with mybar = "333" only if mykey = '1.1' AND myfoo = 'abc'
If either mykey != '1.1' OR myfoo != 'abc' I would like to INSERT an new row.
Is this possible with one statement?
A unique index in MySQL does not have to be on a single column. You can add a UNIQUE index on multiple columns simply by specifying more columns in your ALTER TABLE..ADD UNIQUE statement:
ALTER TABLE myTable ADD UNIQUE (
mykey,
myfoo
);
Now you can use a regular INSERT INTO...ON DUPLICATE KEY statement.
SQLFiddle DEMO (note that the multiple repeated values are not added - all others are)
Note:
If either is NULL, it will not be counted as unique. mykey being 'bar' and myfoo being NULL could be added to infinity even though they have the "same" values (NULL isn't really a value).

is it possible to have a unique on a group of rows instead of entire table?

i have a table like
CREATE TABLE IF NOT EXISTS grouped_executions (
id INTEGER UNSIGNET NOT NULL ,
execution_id INTEGER UNSIGNED NOT NULL REFERENCES execution.execution_id ,
president BOOLEAN NOT NULL DEFAULT 0
PRIMARY KEY ( id, execution_id )
) ENGINE = InnoDB ;
all i want is to make president unique in a group of rows with same id.
for example: i have such data:
id | execution_id | president
= - = - = - = - = - = - = - =
1 | 1 | 0
1 | 2 | 1
1 | 3 | 0
1 | 4 | 0
i want mysql prevents inserting new row with id = 1 & president = 1
( of course i can make another table that holds president of a group but is above structure possible? )
Yes, you can add a unique-constraint for the combination of the two columns.
How do I specify unique constraint for multiple columns in MySQL?
You may want to try to set default NULL for president as unique key allows multiple NULL values, but then, it kind of make no sense to have a bool with only one 1 and many NULLs, but no 0. If you don't want to set other mechanisms like triggers (suggested by #David Hedlund), you better of with another table for that relationship (as you mentionned it). This way, if you one day wants a unique "secretary" or "treasury", it would be easy to define a new table instead of having that trigger (or another one) verify for this relationship.
Have you tried this?
ALTER TABLE grouped_executions ADD CONSTRAINT PRIMARY KEY (id, president);