I'm facing a slight problem. I'm working on an application at the moment that requires the use of dynamic table names in MySQL.
Basically, I have a process of selecting albums from a database based on a number of factors (Genre, Play Length, Release Date etc) -- There is a section of this process that allows the user to create a bunch of custom filters..
The custom filters return a count to the user of all the albums within that selection criteria. The ID's of those albums are then stored in a table with a randomly generated hash/serial number (e.g. albumSelect_20880f9c05d68a)
I did it this way because I didn't want to store a huge comma separated list in a field (Really silly) -- And I didn't fancy sending an array of values to a hidden field in my HTML as this would just increase data throughput (Could be thousands of rows at a time)
In CodeIgniter, I'm using Query Bindings to generate my SQL queries, like so:
select * from artists where artistName = ? AND albumTitle = ?
The query is then automatically escaped when I parameterize the query
$query = $this->db->query($sql,array("Singer","Album"));
Now comes the tricky part
If I write my query to look something like this:
$sql = "select albumid from albums where albumid in (select albumid from albumSelect_?)";
$this->db->query($sql,array('20880f9c05d68a'));
The resulting query becomes:
select `albumid` from `albums` where `albumid` in (select `albumid` from `albumSelect_'20880f9c05d68a'`)
And quite rightly so, but obviously the query is then invalid..
Edit: More Info
The query could be part of a bigger query, depending on what criteria the user selects. e.g.
$sql = "select albumid from albums where albumid in(select albumid from tags where tag = ?) AND albumid in(select albumid from albumSelect_?)";
I was just wondering if there was a way to get this working, OR if anyone could suggest a better alternative.. Concatenation of the table name is obviously not an option.
Thanks in advance!
Dave
The escaping mechanisms are only for data strings, not for schema names. In other words, only for the content of a table, not for its structure. So you'll either have to paste the string into the query yourself, or avoid using tables in this way. The ? of query templates won't help you there.
If you paste the string into the table name, you can use the usual PHP string concatenation mechanisms. You should make extra sure that you check the string against a suitably strict regular expression, to avoid SQL injection. Make sure that you really only paste a single random string of the format you generate, and nothing else.
As an alternative, you could have one big table, containing all the selections, and use an additional column to hold your identification hash, or some other suitable key to identify a single selection. That way, you wouldn't have to modify the database schema during normal operations. I believe most developers, me included, would rather avoid such modifications by program code. Good database design works with a fixed schema.
It sounds to me like you want to use dynamic SQL. You'll probably have to go down the path of prepared statements. With them, you can call PREPARE on the string and subsequently EXECUTE it. Normal concatenation works just fine.
That should allow you to build your SQL as a string and execute it. If you use CodeIgniter's parametrization in combination with MySQL stored procedures, you can call a query like "CALL selectAlbums(?, ?)" (assuming selectAlbums is the stored procedure containing the PREPARE for the actual query), which will return the set.
If you want to get rid of the 's in output, channel the parameters through CONCAT, which will produce a normal string.
Related
I have this SQL Query which I want to sanitize:
SELECT * FROM navigation_$cat ORDER BY parent ASC, prio ASC
The problem is that I can't get the $cat sooner because it's being send via AJAX, and indicates which database needs to be accessed. How can I create a PDO statement that is safer than simply putting a string together?
You can implement a whitelist, but another option would be to check for valid input using:
SHOW TABLES LIKE :tblname
and 'navigation_'.$cat as the parameter. Check that this returns exactly one table, and that the returned table exactly matches the passed parameter.
Once that's done, you know it's safe to inject into the query, because you've ascertained that it is indeed a valid table name.
That being said, "dynamically selecting from an unknown-in-advance table" is usually a sign that you're designing your database wrong, and you should probably just have one navigation table with a category column inside.
IS it possible to have something like this:
SELECT c.id as id
FROM Channels c
LEFT JOIN CONCAT('hello_',c.id)
I NEED this concat with the c.id. THERE IS NO OTHER WAY.
So any tips?
You asked whether you can use an expression involving column values and functions like CONCAT to generate table names in queries in MySQL.
The short answer is no.
There's a longer answer involving MySQL prepared statements. That's basically a way to use string processing in a MySQL stored procedure to generate the text of a query to run.
If you intend to do it at SQL level: it is not possible. Table name tokens are not computed from expressions.
However, with php and creating a query dynamically, it should be quite trival to use a "modified" table name according to your need.
So, your concat() should be a php expression when building the query.
In the case where you are trying to join a (different) table based on the column value of a base table, you are far from any SQL semantics.
In that case you might want to rearrange your schema to merge all table instances identified by what your concat is now trying to compute info a single table and label each row with the logical table it belongs to.
I am working on converting a prototype web application into something that can be deployed. There are some locations where the prototype has queries that select all the fields from a table although only one field is needed or the query is just being used for checking the existence of the record. Most of the cases are single row queries.
I'm considering changing these queries to queries that only get what is really relevant, i.e.:
select * from users_table where <some condition>
vs
select name from users_table where <some condition>
I have a few questions:
Is this a worthy optimization in general?
In which kind of queries might this change be particularly good? For example, would this improve queries where joins are involved?
Besides the SQL impact, would this change be good at the PHP level? For example, the returned array will be smaller (a single column vs multiple columns with data).
Thanks for your comments.
If I were to answer all of your three questions in a single word, I would definitely say YES.
You probably wanted more than just "Yes"...
SELECT * is "bad practice": If you read the results into a PHP non-associative array; then add a column; now the array subscripts are possibly changed.
If the WHERE is complex enough, or you have GROUP BY or ORDER BY, and the optimizer decides to build a tmp table, then * may lead to several inefficiencies: having to use MyISAM instead of MEMORY; the tmp table will be bulkier; etc.
EXISTS SELECT * FROM ... comes back with 0 or 1 -- even simpler.
You may be able to combine EXISTS (or a suitable equivalent JOIN) to other queries, thereby avoiding an extra roundtrip to the server.
I'm trying to figure out how and which is best for storing and getting multiple entries into and from a database. Either using explode, split, or preg_split. What I need to achieve is a user using a text field in a form to either send multiple messages to different users or sharing data with multiple users by enter their IDs like "101,102,103" and the PHP code to be smart enough to grab each ID by picking them each after the ",". I know this is asking a lot, but I need help from people more skilled in this area. I need to know how to make the PHP code grab IDs and be able to use functions with them. Like grabbing "101,102,103" from a database cell and grabbing different stored information in the database using the IDs grabbed from that one string.
How can I achieve this? Example will be very helpful.
Thanks
If I understand your question correctly, if you're dealing with comma delimited strings of ID numbers, it would probably be simplest to keep them in this format. The reason is because you could use it in your SQL statement when querying the database.
I'm assuming that you want to run a SELECT query to grab the users whose IDs have been entered, correct? You'd want to use a SELECT ... WHERE IN ... type of statement, like this:
// Get the ids the user submitted
$ids = $_POST['ids'];
// perform some sanitizing of $ids here to make sure
// you're not vulnerable to an SQL injection
$sql = "SELECT * FROM users WHERE ID IN ($ids)";
// execute your SQL statement
Alternatively, you could use explode to create an array of each individual ID, and then loop through so you could do some checking on each value to make sure it's correct, before using implode to concatenate them back together into a string that you can use in your SELECT ... WHERE IN ... statement.
Edit: Sorry, forgot to add: in terms of storing the list of user ids in the database, you could consider either storing the comma delimited list as a string against a message id, but that has drawbacks (difficult to do JOINS on other tables if you needed to). Alternatively, the better option would be to create a lookup type table, which basically consists of two columns: messageid, userid. You could then store each individual userid against the messageid e.g.
messageid | userid
1 | 1
1 | 3
1 | 5
The benefit of this approach is that you can then use this table to join other tables (maybe you have a separate message table that stores details of the message itself).
Under this method, you'd create a new entry in the message table, get the id back, then explode the userids string into its separate parts, and finally create your INSERT statement to insert the data using the individual ids and the message id. You'd need to work out other mechanisms to handle any editing of the list of userids for a message, and deletion as well.
Hope that made sense!
Well, considering the three functions you suggested :
explode() will work fine if you have a simple pattern that's always the same.
For instance, always ', ', but never ','
split() uses POSIX regex -- which are deprecated -- and should not be used anymore.
preg_split() uses a regex as pattern ; and, so, will accept more situations than explode().
Then : do not store several values in a single database column : it'll be impossible to do any kind of useful work with that !
Create a different table to store those data, with a single value per row -- having several rows corresponding to one line in the first table.
I think your problem is more with SQL than with PHP.
Technically you could store ids into a single MySQL field, in a 'set' field and query against it by using IN or FIND_IN_SET in your conditions. The lookups are actually super fast, but this is not considered best practice and creates a de-normalized database.
What is nest practice, and normalized, is to create separate relationship tables. So, using your example of messages, you would probably have a 'users' table, a 'messages' table, and a 'users_messages' table for relating messages between users. The 'messages' table would contain the message information and maybe a 'user_id' field for the original sender (since there can only be one), and the 'users_messages' table would simply contain a 'user_id' and 'message_id' field, containing rows linking messages to the various users they belong to. Then you just need to use JOIN queries to retrieve the data, so if you were retrieving a user's inbox, a query would look something like this:
SELECT
messages.*
FROM
messages
LEFT JOIN users_messages ON users_messages.message_id = messages.message_id
WHERE
users_messages.user_id = '(some user id)'
Given a result set, how can I determin the actual names of the fields specified in the query (NOT their aliases).
$query = "SELECT first AS First_Name, last AS Last_Name FROM people";
$dbResult = mysql_query($query);
$fieldCount = mysql_num_fields($dbResult);
for ($i=0; $i<$fieldCount; $i++) {
// Set some values
$fieldName = mysql_field_name($dbResult, $i);
}
This example returns field names, but in this example it returns the alias "First_Name" instead of the actual field name "first".
Is it possible to get the actual field name from such a query. Particularly if I am writing a function and have no idea what query will be thrown at it.
If you are using MySQLi:
http://www.php.net/manual/en/mysqli-result.fetch-field.php
The field object has a "orgname" property.
The "classic" MySQL equivalent function doesn't report back the original column names.
Short answer: you don't.
Long answer: Once the dataset is pulled by MySQL and sent back to PHP, the only information PHP now has is the columns, or aliases if you used them. There is no way to look at a result set and determine what the original column names were. You have to switch to another DB driver like mysqli to obtain this info.
Your question doesn't make sense.
What are you going to do if you get a derived column i.e.
select column_a + column_b as order_total from orders;
are you saying you want to know that the original query was column_a + column b ??
if so, you probably need to write a query parser, or get one off the internet.
I think the implementation of that is beyond the scope of your question though :)
I'm not 100% sure about this, but I would say: there is no way.
The MySQL gives you back the result set, nothing more. It does not return the select statement nor any details about it.
So you cannot get the original field names because the server will provide you the information you asked: alias names.
If you don't mind making a second query (and your using MySQL 5 or greater) you can ask information_schema for the names.
Check out MySQL Reference for the details:
SHOW COLUMNS FROM tbl_name;
if you have access to the string of the query you could try a regular expression to parse it.
I'm no regex master but you could chop up the string by looking at the text between 'select' and 'from' then grabbing all the field names as either
field FieldAlias
or
field as FieldAlias
If you're trying to write some functionality to let you know what fields are being fetched for handling updates - the only way to do this correctly is for it to present an SQL-less interface to the code above and manage all SQL generation itself. This is called a data abstraction layer.