Joining mysql tables vs updating multiple tables - php

Lets say i got two tables in mysql.
1. person (id, name, lastname) - Image
2. someothertable (id, name, lastname, action, quantity) - image
I wanted to ask, if its really bad practice, to update both tables at once? For example if someone updates the last name of Robert Jackson to "Smith" then do 2 queries:
mysql_query("UPDATE person SET lastname = '$lastname' WHERE id = '$id'");
mysql_query("UPDATE someothertable SET lastname = '$lastname' WHERE name = '$name' AND lastname = '$oldlastname'");
Assuming for now, you wont meet 2 same names and surnames (its just an example).
Is it strongly recommended, to join those two tables when displaying data from tables, and change last name only in person table?
I didn't have need to use join before (never had databases big enough), and I just started to wonder if there is another way to do this (than 2 queries). Using join will require some code changing, but i am ready to do it, if its right thing to do.

Using a join is not a function of how big your databases are, it's about normalization and data integrity. The reason you would have lastname in only one table is so that there's no need to worry about keeping values in sync. In your example, if those calls are in a single transaction, then they should stay in sync. Unless one of them is changed somewhere else, or manually in the database.
So an option for you would be to have these tables:
person (id, name, lastname)
someothertable (id, person_id, action, quantity)

Instead of using 2 update, you can use trigger : Tutorial here

One option would be to make someothertable have a foreign key constraint on the lastname field in Person. You could apply an update trigger so it would automatically cascade.
Here is an example below:
Alter table someothertable add constraint foreign key (lastname) references Person (lastname) on delete cascade on update cascade;
A generic version of that can be seen below:
Alter table [table-name] add constraint foreign key (field-in-current-table) references [other-table-name] (field-in-other-table) on delete cascade on update cascade;
This can be applied to any field in any table. You can then set the triggers to be appropriate for you. Here is a reference link.
Have you considered normalization?
Another option would be to assign each person in the Person table a uniqueID (i.e. PersonID). Now in all your other tables you where you reference a person you reference them by the unique id. This adds many advantages:
1) It keeps the data normalized
2) It maintains data integrity
3) No need for updates, triggers, or cascades
4) A change would only be required in one place
Hope this helps. Best of luck!

Related

MySQL insert non-duplicate multiple values for each key

it's late, my child cried all dayand I'm expected to pull this off by the next 3 hours.
I have a 3 MySQL tables "entities", "users", "priviledges" (all have their id as key).
The table "priviledges" is having these columns: id(PRIMARY KEY), user_id, entity_id, priviledge
The thing is that when...
I grant the highest priviledge(3) to a user the system should create new table entries for all remaining entities
(once you become "admin" for one entity, you should be "admin" for all entities; if you should have priviledge(2) for some entity already, it should be changed to 3).
I cannot alter the SQL structure and I'm using PHP with PDO. I tried, but I always end up with duplicate entries in "priviledges" as I'm not able to utilise INSERT IGNORE (yep, still SQL noob).
Right now I just select all users with priviledges.priviledge(3), then I select all DISTINCT entities.id and dump them into PHP arrays and then I loop foreach through every "user" and "entity". So instead of sending 1 query, I'm ending with USERSxENTITIES and rightnow its over 500 queries.
Will there be a merciful soul to help me?
First it seems from your data structure that (user_id, entity_id) is meant to be unique.
Because of your "no table schema changes" criteria (otherwise I'd drop id (assumed to be an auto_increment) and add this as the primary key), so instead, create a unique index:
CREATE UNIQUE INDEX uniq_u_e ON priviledges (user_id, entity_id)
You probably have duplicate already so search existing questions for deleting duplicates and then add this index.
I assume you have another table of entities.
To create an admin (3) user on all entities that has an id as its entity primary key.
INSERT INTO priviledges( user_id, entity_id, priviledge)
SELECT 42 as user_id, id, 3
FROM entities
ON DUPLICATE KEY
UPDATE priviledge = 3
So this ensure that the user 42 has admin(3) access on all entities.

How to Structure a Table where *some* Columns can have Multiple Values? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to Store Multiple Options selected by User in a Table
I am very confused by this.
I want my users to be able to restrict who may contact them. So I need to create a new table of RESTRICTIONS for each user and their selected criteria, which will have a column for each user's restriction criteria. Those columns will include age, income, looking for, drugs, etc. Some of those columns (looking for, drugs, etc.) might contain multiple choices, and therein lies my problem. How do I structure the table in this way, considering some criteria (columns) can have multiple values while others do not?
I've been told about join tables and enum values (both of which I've never worked with before), but I am really confused as to how I would structure a table in which some columns (not all), can contain multiple choices.
How do I store those multiple choices in those specific columns of the table and how do I structure the overall table of RESTRICTIONS?
A DB column (at least theorethically) should NOT hold multiple values. Unfortunately, there are some programmers that store multiple values in a single column (values separated by comma for examples) - those programmers (in most cases) destroy the concept of DB and SQL.
I suggest you to read about Database Normalization to get a start in organizing your tables. And, do your best to achieve the Codd's Third Normal Form
This is the simplest way. Multiple attributes become rows in a second table.
CREATE TABLE restrictions (
user_id INTEGER PRIMARY KEY, -- references users (user_id), not shown
age_restriction VARCHAR(10) NULL,
income_restriction VARCHAR(20) NULL
);
CREATE TABLE looking_for (
user_id INTEGER NOT NULL REFERENCES restrictions (user_id),
looking_for VARCHAR(35) NOT NULL, -- could also be a foreign key.
PRIMARY KEY (user_id, looking_for)
);
INSERT INTO restrictions (user_id) VALUES (1);
INSERT INTO restrictions (user_id, age_restriction) VALUES (2, '> 25');
INSERT INTO looking_for VALUES (1, 'boat');
INSERT INTO looking_for VALUES (1, 'dunky fazoo');
If you wanted to accept multiple restrictions on age, such as '> 25' and '< 55', you could build another table for that, too.
To retrieve all the restrictions, use an OUTER JOIN.
SELECT r.user_id, r.age_restriction, r.income_restriction, lf.looking_for
FROM restrictions r
LEFT JOIN looking_for lf ON lf.user_id = r.user_id
You probably need more than one table.
You have a "users" table already, right? If one of your "restrictions" criteria can have just one value per user, then that column belongs in the "users" table. So you might have columns "min_age" and "max_age" in the users table, because presumably each user has only one, contiguous range of ages they are looking for.
On the other hand, for each restriction criterion that can have multiple values, you need a new table. So you might have a table "users_restrictions_drugs" in which the primary key is (user, drug). But you might also have a table "users_restrictions_lookingfor" in which the primary key is (user, lookingfor). Whatever "looking for" is.
For some of these tables it may make sense either
to define the second column (the one that isn't "user") as an enum
or (better) to have an additional table that sets out the possible values of that second column and is referenced by a foreign key.
table restrictions
user_id smoker_ok min_height max_height min_age max_age
-------------------------------------------------------
1 Y 150 200 24 34
2 N 100 180 32 57
table drug_restrictions
user_id drug_id drug_allowed
----------------------------------
1 H N
1 M Y
2 E Y
Would be an example. In the restrictions table, you can store explicit, singular values - smokers yes or no, or min and max requirements.
For each table where there are multiple choices, you can create a join table - I've given an example for drugs.
In the drug_restrictions table, user 1 says she doesn't want people using H, but does want people using M.
This solution allows you to use the "drug_id" as a foreign key to whatever table in your database populates the "drugs" field on the user interface. It allows you to use regular, standard SQL conventions for those foreign keys, and to enforce them at the database level by declaring them as foreign keys.
The drawback is, of course, that you have to query lots of tables to find matching records, and that's not much fun.
So, you could also follow Catcall's recommendation - this dramatically reduces the number of tables, but makes it impossible to use "standard" foreign key integrity constraints. This might be okay - it's certainly going to be faster.
I'd be reluctant to use enums - they tend to lead to complex queries, and are not "standard" SQL.
There's no problem to have tables where some columns have duplicate values. Consider a tbale with users; there's no problem if two users have the same birthday?
The only problem is a table where a primary key occurs more than once. For instance, a user table may very well have username as its primary key, and you wouldn't want two users with the same username.
So, make one table that lists users, one that lists restrictions, and one that joins the two. The latter will have one entry for every combination of user/permission.

Updating a many-to-many connector table all at once

Say I have two tables:
articles
categories
There's a many to many table that connects them.
CREATE TABLE cat2art (
article_id INT,
category_id INT
);
For a specific article, I have a 'new list' of category id's, and we need to update the cat2art table with these.
Some categories got removed, some got added and some stayed where they were. What is the most effective way to update this table?
I could naively delete all records with the specified article_id and simply add them again. However, if I were to record a date in that same table that tracks when an article was linked to a category, that information will now be destroyed.
I'm looking for a great pattern that easily solves this issue. This question is specifically for PHP and MySQL, but answers in other languages are also fine provided they are applicable to PHP+MySQL as well.
Other systems support MERGE statement which would do exactly what you want.
However, in MySQL, you would need at least two queries (it cannot delete and insert/update in a single statement):
DELETE
FROM cat2art
WHERE art_id = $art_id
AND cat_id NOT IN ($new_cat_1, $new_cat_2, …);
INSERT
IGNORE
INTO cat2art
VALUES
($art_id, $new_cat_1),
($art_id, $new_cat_2),
…;
You can define (article_id, category_id) as unique key, and when inserting a connection use INSERT IGNORE syntax. That way if this connection already exists it will not be added again, nor will it update the existing record, and the create_date column stays untouched.
example:
INSERT IGNORE INTO cat2art (article_id, category_id, create_date)
VALUES(100,200,NOW());

Managing Foreign Keys

So I have a database with a few tables.
The first table contains the user ID, first name and last name.
The second table contains the user ID, interest ID, and interest rating.
There is another table that has all of the interest ID's.
For every interest ID (even when new ones are added), I need to make sure that each user has an entry for that interest ID (even if its blank, or has defaults).
Will foreign keys help with this scenario? or will I need to use PHP to update each and every record when I add a new key?
Foreign keys are a kind of constraint, so they can only fail when you attempt to add records.
You can accomplish what you are describing with a trigger. I don't know the MySql syntax, but in SQL Server it would look something like this:
CREATE TRIGGER TR_ensure_user_interest ON interest FOR INSERT, UPDATE AS
BEGIN
INSERT user_interest (user_id, interest_id)
SELECT user_id, interest_id
FROM inserted
,user
EXCEPT (SELECT user_id, interest_id)
END
Note that this is a rather inefficient approach, but it should cover many of the cases you're concerned about.
UPDATE: I agree with the others who have observed the design "smell" here. If you can accomplish the required result using JOIN queries, that would be a much more efficient solution. However, I was trying to answer the question actually asked. (Plus, I have been in this situation, where physical records are helpful to other database users who are not adept at compound queries.)
For every interest ID (even when new
ones are added), I need to make sure
that each user has an entry for that
interest ID (even if its blank, or has
defaults).
It sounds like you need an OUTER JOIN (either LEFT or RIGHT) in one of your queries instead.
For example, if you wanted to get the level of interest a particular person has for each interest:
Assuming your tables look like this:
users:
user_id PK
user
user_interests:
user_id PK FK
interest_id PK FK
interest_level
interests:
interest_id PK
interest
SELECT i.interest, ui.interest_level
FROM interests i
INNER JOIN user_interests ui USING (interest_id)
LEFT JOIN users u USING (user_id)
WHERE user_id = ?
? is a placeholder.
Note that ui.interest_level will be null for interests with no data.
It sounds like you are forcing your physical design to mirror your logical design too tightly.
Maybe it would be a good idea to rethink exactly why you need to insert a row for every user in the physical table. Couldn't you just write your queries to assume the default value for an interestID if there isn't an associated interestID for a given user?
"Will foreign keys help with this scenario?"
No.
Your constraint is a sort of "completeness" constraint. It implies that for each new Interest added, there must be as many rows added to the USER_INTEREST table as there are users.
No SQL system is able to enforce that for you. It's up to you to enforce it through code.

PHP/MYSQL: SELECT statement, multiple WHERE's, populated from database

I'm trying to figure out how to get a select statement to be populated by an ever-changing number of where's. This is for an order-status tracking application.
Basically, the idea is a user (customer of our company) logs in, and can see his/her orders, check status, etc. No problem. The problem arises when that user needs to be associated with multiple companies. Say they work or own two different companies, or they work for a company that owns multiple sub-companies, each ordering individually, but the big-shot needs to see everything ordered by all of the companies. This is where I'm running into a problem. I can't seem to figure out a good way of making this happen. The only thing I have come up with is this:
client='Client Name One' OR client='Client name two' AND hidden='0' OR client='Client name three' AND hidden='0' OR client='Client name four' AND hidden='0'
(note that client in the previous code refers to the user's company, thus our client)
placed inside of a column called company in my users table of the database. This then gets called like this:
$clientnamequery = "SELECT company FROM mtc_users WHERE username='testing'";
$clientnameresult = mysql_query($clientnamequery); list($clientname)=mysql_fetch_row($clientnameresult);
$query = "SELECT -redacted lots of column names- FROM info WHERE hidden='0' AND $clientname ORDER BY $col $dir";
$result = mysql_query($query);
Thing is, while this works I can't seem to make PHP add in the client=' and ' AND hidden='0' correctly. Plus, it's kind of kludgy.
Any ideas? Thanks in advance!
Expanding on Tim's answer, you can use the IN operator and subqueries:
SELECT *columns* FROM info
WHERE hidden='0' AND client IN
( SELECT company FROM co_members
WHERE username=?
)
ORDER BY ...
Or you can try a join:
SELECT info.* FROM info
JOIN co_members ON info.client = co_members.company
WHERE co_members.username=?
AND hidden='0'
ORDER BY ...
A join is the preferred approach. Among other reasons, it will probably be the most efficient (though you should test this with EXPLAIN SELECT ...). You probably shouldn't grab all table columns (the info.*) in case you can later change the table definition; I only put that in because I didn't know which columns you wanted.
On an unrelated note, look into using prepared queries with either the mysqli or PDO drivers. Prepared queries are more efficient when you execute a query multiple times and also obviate the need to sanitize user input.
The relational approach involves tables like:
CREATE TABLE mtc_users (
username PRIMARY KEY,
-- ... other user info
) ENGINE=InnoDB;
CREATE TABLE companies (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR() NOT NULL,
-- ... other company info
) ENGINE=InnoDB;
CREATE TABLE co_members (
username NOT NULL,
company NOT NULL,
FOREIGN KEY (`username`) REFERENCES mtc_users (`username`)
ON DELETE CASCADE
ON UPDATE CASCADE,
FOREIGN KEY (`company`) REFERENCES companies (`id`)
ON DELETE CASCADE
ON UPDATE CASCADE,
INDEX (`username`, `company`)
) ENGINE=InnoDB;
If company names are to be unique, you could use those as a primary key rather than an id field. "co_members" is a poor name, but "employees" and "shareholders" didn't quite seem the correct terms. As you are more familiar with the system, you'll be able to come up with a more appropriate name.
You can use the IN keyword
client IN('client1','client2',...)

Categories