I have this lovely piece of code that I want to optimize:
$result = mysql_query("select day from cities where name='St Charles'");
$row = mysql_fetch_assoc($result);
$day = $row['day'];
$result = mysql_query("select id,durability from goods_meta_data");
while($row = mysql_fetch_assoc($result)) {
mysql_query("delete from possessions where day_created<" . ($day-$row[durability]) . " and good_id=" . $row['id']);
}
It deletes rows from the table 'possessions' if the possession has expired. Whether or not the possession has expired is determined by values in the tables 'goods' and 'cities'.
Specifically, the age of the good = day-day_created, if the age of the good is more than its durability, then it is expired. (Every possession is a type of good, and every good has a unique durability.)
This code works, but I feel like this could be done in a single query.
The latency to the mysql server is particularly large, so doing this operation in less queries would be very beneficial.
How do I do that? Any ideas?
Also, if you can point me to any useful resources where I can learn about taking better advantage of relational databases in this manner, it would be helpful.
Assuming your first query returns a single result, then this should work:
DELETE p
FROM possessions p
INNER JOIN goods_meta_data gmd ON p.good_id = gmd.id
INNER JOIN cities c ON c.name = 'St Charles'
WHERE p.day_created < c.day - gmd.durability
Related
$id = $user['id']; // the ID of the logged in user, we are retrieving his friends
$friends = $db->query("SELECT * FROM friend_requests WHERE accepted='1' AND (user1='".$id."' OR user2='".$id."') ORDER BY id DESC");
while($friend = $friends->fetch_array()) {
$fr = $db->query("SELECT id,profile_picture,age,full_name,last_active FROM users WHERE (id='".$friend['user1']."' OR id='".$friend['user2']."') AND id != '".$id."'")->fetch_array();
echo $fr['age'];
}
I am basically looping through all my friends, and getting information about each one.
How would I ago about optimizing this, I am aware that that it is inefficient to run this query so many times, considering there are thousands of "friends", but I'm not exactly sure how to go about optimizing it. Any help is appreciated, thanks.
That article in the comments is great. You're definitely going to want to figure out how to write the join yourself. Here's my attempt at writing it for you. While you're rewriting the query, you might want to not * columns from friend_requests (or any at all since you're going to use the join). Any column you pull will have to be loaded into memory so any you can avoid pulling will help out. Especially if you're PHP server and your DB server are not on the same machine (less data over the network).
Anyway, here's my shot in the dark:
$id = $user['id'];
$friends = $db->prepare("
SELECT
freq.*
,users.id AS users_id
,users.profile_picture
,users.age
,users.full_name
,users.last_active
FROM friend_requests freq
INNER JOIN users u ON users.id IN (freq.user1,freq.user2) AND freq.id != u.id
WHERE
freq.accepted='1'
AND ? IN (freq.user1,freq.user2
ORDER BY freq.id DESC");
if($friends) {
$friends->bindParam("s",$id);
if($friends->execute()) {
// get_result() only works if you have the MySQL native driver.
// You'll find out real quick if you don't
$myFriends = $friends->get_result();
while($row = $myFriends->fetch_assoc()) {
echo $row['age'];
}
}
}
else {
printf("SQL Error: %s.<br>\n", $friends->error);
}
// cleanup
$friends->close();
quick reference: get_result()
i have a bit of a problem, ive never used JOIN before, and this is the first time, and i got into some problems:
<?php
//$count to keep the counter go from 0 to new value
$count=0;
//I need to select the level for the users building first, meanwhile i also
//need to get the money_gain from the table buildings, which is a table that
//is common for each member, which means it doesnt have a userid as the other table!
//And then for each of the buildings there will be a $counting
$qu1 = mysql_query("SELECT building_user.level,buildings.money_gain FROM building_user,buildings WHERE building_user.userid=$user");
while($row = mysql_fetch_assoc($qu1))
{
$count=$count+$row['level'];
echo $row['level'];
}
?>
So basically ive heard that u should tie them together with a common column but, in this case thir isnt any.. im just lost right now?
EDIT Oh right, I need the right level to be taken out with the correct money_gain too, in building_user its 'buildingid'and in buildings, its just 'id'! have no idea how to make a common statement though!
From your edit,
SELECT building_user.level,buildings.money_gain FROM building_user,buildings WHERE building_user.userid=$user AND building_user.buildingid = building.id;
You essentially get the records for the user joining them at the building id
Performance-wise, joins are a better choice but for lightweight queries such as this, the query above should work fine.
You can also give each column a neater name
SELECT building_user.level as level,buildings.money_gain as money gain FROM building_user,buildings WHERE building_user.userid=$user AND building_user.buildingid = building.id;
and access them as
$level = $row['level'];
$gain = $row['gain'];
i think you problem is, that MySQL doesn't know how to JOIN the two Tables. Therefor you have to tell MySQL how to do that.
Example
SELECT * FROM TABLE1 INNER JOIN Table1.col1 ON TABLE2.col2;
where col1 and col2 are the columns to join (a unique identifier)
<?php
$count=0;
$qu1 = mysql_query("SELECT bu.level, bs.money_gain FROM building_user bu, buildings bs WHERE bu.userid=$user");
while($row = mysql_fetch_assoc($qu1))
{
$count=$count+$row['level'];
echo $row['level'];
}
?>
Try this
$qu1 = mysql_query("SELECT building_user.level,buildings.money_gain
FROM building_user
JOIN buildings ON building_user.building_id = buildings.id
WHERE building_user.userid=$user");
building_user.building_id is the foreght key of table building_user and
buildings.id is the primary key of table buildings
my foundation on SQL is pretty weak so I hope you could bear with me. I have three tables: contents, categories, and categorization. The setup was chosen since some content will belong to one or more categories.
I want to fetch contents and its corresponding categories.
This is an overly-simplified version of the current script, without error-checking routines:
$q = "SELECT * FROM contents WHERE contents.foo = 'bar'"
$resource = mysql_query($q);
$categoryFilter = array();
$q2 = "SELECT * FROM categorization WHERE ";
while($content = mysql_fetch_assoc($resource))
{
$categoryFilter[] = "content_id='" . $content["id"] . "'";
}
if(count($categoryFilter))
{
$q2 .= implode(" OR ", $categoryFilter);
mysql_query($q2);
}
That's the gist of it. I hope you get what I am trying to do. I don't know if I can actually use JOINS the content_id may be present in multiple rows in categorization. So what I did was to simply append multiple OR's, trying to fetch items one by one. I really would not like to use multiple queries in this scenario. I hope anyone could suggest an approach
Thanks for your time
One query should be enough to fetch data from all three tables:
SELECT categories.category_id #, other fields
FROM contents
INNER JOIN categorization ON contents.content_id = categorization.content_id
INNER JOIN categories ON categorization.category_id = categories.category_id
WHERE contents.content_id = 1 # AND other filters
Tweak the columns in the SELECT clause and/or conditions in WHERE clause according to your needs.
This should do the same thing as in your example:
$q = "
SELECT *
FROM
contents c
categorization ctg ON ctg.content_id = c.id
WHERE c.foo = 'bar'
";
$result = mysql_query($q);
If I understand it correctly, you can do this in one sql statement
SELECT *
FROM contents t1
JOIN categorization t2
WHERE t1.content_id = t2.content_id AND t1.foo = 'bar'
Also ensure that content_id is indexed both in 'content' and 'categorization'. You may find it worthwhile indexing 'foo' aswell, but it depends on how you are actually searching.
I have the following 3 tables in the database.
Programs_Table
Program_ID (Primary Key)
Start_Date
End_Date
IsCompleted
IsGoalsMet
Program_type_ID
Programs_Type_Table(different types of programs, supports a dropdown list in the form)
Program_type_ID (Primary Key)
Program_name
Program_description
Client_Program_Table
Client_ID (primary key)
Program_ID (primary key)
What is the best way to find out how many clients are in a specific program (program type)?
Would the following SQL statement be the best way, or even plausible?
SELECT Client_ID FROM Client_Program_Table
INNER JOIN Programs_Table
ON Client_Program_Table.Program_ID = Programs_Table.Program_ID
WHERE Programs_Table.Program_type_ID = "x"
where "x" is the Program_type_ID of the specific program we're interested in.
OR is the following a better way?
$result = mysql_query("SELECT Program_ID FROM Programs_Table
WHERE Program_type_ID = 'x'");
$row = mysql_fetch_assoc($result);
$ProgramID = $row['Program_ID'];
$result = mysql_query("SELECT * FROM Client_Program_Table
WHERE Program_ID = '$ProgramID'");
mysql_num_rows($result) // returns how many rows of clients we pulled.
Thank you in advance, please excuse my inexperience and any mistakes that I've made.
Here is how you can do it:
<?php
// always initialize a variable
$number_of_clients = 0;
// escape the string which will go in an SQL query
// to protect yourself from SQL injection
$program_type_id = mysql_real_escape_string('x');
// build a query, which will count how many clients
// belong to that program and put the value on the temporary colum "num_clients"
$query = "SELECT COUNT(*) `num_clients` FROM `Client_Program_Table` `cpt`
INNER JOIN `Programs_Table` `pt`
ON `cpt`.`Program_ID` = `pt`.`Program_ID`
AND `pt`.`Program_type_ID` = '$program_type_id'";
// execute the query
$result = mysql_query($query);
// check if the query executed correctly
// and returned at least a record
if(is_resource($result) && mysql_num_rows($result) > 0){
// turn the query result into an associative array
$row = mysql_fetch_assoc($result);
// get the value of the "num_clients" temporary created column
// and typecast it to an intiger so you can always be safe to use it later on
$number_of_clients = (int) $row['num_clients'];
} else{
// query did not return a record, so we have no clients on that program
$number_of_clients = 0;
}
?>
If you want to know how many clients are involved in a program, you'd rather want to use COUNT( * ). MySQL (with MyISAM) and SQL Server have a fast way to retrieve the total number of lines. Using a SELECT(*), then mysql_num_rows leads to unnecessary memory ressources and computing time. To me, this is the fastest, though not the "cleanest" way to write the query you want:
SELECT
COUNT(*)
FROM
Client_Program_Table
WHERE
Program_ID IN
(
SELECT
Program_ID
FROM
Programs_Table
WHERE
Program_type_ID = 'azerty'
)
Why is that?
Using JOIN make queries more readable, but subqueries often prove to be computed faster.
This returns a count of the clients in a specific program type (x):
SELECT COUNT(cpt.Client_ID), cpt.Program_ID
FROM Client_Program_Table cpt
INNER JOIN Programs_Table pt ON cpt.Program_ID=pt.Program_ID
WHERE pt.Program_type_ID = "x"
GROUP BY cpt.Program_ID
Let's put an easy example with two tables:
USERS (Id, Name, City)
PLAYERS (Id_Player, Number, Team)
And I have to do a query with a subselect in a loop, where the subselect is always the same, so I would like to divide it into two queries and put the subselect outside the loop.
I explain. What works but it is not optimize:
for($i=0;$i<something;$i++)
{
$res2=mysql_query("SELECT Team from PLAYERS WHERE Number=$i
AND Id_Player IN (SELECT Id FROM USERS WHERE City='London')");
}
What I would like to do but it doesn't work:
$res1=mysql_query("SELECT Id from USERS where City='London'");
for($i=0;$i<something;$i++)
{
$res2=mysql_query("SELECT Team from PLAYERS WHERE Number=$i
AND Id_Player IN **$res1**");
}
Thanks!
Something like this should work.
<?
$sql = "SELECT Team from PLAYERS
JOIN USERS on (Id_player=Id)
WHERE Number BETWEEN $minID AND $maxID
AND City='London'
GROUP BY Team";
$results=mysql_query($sql) or die(mysql_error());
// $results contain all the teams from London
// Use like normal..
echo "<ul>\n";
while($team = mysql_fetch_array($results)){
echo "\t<li>{$team['Team']}</li>\n";
}
echo "</ul>";
Placing SQL quires in loops can be very slow and take up a lot of resources, have a look at using JOIN in you SQL. It's not that difficult and once you've got the hang of it you can write some really fast powerful SQL.
Here is a good tutorial worth having a look at about the different types of JOINs:
http://www.keithjbrown.co.uk/vworks/mysql/mysql_p5.php
SELECT PLAYERS.*, USERS.City FROM PLAYERS, USERS WHERE USERS.City='London' AND PLAYERS.Number = $i
Not the best way to do it; maybe a LEFT JOIN, but it should work. Might have the syntax wrong though.
James
EDIT
WARNING: This is not the most ideal solution. Please give me a more specific query and I can sort out a join query for you.
Taking your comment into account, let's take a look at another example. This will use PHP to make a list we can use with the MySQL IN keyword.
First, make your query:
$res1 = mysql_query("SELECT Id from USERS where City='London'");
Then, loop through your query and put each Id field one after another in a comma seperated list:
$player_ids = "";
while($row = mysql_fetch_array($res1))
{
$player_ids .= $row['Id'] . ",";
}
$player_ids = rtrim($player_ids, ",");
You should now have a list of IDs like this:
12, 14, 6, 4, 3, 15, ...
Now to put it into your second query:
for($i = 0; $i<something; $i++)
{
$res2 = mysql_query("SELECT Team from PLAYERS WHERE Number=$i
AND Id_Player IN $player_ids");
}
The example given here can be improved for it's specific purpose, however I'm trying to keep it as open as possible.
If you want to make a list of strings, not IDs or other numbers, modify the first while loop, replacing the line inside it with
$player_ids .= "'" . $row['Id'] . "',";
If you could give me your actual query you use, I can come up with something better; as I said above, this is a more generic way of doing things, not necessarily the best.
Running query in a loop is not a great idea. Much better would be to get whole table, and then iterate through table in loop.
So query would be something like that:
"SELECT Team from PLAYERS WHERE Number BETWEEN($id, $something)
AND Id_Player IN (SELECT Id FROM USERS WHERE City='London')"
$res1=mysql_query("SELECT Id from USERS where City='London'");
for($i=0;$i<something;$i++)
{
$res2=mysql_query("SELECT Team from PLAYERS WHERE Number=$i
AND Id_Player IN **$res1**");
}
Would work, but mysql_query() returns a RESULT HANDLE. It does not return the id value. Any select query, no matter how many, or few, rows it returns, returns a result statement, not a value. You first have to fetch the row using one of the mysql_fetch...() calls, which returns that row, from which you can then extract the id value. so...
$stmt = mysql_query("select ID ...");
if ($stmt === FALSE) {
die(msyql_error());
}
if ($stmt->num_rows > 0) {
$ids = array();
while($row = mysql_fetch_assoc($stmt)) {
$ids[] = $row['id']
}
$ids = implode(',', $ids)
$stmt = mysql_query("select TEAM from ... where Id_player IN ($ids)");
.... more fetching/processing here ...
}