Have unique combination of multiple attributes - mysql

What I am trying to do is set up the database table so that a set of multiple attributes must be unique but can be put in the database as many times as possible. For example if I had the following information with ID as the primary key:
id email name value
1 a#gmail.com A AValue
2 a#gmail.com A BValue
3 b#gmail.com B CValue
I don't want to have
id email name value
4 a#gmail.com B yetAnotherValue
combination possible where the email is to a different name. I want it to throw an exception. Is there any way to do this without a trigger or creating a separate table. If not , why not? Thank you.

Your question implies that name should be "dependent" on email. As such, your schema violates 3NF and, absent good reason, should be avoided: instead, you should have a table of (email, name) pairs with a UNIQUE constraint defined over the email column (and another over the name column, if the same name cannot be associated with multiple email addresses):
CREATE TABLE email_names (PRIMARY KEY (email))
AS SELECT DISTINCT email, name FROM your_table
Then your data table should simply contain a foreign key into this new table:
ALTER TABLE your_table
DROP COLUMN name,
ADD FOREIGN KEY (email) REFERENCES email_names (email)
To retrieve the names with your data, you would need to join the tables together in the relevant SELECT statement:
SELECT o.id, o.email, n.name, o.value
FROM your_table AS o JOIN email_names AS n USING (email)
However, there are sometimes good reasons for using denormalised schema—often when performance concerns are at play—and constraints of this nature can be enforced in MySQL. Before citing "anticipated" performance concerns, one should be mindful of Knuth's maxim that "premature optimisation is the root of all evil", and that the above approach will be very performant due to the indexing employed. Nevertheless, to enforce this constraint in your existing table:
Create a composite index over the combined columns in the table created above:
ALTER TABLE email_names
ADD INDEX (email, name)
Then, instead of the above changes to your current table, define a composite foreign key into the new table:
ALTER TABLE your_table
ADD FOREIGN KEY (email, name) REFERENCES email_names (email, name)

Related

Unable to figure how to manage foreign keys for 'project-customers-contacts' relation

I have a CUSTOMERS table and a CONTACTS table the relation between them is one to many obviously.
also I have PROJECTS table and PROJECT_CUSTOMERS table with relation one to many and with relation one to one between CUSTOMERS and PROJECT_CUSTOMERS.
my problem is that I have a fifth table PROJECT_CONTACTS ....I can't figure which tables shall I refer to in this tables, currently I am refering to PROJECT_CUSTOMERS and CONTACTS table, is this correct or there is something better ?
Your title refers to "foreign keys" but your question just seems to be about what columns should go in what tables.
First, decide what situations can arise and what you want/need to say about them. (This will your tables, columns, candidate keys, foreign keys and constraints.)
Every table holds rows that make some predicate (statement template parameterized by column names) true. Your design seems to have:
CUSTOMERS(id, ...) -- ID identifies a customer and ...
CONTACTS(id, ...) -- ID identifies a contact and ...
PROJECTS(id, ...) -- ID identifies a project and ...
PROJECT_CUSTOMERS(pid, cust_id, ...) -- project PID has customer CUST_ID and ...
PROJECT_CONTACTS(pid, cont_id, cust_id)...)
-- project PID has contact CONT_ID and project pid has customer CUST_ID and ...
A foreign key has a table & column list referencing a table and column list that forms a candidate key. It says that lists of values in the first table appear as lists of values in the second table. Where that is so, declare a foreign key.

Create two columns with pairwise uniqueness

Is it possible, in MySQL, to create two unique indexes, together?
For example, there is a table that holds two IDs - customer ID and product ID.
I would like to create uniqueness for the pair only.
In simple words, combination of user and product can be present only once.
The same user with another products can be present as much as needed.
And the same product with another users can be present as much as needed.
But the pair itself to be unique. How?
This query actually has an OR relation. I am looking for an AND relation.
ALTER TABLE top ADD CONSTRAINT unique_pair UNIQUE (uid, pid)
This does what you describe:
ALTER TABLE top ADD CONSTRAINT unq_top_unique_pair UNIQUE (uid, pid);
As does:
CREATE UNIQUE INDEX unq_top_uid_pid ON TOP(uid, pid);
Both of these create a unique index, which is then used for enforcing the constraint.

Adding column on specific row location

Its just a quick question :
I have user table and it has fields name, address and books_bought. books_bought is foreign key and its value is some PK from other table. Now after 1st insert, I will fill out all of this fields , but after second INSERT I want only to add a additional books_bought, so that am creating array of books_bought values?
You're doing it the wrong way around - this is a one-to-many relationship i.e. many books bought to one user. You need to have the foreign key on the many side of the relationship, so instead of having a foreign key to books_bought on the users table, add a foreign key to users on the books_bought table.
If you have a books table and a users table, then this is a many-to-many relationship and you will need a link table to go between them to hold the foreign keys.
You should not have more than one book in the books_bought cell because it will violate the atomicity constraint fo the database tables. You have to have a separate entry for each book_bought. This would cause a lot of redundant information as name, address would be repeated for each book bought by a specific person.
To solve this, you have to split the table into something like this:
R1(primary_key , name , address) and R2(foreign_key , books_bought)
Here foreign_key refers to primary_key of R1

mysql multiple foreign key vs inner join

Is there any sense in using two foreign key to the same parent table, to avoid inner join?
table: user_profile
id1, userid, username, firstname
table: user_hobby1
id2, userid(fk), hobby, movies
table: user_hobby2
id3, userid(fk), firstname(fk), hobby, movies
I want to select all firstname and hobby from the above table. I am not sure if user_hobby1 or user_hobby2 is the best design in terms of performance? One adds extra foreign key and another requires join.
Query1 :
Select firstname, hobby
from user_hobby2;
Query2 :
Select p.firstname, h.hobby
from
user_profile p
inner join user_hobby1 h on u.userid=h.userid;
Copying the value of an attribute from the user table into the hobby table isn't a "foreign key", that's redundancy.
Our performance objectives are not usually met with an approach of avoiding JOIN operations, which are a normal part of how relational databases operate.
I'd go with the normalized design as a first cut. Each attribute should be dependent on the key, the whole key, and nothing but the key. The "firstname" attribute is dependent on the id of the user, not the hobby.
Sometimes, we do gain performance benefits by introducing redundancy into the database. We have to do that in a controlled way, and make sure that we don't get update anomalies. (Consider what changes we want to apply if the value of "firstname" attribute is updated... do we make that change to the user table, the user_hobby table, or both.
Likely, "firstname" is not unique in the user table, so we definitely don't want a foreign key referencing that column; we want foreign keys that reference the user table to reference the PRIMARY KEY of the table.
There's no point in having two foreign keys defined between user_hobby and user, if a user_hobby is related to exactly one user. We only need one foreign key... we just store the id from the user table in the user_hobby table.
if you have two FK in user_hobby2 then you can only ensure that userid and username exist in user_profile, but you have no way to ensure which userid goes with a given username.
if you make (userid, username) a composite FK, then you'll guarantee the consistency of each tuple, but composite FK are generally more complicate to deal with. Depending on the behavior for update and delete cascades I've seen mysql triggering them both and refusing to delete from the parent.
Besides... what's the point of keeping that composite FK? It will only help you when you update or delete from user_profile, but won't help you copy the data when you insert new users or new hobbies for a user.
The join you are trying to avoid is very cheap. Just go with the first approach. It's easier to maintain and will help you keep your data consistent and normalized.

How to set a database integrity check on foreign keys referenced fields

I have four Database Tables like these:
Book
ID_Book |ID_Company|Description
BookExtension
ID_BookExtension | ID_Book| ID_Discount
Discount
ID_Discount | Description | ID_Company
Company
ID_Company | Description
Any BookExtension record via foreign keys points indirectly to two different ID_Company fields:
BookExtension.ID_Book references a Book record that contains a Book.ID_Company
BookExtension.ID_Discount references a Discount record that contains a Discount.ID_Company
Is it possible to enforce in Sql Server that any new record in BookExtension must have Book.ID_Company = Discount.ID_Company ?
In a nutshell I want that the following Query must return 0 record!
SELECT count(*) from BookExtension
INNER JOIN Book ON BookExstension.ID_Book = Book.ID_Book
INNER JOIN Discount ON BookExstension.ID_Discount = Discount.ID_Discount
WHERE Book.ID_Company <> Discount.ID_Company
or, in plain English:
I don't want that a BookExtension record references a Book record of a Company and a Discount record of another different Company!
Unless I've misunderstood your intent, the general form of the SQL statement you'd use is
ALTER TABLE FooExtension
ADD CONSTRAINT your-constraint-name
CHECK (ID_Foo = ID_Bar);
That assumes existing data already conforms to the new constraint. If existing data doesn't conform, you can either fix the data (assuming it needs fixing), or you can limit the scope (probably) of the new constraint by also checking the value of ID_FooExtension. (Assuming you can identify "new" rows by the value of ID_FooExtension.)
Later . . .
Thanks, I did indeed misunderstand your situation.
As far as I know, you can't enforce that constraint the way you want to in SQL Server, because it doesn't allow SELECT queries within a CHECK constraint. (I might be wrong about that in SQL Server 2008.) A common workaround is to wrap a SELECT query in a function, and call the function, but that's not reliable according to what I've learned.
You can do this, though.
Create a UNIQUE constraint on Book
(ID_Book, ID_Company). Part of it will look like UNIQUE (ID_Book, ID_Company).
Create a UNIQUE constraint on Discount (ID_Discount, ID_Company).
Add two columns to
BookExtension--Book_ID_Company and
Discount_ID_Company.
Populate those new columns.
Change the foreign key constraints
in BookExtension. You want
BookExtension (ID_Book,
Book_ID_Company) to reference
Book (ID_Book, ID_Company). Similar change for the foreign key
referencing Discount.
Now you can add a check constraint to guarantee that BookExtension.Book_ID_Company is the same as BookExtension.Discount_ID_Company.
I'm not sure how [in]efficient this would be but you could also use an indexed view to achieve this. It needs a helper table with 2 rows as CTEs and UNION are not allowed in indexed views.
CREATE TABLE dbo.TwoNums
(
Num int primary key
)
INSERT INTO TwoNums SELECT 1 UNION ALL SELECT 2
Then the view definition
CREATE VIEW dbo.ConstraintView
WITH SCHEMABINDING
AS
SELECT 1 AS Col FROM dbo.BookExtension
INNER JOIN dbo.Book ON dbo.BookExtension.ID_Book = Book.ID_Book
INNER JOIN dbo.Discount ON dbo.BookExtension.ID_Discount = Discount.ID_Discount
INNER JOIN dbo.TwoNums ON Num = Num
WHERE dbo.Book.ID_Company <> dbo.Discount.ID_Company
And a unique index on the View
CREATE UNIQUE CLUSTERED INDEX [uix] ON [dbo].[ConstraintView]([Col] ASC)