I have this code (removed param escaping just to cut down some code):
private function _get_tag_id($value)
{
$sql = "INSERT INTO tags (tag, added) VALUES ('$value', ".time().") "
. "ON DUPLICATE KEY UPDATE tag_id = tag_id";
$id = execute($sql);
if (empty($id))
{
$sql = "SELECT tag_id FROM tags WHERE tag = '$value'";
$id = execute($sql);
}
return $id;
}
I'm really bad at organizing my code and I've been reading about the importance of keeping your code DRY. Does this include any queries you might have? For example, I need to perform these same queries for a few fields, and what I've done is change it to this:
private function _get_field_id($field, $value)
{
$sql = "INSERT INTO {$field}s ({$field}, added) VALUES ('$value', ".time().") "
. "ON DUPLICATE KEY UPDATE {$field}_id = {$field}_id";
$id = execute($sql);
if (empty($id))
{
$sql = "SELECT {$field}_id FROM {$field}s WHERE {$field} = '$value'";
$id = execute($sql);
}
return $id;
}
Although that reduces some similar functions, it also makes the query much harder to read at first. Another problem after doing this is what happens if sometimes the query may be slightly different for a field? Let's say if the field is tag, I don't need the added column any more and maybe the query would now change to:
$sql = "INSERT INTO {$field}s ({$field}".($field == 'tag' ? '' : ", added").") "
. "VALUES ('$value', ".($field == 'tag' ? '' : time()).") "
. "ON DUPLICATE KEY UPDATE {$field}_id = {$field}_id";
Now it's starting to get extra messy, but I have a feeling people don't actually do that.
Another thing I've read is that functions should only do one thing. So would I cut up this function like this?
private function _get_tag_id($value)
{
$id = $this->_add_tag_id($value);
if (empty($id))
{
$id = $this->_get_tag_id($value);
}
return $id;
}
Or would it be better to leave it the way it was before?
If you don't think either of the ways I've tried organizing the code is correct also feel free to suggest the way you'd do it, or in other words what would be the best way to organize these simple bits of code?
I would turn it upside down - select first, and insert if not found.
Two reasons:
1) You will select and find more often that select and miss, so select first is on average faster.
2) "on duplicate key" is a non-standard extension to INSERT that will cause problems in the future if you should ever move to a SQL database without it. (I think it is MySQL only).
As for which is better, I'd rather try to understand the first or third.
Related
I'm developing a PHP script and I just want to know if I can make this code piece with better performance.
I have to make 2 mysql queries to complete my task. Is there any other way to complete this with better performance?
$language = "en";
$checkLanguage = $db->query("select id from language where shortName='$language'")->num_rows;
if($checkLanguage>0){
$languageSql = $db->query("select id from language where shortName='$language'")->fetch_assoc();
$languageId = $languageSql['id'];
}else{
$languageSql = $db->query("insert into language(shortName) values('$language')");
$languageId = $db->insert_id;
}
echo $languageId
You can improve your performance, by storing the stamtement object to a variable, this way it will be one less query:
$checkLanguage = $db->query("select id from language where shortName='$language'");
if($checkLanguage->num_rows >0){
$languageSql = $checkLanguage->fetch_assoc();
$languageId = $languageSql['id'];
}else{
$languageSql = $db->query("insert into language(shortName) values('$language')");
$languageId = $db->insert_id;
}
echo $languageId
or second option you add unique constraint to language and shortName.
If you insert a duplicate it will throw an error, if not it will insert, this way you keep only one query the INSERT one, but you might need a try catch for duplicates.
Why not just do something like this:
$language = "en";
$dbh = $db->query("INSERT IGNORE INTO `language` (`shortName`) VALUES ('{$language}');");
$id = $db->insert_id;
echo (($id !== 0) ? $id : FALSE);
This will perform your logic in a single query, and return the id, or false on a duplicate. It is generally better to resolve database performance issues in the SQL rather than in PHP, because about 65% of your overhead is in the actual connection to the database, not the query itself. Reducing the number of queries you run typically has a lot better impact on performance than improving your scripting logic revolving around them. People that consistently rely on ORM's often have a lot of trouble with this, because cookie cutter SQL is usually not very performant.
My entry form I have an inventory database with tables like aluminium, iron etc... Each table contains a subcategory of items like aluminium_pala, iron_1.5inch and so on. The entry code is like this:
include("dbConnect.php");
$orderNo = $_POST["number"];
if(isset($_POST["mat1"])&&$_POST["mat1"]!=NULL)
{
$mat1 = $_POST["mat1"];
$selmat1 = $_POST["selmat1"];
$amtmat1 = $_POST["amtmat1"];
$query = "INSERT INTO $mat1 ($selmat1,orderNo) VALUES (-$amtmat1,$orderNo);";
if(!($result = $mysqli->query($query)))
print "<div class='error'>insertion failed. Check your data</div>";
}
if(isset($_POST["mat2"])&&$_POST["mat2"]!=NULL)
{
$mat2 = $_POST["mat2"];
$selmat2 = $_POST["selmat2"];
$amtmat2 = $_POST["amtmat2"];
$query = "INSERT INTO $mat2 ($selmat2,orderNo) VALUES (-$amtmat1,$orderNo);";
if(!($result = $mysqli->query($query)))
print "<div class='error'>insertion failed. Check your data</div>";
}... and it goes on till mat11
I am trying to collect each similar table (mat1, mat2..) and their corresponding item (selmat1, selmat2...) and bunch the all in one query. That is, instead of going
INSERT INTO al_openable (zPala,orderNo) VALUES (23,14);
INSERT INTO al_openable (outer,orderNo) VALUES (50,14);
I am trying to execute it like
INSERT INTO al_openable (zPala,outer,orderNo) VALUES (23,50,14);
I need this to avoid duplicate foreign key entry(for $orderNo). One idea I've been considering is to use UPDATE if the order number is pre-existing. Do you guys think this is a good idea? And if so, what will be the best way to execute it? If not, how would a more experienced programmer solve this conundrum?
I think this question is related to your query: Multiple Updates in MySQL
You may use ON DUPLICATE KEY UPDATE in combination with INSERT statement.
Currently I have a piece of code that functions fine as it is. But what I really want to do is take the <?php if strval ..?> part and place it into the "SELECT * FROM projects" part as a WHERE clause. I am not sure whether this is possible or how to go about it. Any thoughts would be really valued. Hope this makes sense.
<?php
// ** User ID
$userid = $row_listelements ['id'];
// ** Projects
mysql_select_db($database_db, $db);
$query_activeusers = "SELECT * FROM projects ";
$activeusers = mysql_query($query_activeusers, $db) or die(mysql_error());
$row_activeusers = mysql_fetch_assoc($activeusers);
$totalRows_activeusers = mysql_num_rows($activeusers);
?>
<? do {?>
<?php if (!(strpos($row_activeusers['assignedto'], strval(",".$userid.",")) === false)) { ?>
<div><?=$row_activeusers['jobnumberdisplay'];?></div>
<?php } ?>
<? } while ($row_activeusers = mysql_fetch_assoc($activeusers)); ?>
<strong><?php echo $totalRows_activeusers; ?></strong>
In case your userid is a unique number, and I understand your question correctly, you could reach this by:
using IN - can handle strings and numbers (they have to be unique to make this work):
$query_activeusers = "SELECT * FROM projects WHERE ".$userid." IN (assignedto)";
or using FIND_IN_SET - can handle Strings and numbers is case sensitive:
$query_activeusers = "SELECT * FROM projects WHERE FIND_IN_SET('".$userid."', assignedto)"
But I think you should look at your database design. The trouble with including Foreign Keys in a delimited list like this is that whole point of a foreign key is to enable you to locate the information in the other table quickly, using Indexes. By implementing a database as it sounds you have, you have all sorts of issues to resolve.
i m not sure but maybe this will help you.
if(strval ...)
{
$where = "where field = $value";
}
Now you can use this $where variable in your select query. it will execute only if your if condition is satisfy.
If I understand your code correctly, you should use SQL LIKE, i.e. something like:
$query_activeusers = "SELECT * FROM projects WHERE assignedto LIKE '%," .
((int) $userid) . ",%'";
The cast to (int) here is done to ensure you have no special SQL characters in $userid - kind of cheap SQL quoting for values that are integers. You do not really need to use strval as concatenation into the string will convert that number into a string.
I found a PHP-based recipe and meal plan script, that I'd like to edit.
There's a MealPlan Class that allows all users to create their own daily menus/mealplans, by selecting from a list of all recipes found in the database.
I want to switch the table/column names used in the insert statement, based on the level of the user.
The original insert method is:
// Insert Meal Plan
function insert($date, $meal, $recipe, $servings, $owner) {
global $DB_LINK, $db_table_mealplans, $LangUI;
$date = $DB_LINK->addq($date, get_magic_quotes_gpc());
$meal = $DB_LINK->addq($meal, get_magic_quotes_gpc());
$recipe = $DB_LINK->addq($recipe, get_magic_quotes_gpc());
$servings = $DB_LINK->addq($servings, get_magic_quotes_gpc());
$owner = $DB_LINK->addq($owner, get_magic_quotes_gpc());
$sql = "INSERT INTO $db_table_mealplans (
mplan_date,
mplan_meal,
mplan_recipe,
mplan_servings,
mplan_owner)
VALUES (
'$date',
$meal,
$recipe,
$servings,
'$owner')";
$rc = $DB_LINK->Execute($sql);
DBUtils::checkResult($rc, NULL, $LangUI->_('Recipe already added on:'.$date), $sql);
}
My idea was to change:
mplan_date,
mplan_meal,
mplan_recipe,
mplan_servings,
mplan_owner
to:
$mplan_date,
$mplan_meal,
$mplan_recipe,
$mplan_servings,
$mplan_owner
and use a switch statement to change the table/column names. But I've been told that this sort of thing is frowned upon in OOP. What's the best way to go about this?
(NOTE: I can post the whole class if need be, but figured this would be enough info)
I don't have any code for you, but I've done something similar to this recently. Basically I dynamically made the SQL string by concating the string depending on what the user input. Bear with me as I try to type something on the fly.
$columnsArray = array();
$sqlCmd = "INSERT INTO";
If (someCondition) {
array_push($columnsArray, "mplan_date");
}//Could try turning this into a loop and just push all
//applicable columns into the array
$counter = 0;
Foreach ($columnsArray as $columnName) {
$sqlCmd .= " $columnName";
$counter++
If ($counter < count($columnsArray)){
$sqlCmd .= ",";
}//This will put a comma after each column with the
//exception of the last one.
}
So I did something like this only with a user defined select statement to retrieve data. Basiclly keep going with the logic until you’ve built a custom string that will be the user defined SQL statement, and then execute it. This includes doing something similar to the above script to concat the values into the string.
Hope this gives you some ideas.
I think this is an escaping issue or something. When I execute the query and populate all variables, everything is peachy and all row is updated properly in the DB.
I looked on StackOverflow to get me rolling with these dynamic/contructed on the fly queries and I'm at the end of my rope.
My stuff looks like this:
$sql="UPDATE users SET ";
if (!empty($fname)) { "fname = '$fname', ";}
if (!empty($lname)) { "lname = '$lname', ";}
if (!empty($location)) { "location = '$location', ";}
if (!empty($url)) { "url = '$url', ";}
"WHERE id = '$id' LIMIT 1";
When I break the query to insert the "IFs" I keep getting the following: Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
I ECHO'd the query and for some odd reason it's nto complete and the variables are coming in before the query start like so
fname = 'Rob', lname = 'Smith', location = 'Jersey City, NJ', url = 'http://somesite.com', UPDATE users SET Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 1
Sorry if I am not clear. I will clarify where needed. I am new at all this. Thank you!
You're not allowed to have a comma after the last thing you SET.
One easy solution is this:
$set = array();
if (!empty($fname)) { $set[] = "fname = '$fname'";}
if (!empty($lname)) { $set[] = "lname = '$lname'";}
if (!empty($location)) { $set[] = "location = '$location'";}
if (!empty($url)) { $set[] = "url = '$url'";}
if(!empty($set)) {
$sql = "UPDATE users SET ";
$sql .= implode(', ', $set)
$sql .= " WHERE id = '$id' LIMIT 1";
}
Oh, and make sure the variables you're shoving in the query are SQL safe; otherwise you've got a SQL injection issue.
Remember in these programming languages, each statement (text ending with a ;) is much like a complete sentence. You need a subject-object-verb for it to make sense. I can't just say
doggy;
I have to say
feed the doggy;
Similarly, I can't just say
"fname = '$fname', "
when I mean "Append this string to the query I started earlier". I have to be explicit:
$sql .= "fname = '$fname', ";
I'm saying "Append this text to $sql". Its a complete sentence.
better to put all your SETs into an array and implode them into a string. That way you can be sure there are no dangling commas. Something like:
if (!empty($fname)) $sets[]="fname = '$fname' ";
if (!empty($lname)) sets[]= "lname = '$lname' ";
if (!empty($location)) sets[]= "location = '$location' ";
if (!empty($url)) sets[]= "url = '$url' ";
$setstring= implode(',',$sets);
if($setstring) {
$query="UPDATE users SET $sets WHERE id = '$id' LIMIT 1";
//run query, etc.
}
Not really a direct answer but for dynamic queries i suggest using PDO. That way you can specify optional parameters more secure, elegant and easier.
<?php
$stmt = $dbh->prepare("INSERT INTO REGISTRY (name, value) VALUES (:name, :value)");
$stmt->bindParam(':name', $name);
$stmt->bindParam(':value', $value);
// insert one row
$name = 'one';
$value = 1;
$stmt->execute();
// insert another row with different values
$name = 'two';
$value = 2;
$stmt->execute();
?>
If your queries become larger, the way you are doing things now will be pretty complicated to maintain.
echo out your query and take a look at the commas in your SET caluse. Do you have too many? Not enough? I think you'll find that you have one extra comma. You'll probably want to use the implode() function to build up your SET clause. This will insert the appropriate number of commas in the appropriate places.
I see two problems, there is no space before WHERE which means it could turn out "url=http://www.stackoverflow.com"WHERE" and maybe cause a problem.
Also, there is a comma at the end of every SET clause, the last one in the list should not have a comma.