PDO rowcount used often - php

I often use the function rowCount of PDO like this for example:
$sql = $dataBase->prepare('SELECT email, firstname, lastname
FROM pr__user
WHERE id = :id');
$sql->execute(array('id' => $_SESSION['user_id']));
$rowCount = $sql->rowCount();
It al the time worked fine but I saw in the PHP manual:
If the last SQL statement executed by the associated PDOStatement was
a SELECT statement, some databases may return the number of rows
returned by that statement. However, this behaviour is not guaranteed
for all databases and should not be relied on for portable
applications.
http://php.net/manual/en/pdostatement.rowcount.php
It works fine with MySQL and MariaDB so I kept on using it. As I use it an application I wish portable, should I modify my code?

I never ask for the row count. Querying always returns an array of result rows (in some format), I can simply ask how many rows in the array -- such as with PHP's count(..) function.

What you're missing is that PDO is an interface to many different databases, not just MySQL. They make no guarantees that the function will return the same sort of values on completely different back-ends.
This is what "for portable applications" means: If you want your code to run universally on an arbitrary database you may need to avoid using that function. If that's not the case, you're not writing generic library code, you can depend on MySQL's particular behaviour.
Just be sure to test whatever you're doing to ensure that assumption is reasonable.

Rather, it's just pointless and superfluous. You are bloating your code for no reason.
From your example it is evident that you are going to use the data selected (otherwise there is no point in selecting email). So it means that you can use that data instead of row count all the way. Assuming the next operator would be fetch(), you can omit rowCount()
$sql = $dataBase->prepare('SELECT email, firstname, lastname
FROM pr__user
WHERE id = :id');
$sql->execute(array('id' => $_SESSION['user_id']));
$user = $sql->fetch();
if ($user) { ...
and from now on you can use $user in the every single condition where $rowCount has been used. Simply because you don't actually need a count here, but rather a boolean flag for which purpose an array would serve as good as an integer.
Even in a case when you don't need email but only to know whether a user exists, you can simply select just 1 scalar value and then fetch it - so your code remains uniform.

Related

Most efficent way of determing if a value is in a table

I often run into the situation where I want to determine if a value is in a table. Queries often happen often in a short time period and with similar values being searched therefore I want to do this the most efficient way. What I have now is
if($statment = mysqli_prepare($link, 'SELECT name FROM inventory WHERE name = ? LIMIT 1'))//name and inventory are arbitrarily chosen for this example
{
mysqli_stmt_bind_param($statement, 's', $_POST['check']);
mysqli_stmt_execute($statement);
mysqli_stmt_bind_result($statement, $result);
mysqli_stmt_store_result($statement);//needed for mysqli_stmt_num_rows
mysqli_stmt_fetch($statement);
}
if(mysqli_stmt_num_rows($statement) == 0)
//value in table
else
//value not in table
Is it necessary to call all the mysqli_stmt_* functions? As discussed in this question for mysqli_stmt_num_rows() to work the entire result set must be downloaded from the database server. I'm worried this is a waste and takes too long as I know there is 1 or 0 rows. Would it be more efficient to use the SQL count() function and not bother with the mysqli_stmt_store_result()? Any other ideas?
I noticed the prepared statement manual says "A prepared statement or a parametrized statement is used to execute the same statement repeatedly with high efficiency". What is highly efficient about it and what does it mean same statement? For example if two separate prepared statements evaluated to be the same would it still be more efficient?
By the way I'm using MySQL but didn't want to add the tag as a solution may be non-MySQL specific.
if($statment = mysqli_prepare($link, 'SELECT name FROM inventory WHERE name = ? LIMIT 1'))//name and inventory are arbitrarily chosen for this example
{
mysqli_stmt_bind_param($statement, 's', $_POST['check']);
mysqli_stmt_execute($statement);
mysqli_stmt_store_result($statement);
}
if(mysqli_stmt_num_rows($statement) == 0)
//value not in table
else
//value in table
I believe this would be sufficient. Note that I switched //value not in table
and //value in table.
It really depends of field type you are searching for. Make sure you have an index on that field and that index fits in memory. If it does, SELECT COUNT(*) FROM <your_table> WHERE <cond_which_use_index> LIMIT 1. The important part is LIMIT 1 which prevent for unnecessary lookup. You can run EXPLAIN SELECT ... to see which indexes used and probably make a hint or ban some of them, it's up to you. COUNT(*) works damn fast, it is optimized by design return result very quickly (MyISAM only, for InnoDB the whole stuff is a bit different due to ACID). The main difference between COUNT(*) and SELECT <some_field(s)> is that count doesn't perform any data reading and with (*) it doesn't care about whether some field is a NULL or not, just count rows by most suitable index (chosen internally). Actually I can suggest that even for InnoDB it's a fastest technique.
Also use case matters. If you want insert unique value make constrain on that field and use INSERT IGNORE, if you want to delete value which may not be in table run DELETE IGNORE and same for UPDATE IGNORE.
Query analyzer define by itself whether two queries are the same on or not and manage queries cache, you don't have to worry about it.
The different between prepared and regular query is that the first one contains rule and data separately, so analyzer can define which data is dynamic and better handle that, optimize and so. It can do the same for regular query but for prepared we say that we will reuse it later and give a hint which data is variable and which is fixed. I'm not very good in MySQL internal so you can ask such questions on more specific sites to understand details in a nutshell.
P.S.: Prepared statements in MySQL are session global, so after session they are defined in ends they are deallocated. Exact behavior and possible internal MySQL caching is a subject of additional investigation.
This is the kind of things in-memory caches are really good at. Something like this should work better than most microoptimization attempts (pseudocode!):
function check_if_value_is_in_table($value) {
if ($cache->contains_key($value)) {
return $cache->get($value);
}
// run the SQL query here, put result in $result
// note: I'd benchmark if using mysqli_prepare actually helps
// performance-wise
$cache->put($value, $result);
return $result;
}
Have a look at memcache or the various alternatives.

SELECT FROM a database named from a value in a JOIN

I have a SQL statement that has to pull information from two databases, one is a constant and known database, and the other is dynamically found in the first database. The databases structure looks like this:
(database) lookup
(table) test
(fields) key, database_name
(row sample) "foo", "database_foo"
(database) database_foo
(table) bar
(fields) important1, important2
(row sample) "silly", "test"
So my SQL statement looks like this:
SELECT
test.key as key,
test.database_name as database_name,
bar.important1 as important1,
bar.importnat2 as important2,
FROM
lookup.test as test,
(database_name).bar as bar, # this, obviously, doesn't work
WHERE
key = 'foo'
LIMIT 1;
Is there a way I can make this work, or is it better for me to just do two separate SQL statements, one for the lookup, and one for the database?
If you must do it this way then you need to use dynamic sql and a two statements.
You have your query built as a string and then you run an EXEC on the query once it's constructed.
In this case you would have a string variable for the db name, then you would create a query from this variable and your literal query, then you would simply execute it.
Be aware, though, this makes you vulnerable to SQL Injection if you don't control the input parameters.
Erland Sommarskog has a great primer on using dynamic SQL:
http://www.sommarskog.se/dynamic_sql.html
EDIT: From #BryanMoyle comment below, you will likely need to do both a separate query and dynamic sql. You need to extract the value in order to determine the other DB name... Since you cannot use the DB name as a variable otherwise, you'll need to SELECT this information first, then stick it into the subsequent query.
I personally go for 2 separate statements; it would make it easier to control for errors such as the the lookup provides a row, that the row provides a valid database, etc.
As Matthew pointed out, beware of SQLIA and sanitize all user input. I like to MD5 hash inputs and compare to the hash of the value looked up.
I'd go for two separate queries, mediated by php: It's simpler, more robust, and will keep you sane and productive. Write one query to find out which database you should be talking to. Use php (not SQL) to direct your query to that database:
$row = $lookup->prepare("SELECT database_name WHERE key = 'foo'")->execute()->fetch();
$db = $row[0];
Then you contact $db and ask for the row with key foo. Use PHP code to select the right open connection, or switch databases inside the connection with USE:
$query2 = "USE " . $db . "; SELECT * FROM bar where key == 'foo'"

Can I use the result of a previous MySQL query in the From section of another MySQL query?

I'm using a PHP webservice where I have performed a simple SELECT query, and stored it
$result = run_query($get_query);
I now need to perform further querying on the data based on different parameters, which I know is possible via MySQL in the form:
SELECT *
FROM (SELECT *
FROM customers
WHERE CompanyName > 'g')
WHERE ContactName < 'g'
I do know that this performs two Select queries on the table. However, what I would like to know is if I can simply use my previously saved query in the FROM section of the second section, such as this, and if my belief that it helps performance by not querying the entire database again is true:
SELECT *
FROM ($result)
WHERE ContactName < 'g'
You can make a temp table to put the initial results and then use it to select the data and in the second query. This will work faster only if your 1-st query is slow.
PHP and SQL are different languages and very different platforms. They often don't even run in the same computer. Your PHP variables won't interact at all with the MySQL server. You use PHP to create a string that happens to contain SQL code but that's all. In the end, the only thing that counts is the SQL code you sent to the server—how you manage to generate it is irrelevant.
Additionally, you can't really say how MySQL will run a query unless you obtain an explain plan:
EXPLAIN EXTENDED
SELECT *
FROM (SELECT *
FROM customers
WHERE CompanyName > 'g')
WHERE ContactName < 'g'
... but I doubt it'll read the table twice for your query. Memory is much faster than disk.
Thanks for the responses, everyone. Turns out what I was looking for was a "query of query", which isn't supported directly by PHP but I found a function over here which provides the functionality: http://www.tom-muck.com/blog/index.cfm?newsid=37
That was found from this other SO question: Can php query the results from a previous query?
I still need to do comparisons to determine whether it improves speed.
If I understand your question correctly you want to know whether saving the "from" part of your SQL query in a php variable improves the performance of you querying your SQL server, then the answer is NO. Simply because the variable keeping the value is inserted into the query.
Whether performance is gained in PHP, the answer is most probable yes; but depends on the length of the variable value (and how often you repeat using the variable instead of building a new complete query) whether the performance will be notable.
Why not just get this data in a single query like this?
SELECT *
FROM customers
WHERE CompanyName > 'g'
AND ContactName < 'g'

PHP or MYSQL not recognizing value

I'm using php to query from mysql in which either the home_team or away_team column contains a given value. I know that records of such values exist in the DB. But, for some reason either PHP or MySQL is telling me that it's not. My code is as follows.
$teams = array("St. Louis",
"NY Yankees",
"NY Mets",
"LA Dodgers",
"LA Angels");
foreach($teams as $given_team)
{
$query = mysql_query("SELECT COUNT(*) FROM current_season_games WHERE home_team = '".$given_team."' OR away_team = '".$given_team."'")or die(mysql_error());
$count = mysql_result($query, 0);
echo "".$count."<br />";
}
I know that the $count variable that's being echoed is incorrect. I'm looking at some rows in the DB that fit the conditions specified in the query.
Any ideas?
Thanks,
Lance
You are using COUNT(*). This won't give you the value of the field. COUNT(*) is usually used to measure how many results are returned.
What you'll want to do is actually select the column name or alternatively, select the entire row that matches the criteria. Something like this -
SELECT * FROM current_season_games
WHERE home_team = '".$given_team."' OR away_team = '".$given_team."'")
Here are a couple of things to check:
is PHP connecting to the same host and database? (This has been a 'DOH!' moment for some developers, when they have multiple databases... one local, and one on a test server.) Are you sure you are querying the same table?
try testing a simpler query, e.g. SELECT COUNT(*) FROM current_season_games to see if you can get any non-zero count returned
try putting the query into a string, and echoing out the string, before you execute it
is this an InnoDB table, and have the rows you are trying to count been committed? (Did the session adding the rows do a START TRANSACTION, insert the rows, and then not yet commit them? (This is a DOH! moment for some developers that are using multiple environments (e.g. SQLyog and a web server). (A separate session with transaction isolation level of REPEATABLE READ would not see the uncommitted rows)
is that column in the database using a case sensitive collation, where the equality comparison in the predicates (WHERE clause) is being handled as case sensitive vs. case insensitive?
None of those may solve your problem, but it's impossible to tell (from here), without a lot more information, what the issue is. But it's a few things you can check.
In old code that I come across still using the deprecated mysql_* functions, I'll usually come across the use of the mysql_num_rows() function.
Retrieves the number of rows from a result set. This command is only valid for statements like SELECT or SHOW that return an actual result set. To retrieve the number of rows affected by a INSERT, UPDATE, REPLACE or DELETE query, use mysql_affected_rows().
I would recommend changing your code to something like this -
$result = mysql_query("SELECT COUNT(*) FROM current_season_games WHERE home_team = '".$given_team."' OR away_team = '".$given_team."'")or die(mysql_error());
$count = mysql_num_rows($result);
Notice that the mysql_query() function doesn't return a $query as your variable name indicates. It returns a result set that you can then inspect to retrieve that actual data.

Alternative to "PDO::lastInsertId" / "mysql_insert_id"

I always hear that using "lastInsertId" (or mysql_insert_id() if you're not using PDO) is evil. In case of triggers it obviously is, because it could return something that's totally not the last ID that your INSERT created.
$DB->exec("INSERT INTO example (column1) VALUES ('test')");
// Usually returns your newly created ID.
// However when a TRIGGER inserts into another table with auto-increment:
// -> Returns newly created ID of trigger's INSERT
$id = $DB->lastInsertId();
What's the alternative?
IMHO it's only considered "evil" because hardly any other SQL database (if any) has it.
Personally I find it incredibly useful, and wish that I didn't have to resort to other more complicated methods on other systems.
One alternative is to use sequences instead, so you generate the ID yourself before you do the insert.
Unfortunately they are not supported in MySQL but libraries like Adodb can emulate them using another table. I think however, that the emulation itself will use lastInsertId() or equivalent... but at least you are less likely to have a trigger on a table which is purely used for a sequence
If you go the route of ADOdb (http://adodb.sourceforge.net/), then you can create the insert ID before hand and explicitly specific the ID when inserting. This can be implemented portably (ADOdb supports a ton of different databases...) and guarantees you're using the correct insert ID.
The PostgreSQL SERIAL data type is similar except that it's per-table/per-sequence, you specify the table/sequence you want the last insert ID for when you request it.
I guess it's not really state of the art either but I use write locks to make sure that I really get the last inserted ID.
It's not sophisticated and it's not efficient, but if the data you've inserted include unique fields, then a SELECT can obviously yield what you're after.
For example:
INSERT INTO example (column1) VALUES ('test');
SELECT id FROM example WHERE column1 = 'test';
You could try this:
$sql = "SELECT id FROM files ORDER BY id DESC LIMIT 1";
$PS = $DB -> prepare($sql);
$PS -> execute();
$result = $PS -> fetch();

Categories