PHP/MySQL is there SELECT...INSERT and/or SELECT...INSERT...DELETE - php

I'm new to MySQL, and I'm trying to do the following and hoping I can do it all with a single PHP query to MySQL call rather than having to call, store in php, call, store in php etc.
Here's my database setup. 1. table of users, including email address. 2. table of 'invitees' by email address and user who invited the invitee 3. table of 'friends'
I want to join the table of users with the table of invitees to determine which invitees have registered as users and who invited them (keeping in mind they might have been invited by more than 1 person). From there I'll insert the newly registered invitee along with the person who invited him into the friends table. Finally I will delete that invitee from the invited table so that he won't keep receiving invitations.
I see how to do this with 3 separate php calls to mysql: 1. a JOIN to find the users who are in the user table and the invitee table (the join will also identify who invited them) 2. an INSERT to put the pair into the friends table 3. a DELETE to remove the user from the invitee list.
But this will be higher traffic/less optimized with multiple calls to the MySQL server and also a lot of data stored by my php script that I really just need for the next MySQL call. I have a feeling this should be possible in just 1 call with no data returned to the php script.
I see there is an INSERT...SELECT MySQL call (http://dev.mysql.com/doc/refman/5.0/en/insert-select.html), but I have not been able to find a way to combine steps 1 and 2 above, which would be a SELECT...INSERT option. Does this exist? I tried the following, but it didn't work...something like this would be my ideal.
SELECT users.user as user1, invited2.user as user2, invited2.email as email2 FROM users RIGHT JOIN invited2 ON users.email=invited2.invitee;
INSERT INTO friends (user, friend) VALUES (user1, user2), (user2, user1);
DELETE FROM invited2 WHERE invited2.email = email2;
The part before INSERT INTO works on its own, but like I said, if I just return that to my php script, I'll just have to store all the results and then call the second myself. Is there a way to do this in one call? And if so, is there a way to also tack on a delete statement (DELETE from invited2 WHERE invitee=users.email) afterwards?
Any advice would be appreciated. Thanks in advance.

MySQL does provide a INSERT...SELECT (http://dev.mysql.com/doc/refman/5.1/en/insert-select.html)
Your SQL can be edited to:
INSERT INTO friends (user, friend)
SELECT users.user as user1, invited2.user as user2
FROM users
RIGHT JOIN invited2 ON users.email=invited2.invitee;
DELETE invited2
FROM users
RIGHT JOIN invited2 ON users.email=invited2.invitee;
In this case, INNER JOIN seems to make more sense than RIGHT JOIN.

Related

Is this the correct way to structure this SQL query?

I am currently working with PHP and SQL on a website. There is a database containing users (accounts), organisations, and a relational table to link organisations to accounts (a many to many relationship)
When I delete an account from the database, the SQL query should also delete any organisations the account is linked to if the account being deleted is the only account linked to an organisation.
I am relatively new to SQL and have constructed a query which should delete an organisation from the organisations table under the conditions described above.
Here is my query:
'DELETE FROM TBL_ORGANISATIONS WHERE id = (
SELECT org_id FROM TBL_AFFILIATIONS WHERE account_email = :email AND (
SELECT COUNT(*) FROM TBL_AFFILIATIONS WHERE org_id IN (
SELECT org_id FROM TBL_AFFILIATIONS WHERE account_email = :email
)
) = 1
)'
Is this the correct way to structure this query or is there a clearer / more efficient way to do this? As I previously mentioned I am fairly new to SQL and have not yet grasped the concept of all the SQL keywords which can be useful in constructing queries such as this (JOIN etc.)
I thank you all in advance for any advice you can provide.
By the way:
I am using PDO hence the :email for those of you wondering.
If you have foreign key constraints, like I think you should, then this statement will fail, because the affiliation record still points to the organisation record to be deleted.
You can use ON DELETE CASCADE to delete the organisation, like Mihai suggested in his (now deleted) comment, but to do that, you will still have to check whether there's only one affiliation linked to the organisation.
In this case, I'd rather query the organisation's ID first. You will probably have that at hand anyway, because you'll know the details of the account you are deleting. Then first delete the account and next delete the organisation if you need to, with a statement that looks like this:
DELETE FROM TBL_ORGANISATIONS o
WHERE
o.id = :ThatIdYouQueriedBefore AND
NOT EXISTS (SELECT 'x' FROM TBL_AFFILIATIONS a.org_id = o.id);
Personally I'm not a big fan of cascaded deletes, since a seemingly small mistake might cost you a lot of data, but even you do use them, I don't think it makes this particular case much easier.
I think I would do two logical queries. Feel free to wrap in transaction if you need to guarantee that the deletes always happen together, with a rollback if they don't.
First, delete both the account and the account to organization affiliation with a single query (here I am assuming your account table name)
DELETE tbl_account,tbl_affiliations
FROM tbl_account INNER JOIN tbl_affiliations
ON tbl_account.account_email = tbl_affiliations.account_email /* I am assuming join condition here, perhaps there is an id to be used instead */
WHERE tbl_account.acount_email = ?
Then, delete any orphaned organizations:
DELETE tbl_organizations
FROM tbl_organizations LEFT JOIN tbl_affiliations
ON tbl_organizations.org_id = tbl_affiliations.org_id
WHERE tbl_affiliations.org_id IS NULL
Note that since that last query is not dependent on any account-specific information, you could also consider running an asynchronous process to clean up orphaned organizations if you don't need the organization deletion to happen synchronously with the account deletion.
The benefit of this approach is that you can potentially always delete user accounts in the same way, using the first query, as this works regardless as to whether there are multiple accounts associated with the organization. So you don't need any extra application logic or SQL SELECT subqueries to look for cases where there is a single account associated with an organization.
As you are trying to delete the data from two tables at a time, you can try this.
DELETE from TBL_ORGANISATIONS TO JOIN TBL_AFFILIATIONS TA
ON(TO.id=TA.org_id) where TA.account_email=:email;
Here, we are trying to delete the rows from two tables TBL_ORGANISATIONS and TBL_AFFILIATIONS using primary key of TO(i.e. id) and foriegn key TA(i.e. org_id) and adding a condition using where clause where TA.account_email=:email.

Perhaps joining 2 tables together when Users register

So this is my sorta first time doing an Online RPG (MMORPG), It's a browser based-Pokemon game.
In the Database, l've created 2 tables;
1.Pokemons (Columns; ID#, PokemonName, PokemonType, Level, Exp, HPoints, ATT, DEF)
2.Users (Columns; ID, Full Name, E-Mail, Username, Password)
In the Register field, they put in their info (User, Pass, Email), then chooses a Starter Pokemon to fight with. My question is how would i interpret that into a SQL/PHP command that joins the starter pokemon to that User or vise versa?
Far as l know it's
SELECT * FROM table_name;
But let's say l wanted to choose THAT user who just registered. Would the * just automatically choose that player or will it select everything from the Users list (Currently 3 rows of users in the Table).
Im reading w3schools for the moment, but needed some real-time advice on how l should go about with this. Thanks again!
thepokemonrpg.x10.mx If you guys wanna see what l mean.
This would indeed select everything from the Users table:
SELECT * FROM Users
The * doesn't mean all records, it just means all columns for any matching record. However, since there's no filter, all records happen to be matching records. If you want to only select a single record from that table, you would add a WHERE clause:
SELECT * FROM Users WHERE Username='someusername'
There are a few different ways that you can construct the SQL query to include a value like that (since someusername would likely come from a variable and not be explicitly written like that). Just be aware of SQL injection vulnerabilities when building those queries. You wouldn't want to accidentally publish a website where users can write their own database code and execute it on your server.
As for joining the tables, I currently don't see a way that you could do that. These two tables define two distinct entities, but have no way to relate to one another. There are a couple of ways you could do that, depending on how these entities are actually related. To that end:
Does a Pokemon always have exactly 0 or 1 owner? or;
Does a Person always have exactly 0 or 1 Pokemon? or;
Can a Pokemon have many owners and a Person have many Pokemons?
If the first statement is true, then you can add a UserId column to your Pokemons table and make it a foreign key to the Users table. That way every Pokemon record would indicate which User owns it.
If the second statement is true, then you can add a PokemonId column to your Users table and make it a foreign key to the Pokemons table. That way every User record would indicate which Pokemon is currently owns.
If the third statement is true, then you'd need to add a joining table to maintain this many-to-many relationship. Something like this:
PokemonUsers
------------
Id
PokemonId
UserId
Every record in this table would essentially be a link between a record in the Users table and a record in the Pokemons table.

Multiple joins in database

This situation is pretty difficult to explain, but I'll do my best.
For school, we have to create a web application (written in PHP) which allows teachers to manage their students' projects and allow these to make peer-evaluation. As there are many students, every projects has multiple projectgroups (and ofcourse you should only peer-evaluate your own group members).
My databasestructure looks like this at the moment:
Table users: contains all user info (user_id is primary)
Table: projects: Contains a project_id, a name, a description and a start date.
So far this is pretty easy. But now it gets more difficult.
Table groups: Contains a group_id, a groupname and as a group is specific for a project, it also holds a project_id.
Table groupmembers: A group contains multiple users, but users can be in multiple groups (as they can be active in multiple projects). So this table contains a user_id and a group_id to link these.
At last, admins can decide when users need to do their peer-evaluation and how much time they have for it. So there is a last table evaluations containing an evaluation_id, a start and end date and a project_id (the actual evaluations are stored in a sixth table, which is not relevant for now).
I think this is a good design, but it gets harder when I actually have to use this data. I would like to show a list of evaluations you still have to fill in. The only thing you know is your user_id as this is stored in the session.
So this would have to be done:
1) Run a query on groupmembers to see in which groups the user is.
2) With this result, run a query on groups to see to which projects these groups are related.
3) Now that we know what projects the user is in, the evaluations table should be queried to see if there are ongoing evaluations for this projects.
4) We now know which evaluations are available, but now we also need to check the sixth table to see if the user has already completed this evaluation.
All these steps are dependent on the result of each other, so they should all contain their own error handling. Once the user has chosen the evaluation they wish to fill in (a evaluationID will be send via GET), a lot of new queries will have to be run to check which users this member has in his group and will have to evaluate and another check to see which other groupmembers are already evaluated).
As you see, this is quite complex. With all the errorhandling included, my script will be a real mess. Someone told me a "view" might help in this situation, but I don't really understand why this would help me here.
Is there a good way to do this?
Thank you very much!
you are thinking too procedurally.
all your conditions should be easily entered into one single where clause of a sql statement.
you will end up with a single list of the items to be evaluated. only one list, only one set of error handling.
Not sure if this is exactly right, but try this basic approach. I didn't run this against an actual database so the syntax may need to be tweaked.
select p.project_name
from projects p inner join evaluations e on p.project_id = e.project_id
where p.project_id in (
select project_id
from projects p inner join groups g on p.project_id = g.project_id
inner join groupmembers gm on gm.group_id = g.group_id
where gm.user_id = $_SESSION['user_id'])
Also, you'll need to make sure that you properly escape your user_id when making it a part of the query, but that is a whole other topic.

Question about unions

Okay I'm having a problem. I have a social networking site and have some code that will call statuses from only friends. I want it to call both statuses from friends as well as the user logged in. Kind of the way facebook does.
Tables: members, friends, status
members - Handle
friends - Username, Friend
status - Handle, Type(public or friends)
Here is code that will show the most recent status from a user's friends.
$shawing = "SELECT *
FROM status
JOIN friends
ON status.Handle = friends.Friend where friends.Username='$members[Handle]' and status.Type='friends' ORDER by status.Id DESC" or print mysql_error();
$members[Handle] calls from an include that identifies the user who is logged in.
Someone told me a Union would work, but I've only been able to make one status show up and the code I have now only has one result.
Basically, I want to take the list of usernames from the person's friends list and add the user logged in's username and show only statuses from the friends section with those usernames.
Also, it would be great if I could get a structure for the code because I'm new to the whole JOIN and UNION. I understand it, sure, I just don't understand how it's properly setup.
The more UNION and JOIN's you use the more overhead you start to accumulate. Once you try to scale this by 10,000 records you'll start seeing this query in your servers slow query log.
I suggest keeping it simple and run two queries.
1st query: Get the users status
2nd query: Get the users friends status'
The first query should be very fast as you're most likely using the users userid (primary key on the table, yes?). Some times the best way is to just keep things simple.

A better logging design or some SQL magic?

I'm knee deep in modifying some old logging code that i didn't write and wondering what you think of it. This is an event logger written in PHP with MySQL, that logs message like:
Sarah added a user, slick101
Mike deleted a user, slick101
Bob edited a service, Payment
Broken up like so:
Sarah [user_id] added a user [message], slick101 [reference_id, reference_table_name]
Into a table like this:
log
---
id
user_id
reference_id
reference_table_name
message
Please note that the "Bob" and "Payment" in the above example messages are Id's to other tables, not the actual names. A join is needed to get the names.
It looks like the "reference _ table _ name" is for finding the proper names in the correct table, since only the reference _ id is stored. This would probably be good if somehow i could join on a table name that stored in reference_table_name, like so:
select * from log l
join {{reference_table_name}} r on r.id = l.reference_id
I think I see where he was going with this table layout - how much better to have ids for statistics instead of a storing the entire message in a single column (which would require text parsing). Now I'm wondering..
Is there a better way or is it possible to do the make-believe join somehow?
Cheers
To get the join based on the modelling, you'd be looking at a two stage process:
Get the table name from LOG for a particular message
Use dynamic SQL by constructing the actual query as a string. IE:
"SELECT l.* FROM LOG l JOIN "+ tableName +" r ON r.id = l.reference_id"
There's not a lot of value to logged deletions because there's no record to join to in order to see what was deleted.
How much history does the application need?
Do you need to know who did what to a value months/years in the past? If records are required, they should be archived & removed from the table. If you don't need all the history, consider using the following audit columns on each table:
ENTRY_USERID, NOT NULL
ENTRY_TIMESTAMP, DATE, NOT NULL
UPDATE_USERID, NOT NULL
UPDATE_TIMESTAMP, DATE, NOT NULL
These columns allow you to know who created the record & when, and who last successfully updated it and when. I'd create audit tables on a case by case basis, it just depends on what functionality the user needs.

Categories