Alter one Table on Insertion into another - mysql

Let me first explain my situation here , I have a tabled called users which stores the user information.
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| user_id | varchar(10) | NO | PRI | NULL | |
| username | text | NO | | NULL | |
| password | text | NO | | NULL | |
| name | text | NO | | NULL | |
| email | text | NO | | NULL | |
| status | varchar(15) | NO | | active | |
+----------+-------------+------+-----+---------+-------+
And a table called country
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| country_id | varchar(10) | NO | PRI | NULL | |
| country_name | text | NO | | NULL | |
| country_rate | double | NO | | 0.2 | |
+--------------+-------------+------+-----+---------+-------+
Now , I need a process to map the no of countries assigned a particular user.
For Example:
user_001 is allowed to use country_001 , country_002 , country_003 n
user_002 is allowed to use country_003 , country_008
and so on..
What is the best approach to achieve the above?
What I thought is to have a table called say assignment and it will have the following fields:
assignment_id (primary key)
user_id (foreign Key)
country_001 (bool)
country_002 (bool)
...
...
country_010 (bool)
I am not sure if this the best approach , but even if I go for this , I am stuck in the point as to how to alter the structure of assignment , on every insertion in the country table ( Add a BOOL field in assignment with the newly created country_id as the column name).
I hope I was able to explain m situation. I know I can achieve via language support code (PHP , C++ etc) , but I was thinking if this could be done using some kind of TRIGGER so that I don't have to care about it in the code section.
Thanks a Lot.

It would be a better option to define user_countries table like this:
user_id fk on users(user_id)
country_id fk on countries(country_id)
unique key on ( user_id, country_id ) -- composite unique key
I am not sure why you want to define 10 columns in the user-country relation table.
But instead of 10 columns you just define a country_id with a foreign key and
with one-to-many relation ship between user_id and country_id. With this you can easily handle user and country relations. Unique key on their combination makes data access faster and avoids duplicates too.

Related

Should I use FOREIGN KEY or ADD INDEX in sql?

Today I have seen a video lecture in which they gave the foriegn key by using ADD INDEX on a table -
CASE 1 -
DECRIPTION OF TABLE 1 : subjects
+-----------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-----------+------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| menu_name | int(11) | YES | | NULL | |
| position | int(3) | YES | | NULL | |
| visible | tinyint(1) | YES | | NULL | |
+-----------+------------+------+-----+---------+----------------+
DECRIPTION OF TABLE 2 : pages
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| subject_id | int(11) | YES | | NULL | |
| menu_name | varchar(255) | YES | | NULL | |
| position | int(3) | YES | | NULL | |
| visible | tinyint(1) | YES | | NULL | |
| content | text | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
So in the column subject_id of table pages should store the id of table subjects.
Which one should i use and why ? -
ALTER TABLE pages ADD INDEX fk_subject_id (subject_id);
OR
ALTER TABLE pages
ADD FOREIGN KEY (subject_id) REFERENCES students(id);
video lecture uses ALTER TABLE pages ADD INDEX fk_subject_id (subject_id);.
CASE 2 -
Now Please cosider one more example -
According to above details, If I have 5 more tables including pages table(defined above).
All 5 tables have column subject_id which should store the data accodring to column id of table subjects.
So in this case
In this case, Which one Should I use ADD INDEX or FOREIGN KEY and why ?
Q : case 1 - Which one should i use ?
A : I'll choose fk not index,because the reference between pages and subjects is multi to one,if you add index on column with duplicate values that would not be helpful,because in most cases, only one index can be used to optimize a database query,and there's a primary index on subjects,so don't do that again.
note : you have to make pages - subject_id not null
Q : case 2 - Which one should i use ?
A : if the 5 tables between subjects are also multi to one,i'll choose FK not index,the reason like case1's answer.
Using a FOREIGN KEY and an INDEX are different things. FOREIGN KEYs are used for data integrity, so that you cannot have a reference, which points to nothing and that you cannot delete the "base" row without deleting the "linking" rows first (unless you use ON CASCADE DELETE stuff).
Indices are used to improve the search speed to find the correct rows faster in an SELECT and UPDATE query. This has nothing to do with data integrity.
To answer your question: You use a FOREIGN KEY if you want to reference the Id of rows from the other table (like you do with subject_id). Also, you don't need to add an INDEX on the column subject_id, because the InnoDB engine already does that.

MySQL foreign key constraints through more tables

I find myself stack to this problem. I've got the following 3 tables, which I can't modify (it'd be so nice):
Person
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| SSN | varchar(50) | NO | PRI | NULL | |
| name | varchar(50) | YES | UNI | NULL | |
| birthday | date | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Employee
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| SSN | varchar(50) | NO | PRI | NULL | |
| department | varchar(50) | YES | | NULL | |
| salary | varchar(50) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Employer
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| name | varchar(50) | NO | PRI | NULL | |
| department | varchar(50) | YES | | NULL | |
| salary | varchar(50) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
Contract
+----------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+-------------+------+-----+---------+-------+
| employer_name | varchar(50) | NO | PRI | NULL | |
| employee_name | varchar(50) | YES | PRI | NULL | |
+----------------+-------------+------+-----+---------+-------+
I know that:
employee(sin) is foreign key to person(sin),
employer(name) is foreign key to person(name),
contract(employer_name) is a foreign key to employer(name)
And I should insert another foreign key for contract(employee_name), though employee table has only the SSN. Is there a way to reference the foreign key to person.name, passing through employee table, something like
CONSTRAINTS FOREIGN KEY contract(employee_name) REFERENCES TO person(name) WHERE person(sin)=employee(sin);
?
Thank you very much for any help!
An SQL FK constraint says its referencing column list subrow values, if all non-NULL, have to appear as subrow values for its referenced column list, which must be declared UNIQUE/PK in a base table.
Your constraint is not an SQL FK constraint.
If we could use a query in the place of the referenced base table name in an SQL FK constraint then the constraint you would want would be:
Contract(person_name) REFERENCES
(SELECT name FROM Person p JOIN Employee e ON p.name = e.name)(name)
(It can be shown that name is UNIQUE in that table.) But we can't.
If MySQL supported CREATE ASSERTION then you could CHECK that every Contract person_name was IN (SELECT name FROM Person p JOIN Employee e ON p.name = e.name). But it doesn't.
So this is an example of a constraint that, if you can't redesign your tables, you would enforce by appropriate triggers when the tables involved change.
Is there a way to reference the foreign key to person.name, passing through employee table, something like
If you want to figure out situations like this then you have to forego the cop-outs of using precise terms like "foreign key" just because you are somehow reminded of the things they refer to but that aren't there, or using the poetic "passing through", or using the vague "something like". You have to actually write out the conditions that you want your tables to satisfy.

I'm using one table in a mysql database. I want duplicate entries in some fields

I'm setting this up in phpmyadmin. It's a simple table listing a collection of films. One column records the year the film was made. Another the decade it was made. A third, the genre.
I'm getting this message from phpmyadmin:
1062 - Duplicate entry '30' for key 'decade'
This is on the second insert. But, this will also happen for genre and eventually for year.
I thought at first that making these fields varchar instead of int would solve the problem, but I guess I just don't have the colums set up correctly.
What do I need to do?
My Schema:
MariaDB [movies]> desc films;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| alpha-name | varchar(50) | NO | UNI | NULL | |
| page-link | varchar(100) | NO | UNI | NULL | |
| sm-pic | varchar(100) | NO | UNI | NULL | |
| year | varchar(4) | NO | UNI | NULL | |
| decade | varchar(2) | NO | UNI | NULL | |
| genre | varchar(10) | NO | UNI | NULL | |
+------------+--------------+------+-----+---------+-------+
I see now that the keys are designated UNI, but where in phpmyadmin do I select it so that I can allow duplicates?
I can't see where I can thank people so I'm doing it here...
dropping the indexes on these fields does seem to have worked. Thanks for the specific code.
try:
Alter films drop index <column_name>

I need a good MySQL database structure

I'm quite new to MySQL and do not really know how to create a good layout for the following setup:
A riddim (rythm) can hold several tunes (songs). A song has the following information/fields: name, artist, label, producer, last modified timestamp, year, lyrics, flag, tag, source.
A riddim has the fields name, last modified, genre, youtube and image.
Here is an illustration for better understanding:
http://img194.imageshack.us/img194/6553/93112345.png
If I put all the information in one table I will have redundant data (such as genre or image), as every row with an artist/tune would have the riddim (rythm) it was sung on in it.
Currently my tables look like the following:
RIDDIMS table:
+---------------+--------------+------+-----+-------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+-------------------+-------+
| riddim | varchar(255) | NO | MUL | NULL | |
| genre | varchar(9) | NO | | NULL | |
| youtube | varchar(11) | NO | | NULL | |
| image | varchar(11) | NO | | NULL | |
| last_modified | timestamp | NO | | CURRENT_TIMESTAMP | |
+---------------+--------------+------+-----+-------------------+-------+
TUNES table:
+---------------+--------------+------+-----+-------------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+-------------------+-------+
| riddim | varchar(255) | NO | MUL | NULL | |
| artist | varchar(255) | NO | MUL | NULL | |
| tune | varchar(255) | NO | | NULL | |
| label | varchar(255) | NO | | NULL | |
| producer | varchar(255) | NO | | NULL | |
| year | varchar(4) | NO | | NULL | |
| lyrics | text | NO | | NULL | |
| flag | varchar(12) | NO | | NULL | |
| tag | varchar(255) | NO | | NULL | |
| source | varchar(255) | NO | | NULL | |
| last_modified | timestamp | YES | | CURRENT_TIMESTAMP | |
+---------------+--------------+------+-----+-------------------+-------+
But I'm sure it is not a good layout. Do you guys have suggestions on how the tables/database structure should look like?
Having the 'riddems' and 'tunes' in seperate tables is the right structure to use. Basically model your data as signular objects, if certain fields actually belong to two different objects, it should be in two different tables.
To connect the too tables together, on the table that belongs to the parent table (in this case a riddem has many tunes so tunes belongs to riddem) put a reference to the parents id field. So in this case the tunes table should have a field called 'riddem_id' which is set to the riddem the tune belongs to.
The riddim field of the tunes table should be a foreign key to the primary key of the riddims table. Generally it is not a good idea to use a text field as a primary key unless you are absolutely sure they will be unique. A safer option is to use a field called id, or riddim_id or something, which is a number with the AUTO_INCREMENT property set. That way every new item you add will be given its own unique key automatically. Then you set the foreign key to have the same value as the id of the linked record in the other table.
If you use this structure then you don't need to (and indeed shouldn't) duplicate any data. To find information about the riddim that a tune is in you do a join in a select query.
MySQL is not so different from other relational databases. So if you have some experience working with Oracle, DB2, PostgreSQL you can use it in your design.
In your explanation you did not mention the possible parent-child relations between two tables. Is tunes table a parent of riddim table?
If it is true you have to use riddim field in the riddim table as foreign key pointing to the primary riddim field in the tunes table. You can have multiple riddim records referring to the one tunes record.
It is not obvious for me why image and youtube have 11 characters. If youtune field contains actual URL it needs more characters. Are you going to encode youtube and image fields using enumerators to avoid long strings?
Ask yourself how your typical search is going to perform? What is the most important field
to search for?
I am not sure why you use VARCHAR type for year field.SMALLINT would be sufficient and much more efficient for search as well as for storage. As a rule integer types are much more efficient for search operations.

Audit logging for products data?

When the staff change the information of product name, option name or prices. It should insert the data into history log and who done it.
items table:
item_id (PK)
item_name
item_description
Note: item prices are in the item_options table
item_options table:
option_id (PK)
item_id (FK)
option_name
option_price
A item can have 1 or more options.
If I want to change the name items.item_name, It should copy the current record to the history table, delete current record from items table and then insert a new record with the new information in the items table?
What about the item_options, how would that work? If there are multiple options from specific item_id, do that mean I need to duplicate options to history table?
What Audit logging/history tables should look like for items and item_options?
Thanks
Your audit data should be stored per-table, rather than all in one place. What you'd do is create an audit table for each of the tables you want to track, and create triggers to create a record in the audit table for any data-manipulation operation on the audited table.
It's definitely advisable to disallow DELETE operations on the items and item_options tables - add flags like item_active and item_option_active so that you can softdelete them instead. This is normal practice in situations where you're doing things like storing invoices that reference products ordered in the past, and need the data for historical reporting purposes, but not for day-to-day use.
Your audit tables aren't something you should use for referencing old data, your normal data model should support simply "hiding" old data where it's likely that it's still going to be used, and storing multiple versions of data that will change over time.
For auditing, it's also useful to store the username of the last user to modify a given record - when used from a web application, you can't use MySQL's USER() function to get any useful information about who's logged on. Adding a column and populating it means you can use that information in your audit triggers.
NB: I'll assume that you won't allow item IDs to be changed under normal conditions - that would make your auditing system more complex.
If you add active flags, and last-modified-by data to your tables, they'll look something like:
Items table:
mysql> desc items;
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| item_id | int(11) | NO | PRI | NULL | auto_increment |
| item_name | varchar(100) | YES | | NULL | |
| item_description | text | YES | | NULL | |
| item_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
Item options table:
mysql> desc item_options;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| option_id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | YES | MUL | NULL | |
| option_name | varchar(100) | YES | | NULL | |
| option_price | int(11) | YES | | NULL | |
| option_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
Your audit tables need to store four extra pieces of information:
Audit ID - this ID is only unique for the history of this table, it's not a global value
Change made by - the database user who made the change
Change date/time
Action type - INSERT or UPDATE (or DELETE if you were allowing it)
Your audit tables should look something like:
Items audit table:
mysql> desc items_audit;
+------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+----------------+
| audit_id | int(11) | NO | PRI | NULL | auto_increment |
| item_id | int(11) | YES | | NULL | |
| item_name | varchar(100) | YES | | NULL | |
| item_description | text | YES | | NULL | |
| item_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
| change_by | varchar(50) | YES | | NULL | |
| change_date | datetime | YES | | NULL | |
| action | varchar(10) | YES | | NULL | |
+------------------+--------------+------+-----+---------+----------------+
Item options audit table:
mysql> desc item_options_audit;
+---------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+---------+----------------+
| audit_id | int(11) | NO | PRI | NULL | auto_increment |
| option_id | int(11) | YES | | NULL | |
| item_id | int(11) | YES | | NULL | |
| option_name | varchar(100) | YES | | NULL | |
| option_price | int(11) | YES | | NULL | |
| option_active | tinyint(4) | YES | | NULL | |
| modified_by | varchar(50) | YES | | NULL | |
| change_by | varchar(50) | YES | | NULL | |
| change_date | datetime | YES | | NULL | |
| action | varchar(10) | YES | | NULL | |
+---------------+--------------+------+-----+---------+----------------+
Don't use foreign keys on your audit tables; the rows in the audit tables aren't child rows of the records they're auditing, so foreign keys aren't of any use.
Triggers
NB: MySQL doesn't support multi-statement-type triggers, so you need one for each of INSERT, UPDATE and DELETE (if applicable).
Your triggers simply need to INSERT all the NEW values into the audit table. The trigger definitions for the items table might be:
/* Trigger for INSERT statements on the items table */
CREATE DEFINER=`root`#`localhost` TRIGGER trigger_items_insert_audit
AFTER INSERT ON items
FOR EACH ROW BEGIN
INSERT INTO items_audit (
item_id, item_name, item_description,
item_active, modified_by, change_by,
change_date, action
) VALUES (
NEW.item_id, NEW.item_name, NEW.item_description,
NEW.item_active, NEW.modified_by, USER(),
NOW(), 'INSERT'
);
END;
/* Trigger for UPDATE statements on the items table */
CREATE DEFINER=`root`#`localhost` TRIGGER trigger_items_update_audit
AFTER UPDATE ON items
FOR EACH ROW BEGIN
INSERT INTO items_audit (
item_id, item_name, item_description,
item_active, modified_by, change_by,
change_date, action
) VALUES (
NEW.item_id, NEW.item_name, NEW.item_description,
NEW.item_active, NEW.modified_by, USER(),
NOW(), 'UPDATE'
);
END;
Create similar triggers for the item_options table.
Update: Data History In E-commerce
The auditing we did above will allow you to keep a history of any given database table, but creates a data store that isn't suitable for use for data that needs to be accessed regularly.
In an e-commerce system, keeping usable historical data is important, so that you can change attributes while still presenting old values in certain situations.
This should be completely separate from your auditing solution
The best way to store history is to create a history table for each attribute that needs to be stored historically. This Stackoverflow question has some good information about keeping a history of a given attribute.
In your situation, if you're only concerned about price and title, you'd create a prices table, and an item_titles table. Each one would have a foreign key to either the item_options table or the items table (the master tables would still store the current price, or title), and would have the price or title, with its effective dates. These tables should have fine-grained (possibly column-based) permissions to avoid updating the effective_from dates, and the actual values once the record is inserted.
You should use the auditing solution above on these tables also.
if you do not have a bunch of constraints - then your data will get messed up in a hurry when you orphan the item entries by removing option entries and visaversa.
what you are asking for can be done in triggers, but this is not probably what you want.
imaging if you have an item with 2 options.
now you change the item name, that item gets deelted (and moved to history) - you have unlinkable options... is that what you intend?
what about order or other things that reference the items? same issues.
instead, create trigger logic to only allow 'reasonable' edits to the item. if desired, put a copy of the record into a parallel history table, but DO NOT delete the original.
you may also consider adding a status column to the item or some date ranges in order to account for the idea that this item is currently available or whatever other status you may need.