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

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',...)

Related

Using MySQL IGNORE on one column under certain conditions

I want to import records from Gmail into a table, and I do not need duplicates for each account.
Description:
I have a table named list with definition below:
id int(11),
account_id int(11),
email varchar(255),
phone varchar(30),
primary key(id),
FOREIGN KEY (account_id) REFERENCES accounts (id)
This table holds records for different accounts and an email can be considered valid for two or more accounts. This means that an email can repeat in a table but can only appear once for each account_id.
I imported my contacts from Gmail (which is above 700 contacts and other users may have more than that).
The challenge:
I have an option of running two queries (one to check if email or phone exists, the second to insert record) for each record which in my case is 1,400 SQL queries to enable me insert all imported records, ensuring there are no duplicates for each account_id in the list table.
I have looked at MySQL IGNORE and similar keywords like ON DUPLICATE KEY UPDATE but they do not seem to work in this scenario as I cannot make the email and phone columns unique as they can contain duplicate content.
What is the best way of inserting these 700 records ensuring that the email and phone are not repeated for each account_id without having to run 1,400 queries?
QUESTION UPDATE:
I do not think INSERT IGNORE CAN WORK HERE FOR THE FOLLOWING REASONS:
I cannot make email and phone unique columns
The phone number may be empty but with an email entry, this may break the unique pattern
QUESTION ILLUSTRATION
I have two offices using the table to store their customer records. Someone can be a customer to both offices. This means his record can appear twice in the table but can only appear once for each account_id in the table.
The challenge now is to insert several records into the table ensuring that a record does not repeat for each account_id.
What you are trying to achieve is not very clear to me, but it looks very much like you just need to add some two-columns unique constraints.
an email must be unique for one given account_id:
ALTER TABLE your_table ADD UNIQUE (account_id, email);
a phone number must be unique for one given account_id:
ALTER TABLE your_table ADD UNIQUE (account_id, phone);
Both indexes may exist at the same time on your table. Either could raise a "duplicate-key violation" error, and would trigger the IGNORE or the ON DUPLICATE clauses of your insertions.
That being said, there is an issue in your structure. You are about to duplicate your customers' details for each account_id they are in business with.
You should have a customers table that contains all your customer's contact details (and only that), another accounts table -- your "offices", if I understand it right -- and finally one relation table to model the n-n relationship between customers and accounts:
CREATE TABLE customers_accounts (
customer_id INT NOT NULL,
account_id INT NOT NULL,
PRIMARY KEY (customer_id, account_id),
FOREIGN KEY (customer_id) REFERENCES customers(id)
FOREIGN KEY (account_id) REFERENCES accounts(id)
);
You had the answer: use "INSERT IGNORE" but what you probably didn't do is add a composite unique index (mentioned by RamdomSeed above), and/or set blank fields to NULL.
1) Create composite index, using the account id. This means that the email must be unique for that user.
ADD UNIQUE(account_id, email)
2) Regarding the phone "may be blank" set this to NULL when blank. Unique indexes ignore NULLS. (A small gotcha, but probably plays in your favour here, and why it's like that. You can then also add
ADD UNIQUE(account_id, phone)
(Aside: general advice is that you don't usually have multiple uniques on a table as it can get confusing and messy, but it might be what you need and it's fine - so long as you can handle the logic)
Seems like you could use INSERT IGNORE assuming AccountId is your unique identifier:
INSERT IGNORE INTO table
SET field = someValue,
anotherfield = someothervalue
If however you can have the same accounts with multiple emails, then this may not be what you're looking for.
So it sounds like you're using a scripting language (php seems to be popular with mysql) to store an array of contacts from gmail?
If so, this insert statement will insert the record if the account id doesn't exist in the table already -- this uses an Outer Join with a Null check, but you can also use Not In or Not Exists as well:
Insert Into YourTable (Id, AccountId, Email, Phone)
Select t.Id, t.AccountId, t.Email, t.Phone
From (Select 1 Id, 1 AccountId, 'someemail' Email, 'somephone' Phone) t
Left Join YourTable t2 On t.AccountId = t2.AccountId
Where t2.AccountId Is Null
EDIT:
Assuming I'm understanding the comments, then just add to the Outer Join:
Insert Into YourTable (Id, AccountId, Email, Phone)
Select t.Id, t.AccountId, t.Email, t.Phone
From (Select 1 Id, 1 AccountId, 'someemail' Email, 'somephone' Phone) t
Left Join YourTable t2 On t.AccountId = t2.AccountId
And (t.email = t2.email Or t.phone = t2.phone)
Where t2.AccountId Is Null
This should ensure no accounts get reinserted if they have a matching phone or email.
Insert Into YourTable (Id, Account_Id, Email, Phone)
Select a.id, a.Account_Id, a.Email, a.Phone
From (Select t.id, t.Account_Id, t.Email, t.Phone from t
group by account_id,email,phone )a;
Suggest to import the records into a temp table (t). Then only filter the records into another table (yourtable) ie remove the duplicate as you like.

Joining mysql tables vs updating multiple tables

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!

PHP and SQLite, INSERT INTO with SELECT doesn't work

I am planning to create my website; but to do this, I need to write a CMS...
I've already done most of the work, but now I am getting trouble executing this code. It doesn't return any error, but it simply..doesn't do what it should do.
I have three tables in my database:
users(
id_user primary key,
username unique varchar,
pwd unique varchar,
and more...
)
permissions(
id_permission INTEGER PRIMARY KEY,
permission-name varchar,
and more...
)
permissions_users (
id_permission,
id_user
they are foreign keys that references the same fields of the two previous tables
)
I want that this snippet will check if a checkbox with the same name of the field "permission-name" is checked; if it does, the script should insert into the 3rd table the id_user and id_permission retrieved from the tables permissions and users.
But it doesn't append, even if some checkboxes are checked!
Ah, I am using php 5.3.6, PDO and SQLite 3.7.4. The php code is below...
<?php
$res_perm = $db->query(
"SELECT permission-name FROM permissions"
);
while($result_perm=$res_perm->fetch()) {
if(isset($_POST[$result_perm["permission-name"]])) $db->exec(
"INSERT INTO permissions_users SELECT permissions.id_permission, users.id_user FROM permissions, users WHERE permissions.permission-name = '".$result_perm["permission-name"]."' OR users.nickname = '$ob_nickname'"
) OR die(print_r($db->errorInfo()));
}
?>
I haven't worked too much with SQLLite, but I believe the problem is with permission-name field (there is a dash(minus) sign in it, and I'm pretty sure you have to escape the it (in mysql I'd use backticks)

How can I get the foreign keys of a table in mysql

I am creating a class which, takes a table from a database, and displays it to a web-page, with as much functionality as possible. One of the things I would like to support, would be having the class detect which columns in the table have a foreign key constraint on them, so that it can then go to those tables, get all of their values and use them in a select-box which is called when you edit those fields, to avoid someone violating foreign key constraints,
The main problem is discovering which fields have a foreign key constraint on them, and which tables they are pointing to. Does anyone know how to do this???
Thanks,
Lemiant
Simple way to get foreign keys for given table:
SELECT
`column_name`,
`referenced_table_schema` AS foreign_db,
`referenced_table_name` AS foreign_table,
`referenced_column_name` AS foreign_column
FROM
`information_schema`.`KEY_COLUMN_USAGE`
WHERE
`constraint_schema` = SCHEMA()
AND
`table_name` = 'your-table-name-here'
AND
`referenced_column_name` IS NOT NULL
ORDER BY
`column_name`;
The INFORMATION_SCHEMA database contains details of the full schema of all other databases, including constraints:
http://dev.mysql.com/doc/refman/5.0/en/information-schema.html
You can also run a SHOW CREATE TABLE query to get the SQL to create a table, including its constraints.
Much can be retrieved from MySQL's information_schema, foreign keys included, as pointed out by dev-null-dweller.
1
SELECT * FROM information_schema.table_constraints
WHERE table_schema = 'dbname' AND table_name='mytable';
Instead of dbname use the function SCHEMA() to set the name of the database in USE.
2
As pointed out by Dan Grossman, the command
SHOW CREATE TABLE `yourtablename`
can be used basically get an SQL dump of the create table statement.
~3
MySQL provides a SHOW KEYS command. As such you could theoretically get the FK if you know a lower cardinality threshold and have few other keys in the table.
SHOW KEYS FROM `address` WHERE Non_unique AND CARDINALITY > 10000
As the key's cardinality changes each time the internal database is changed, this is rather theoretical. See the cardinality change for instance with running ANALYZE TABLE.
~4
It is useful to stick to a naming schema, such as foreigntablename_foreignfieldname. For example the field user_id in a table billing. Several ORMs of big Web Content Frameworks use this schema.
based on Bill Karwin answer in this other thread, I used this solution to get all the info I needed, included on_delete and on_update rules:
SELECT kcu.referenced_table_schema, kcu.constraint_name, kcu.table_name, kcu.column_name, kcu.referenced_table_name, kcu.referenced_column_name,
rc.update_rule, rc.delete_rule
FROM INFORMATION_SCHEMA.key_column_usage kcu
JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc on kcu.constraint_name = rc.constraint_name
WHERE kcu.referenced_table_schema = 'db_name'
AND kcu.referenced_table_name IS NOT NULL
ORDER BY kcu.table_name, kcu.column_name

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.

Categories