This question already has answers here:
Get table column names in MySQL?
(19 answers)
Closed 9 years ago.
As I am still learning PHP and MySQL, I would like to know if it is possible to query a table without knowing it's name. Knowing the table I am querying and the column I would like to retrieve, I can write something like
$book_title=$row['book_title'];
Then I can use the resulting variable later in the script. In each case, each book category table will have a column of interest with a different name. I have several tables on which I am running queries. I am able to query any table by using a variable that always evaluates to the correct table name, because all the input from users corresponds to the tables in the database, so the $_POST super global will always carry a correct table name. The problem is for me to have a
$variable=$row['column'];
in cases where I do not know a column name before hand even though I know the table name.
My queries are simple, and look like
query="select * FROM $book_categories WHERE id=$id";
$row = mysqli_fetch_array ($result);
$variable=$row['?'];
The question mark say, I do not know what column to expect, as it's name could be anything from the tables in the database!
Since I have several tables, the query will zero on a table, but the column names in each table varies so I would like to be able to use one query that can give me an entry from such a column.
I hope my question is clear and that I am not asking for the impossible. If it's ambiguous, I care to elucidate (hope so).
I'm not sure what you mean, but it is possible to reference specifc columns by typing index (starting with 0) something like this: $row[0], $row[1] where 0 indicates the first column, and 1 indicates the second column from the returned recordset.
Example:
If you have a select-statement like this:
SELECT title, author FROM books
You could reference these two columns with $row[0], $row[1]
If you try to get the value of $row[2] you will get an unassigned value because there are only two columns (0 and 1) from the recordset.
If you have a select-statement like this:
SELECT * FROM book_categories
and the recordset returns three columns, then you could access these with $row[0], $row[1] and $row[2]. $row[3] does not exist because there are only three columns (0,1 and 2)
Since you are learning maybe we could take some time to explain why this is possible but many people (including myself) would say this is bad -- or at least dangerous
Why you can
Your SQL query is basically a text string you send to the DB server, which decode that string trying to interpret it as SQL in order to execute the query.
Since all you send to the DB server is text string, you could build that string however you want. Such as using string interpolation as you did:
select * FROM $book_categories WHERE id=$id
That way, you could replace any part of your query by the content of a variable. You could even go further:
$query FROM $book_categories WHERE id=$id
Where $query could by SELECT * or DELETE.
And, why not initializing all those variables from a form:
$book_categories = $_POST['book_categories'];
$id = $_POST['id'];
$query = $_POST['query'];
Great, no? Well, no...
Why you shouldn't
The problem here is "could you trust those variables to only contain acceptable values?". That is, what would append if $book_categories somehow resolve to one table you didn't want to (say myTableContainigSecretData)? And what if $id resolve to some specially crafted value like 1; DELETE * FROM myImportantTable;?
In these conditions, your query:
select * FROM $book_categories WHERE id=$id
Will become as received by the DB server:
select * FROM myTableContainigSecretData WHERE id=1; DELETE * FROM myImportantTable;
Probably not what you want.
What I've tried to demonstrate here is called SQL injection. This is a very common bug in web application.
How to prevent that?
The best way to prevent SQL injection is to use prepared statement to replace some placeholders in your query by values properly shielded against SQL injection. There was an example posted a few minutes ago as a response to an other question: https://stackoverflow.com/a/18035404/2363712
The "problem" regarding your initial question is that will replace values not table or columns identifiers.
If you really want to replace table/columns identifiers (or other non-value part of your query) by variables contents, you will have to check yourself the content of each of these variables in order to prevent SQL injection. This is quite feasible. But that's some work...
Related
I think the answer to this may be simple, but being new to SQL I am still growing. Here's my dilemma. I have a php array of options with 10 values. When any one option is selected it is passed into a variable named "spots". I have 10 SQL SELECT statements that pull 1 of 10 different tables. The issue is that I do not know exactly what to do in order to get the SQL to recognize which value was selected and based on which was selected show that specific table data. (This would be easy if I were able to use the JavaScript Switch statement, but I do not know an equivalent for that)
EXAMPLE:
PHP
$spots = ["Report1","Report2","Report3","Report4","Report5","Report6","Report7","Report8","Report9","Report10"];
SQL
SELECT *, FROM Report5
ORDER BY TW ASC;
Now how do I get SQL to loop through an array to find a match, then depending on that match select from a list of commands (for example like a JavaScript switch statement)?
Use variable substitution:
foreach ($spots as $spot) {
$sql = "SELECT * FROM $spot ORDER BY TW ASC";
// perform the SQL query using $sql, do what you want with the results
}
Make sure you've validated that the values in $spots are valid if they're coming from the user. Otherwise you'll be subjecting your code to SQL injection.
This question already has answers here:
Why is SELECT * considered harmful?
(16 answers)
Closed 9 years ago.
I am developing an application and I was reading about how queries work. I read somewhere that you should avoid SELECT * FROM... where blah = blah
Why is that? And what's the workaround if you're trying to select pretty much everything?
Initially need to know what data you will need. Although, you can select all at once, if such requests will not be much. The difference in performance, you'll see only in heavy projects.
This is not really a direct answer to your question "Why is that?" (so downvote the answer if you need to.) It's an answer to the "what's a workaround if you need to" question.
The only workaround to avoid SELECT *, when I need all of the columns in the table, is to get a list of all the columns. And that's just extra busy I work I don't need when I'm already busy.
To put a backwards twist on a line from Office Space charaacter Peter Gibbons: "The thing is, Bob, it's not that I don't care, it's just that I'm lazy."
With MySQL I make for less busy work by using the SQLyog right click menu option to generate a skeleton SELECT statement that contains all the columns.
For a SQL statement that references multiple tables, I want every column reference to be qualified with a table alias, so I'll just use a SQL statement to retrieve a ready-to-use list of columns for me:
SELECT GROUP_CONCAT(CONCAT('t.',c.column_name)
ORDER BY c.ordinal_position
) AS col_list
FROM information_schema.columns c
WHERE c.table_schema = 'mydatabase'
AND c.table_name = 'mytable'
if I only need a few out a long list, it's easier for me to get them out of a vertical list
SELECT CONCAT(',s.`',c.column_name,'`') AS col_names
FROM information_schema.columns c
WHERE c.table_schema = 'mydatabase'
AND c.table_name = 'mytable'
ORDER BY c.ordinal_position
When the column references are qualified, the backticks are only needed for "special" characters in column names (or maybe some weird case sensitive setting.)
I can start with that list, and whittle out the columns I know I don't need.
Again, I apologize that this doesn't answer the question "Why?" There's several good reasons, given in answers to similar questions. For me, a big reason is that a future reader of the statement isn't going to have to go look somewhere else to find out what columns are being returned. Sure they can copy the statement, and go run it in a different environment, to see the list. But if the statement has variable substitutions, bind variables, and the dots and double quotes and calls to mysql_real_escape_string (in the case of mysql_ interface), that's a bigger hassle than it needs to be. Sure, the code can be modified to echo out the SQL text before its executed, and the reader may need to do that. But someone just reviewing the code shouldn't have to do that. And having the list of columns and expressions being returned by the statement, in an order more appropriate than the ordinal position of the columns in the table, I think that just makes for more readable code. (If it's important for the column to be returned by the statement, then I think it's reasonable that the column name is shown in the query.)
(This was in terms of application code, statements that are going to be included in an application. For ad hoc queries and development and such, I use the SELECT c.* freely. But when a statement is going into an application, that * gets replaced.
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'"
I've always done validation by pulling the entirety of a SQL table into an array, and searching it for the value I was validating. (Is Joe Smith already in my list of users?)
Is it more effective to do a more specific SQL query looking for that value, and returning true or false?
If so, I would like to see a code example, please.
I've been attempting to do this, but keep running into "Unknown column 'variablename' in 'where clause'"
(edit:)
For example sake, here is some code. I'm attempting to query a SQL table for a specific variable (a user). I then just need to know if it did or did not find the user in the table.
//$value = "Joe Smith"
$result = mysql_query("SELECT * FROM usernames WHERE fullname= $value",$db);
$rowcheck = mysql_num_rows($result);
if ($rowcheck > '0') {
// The value was found in the table.
}
Pulling the entire table is a bad idea. If you happen to have a few million rows in your database, good luck validating this in code. Always narrow down the data transfered with a WHERE clause. If you have the correct indexes, the statement will be fast and your frontend code will be simpler.
SELECT id FROM users WHERE name='Joe Smith'
Check if this returns any rows. Concerning your error, I guess you used the wrong quotes (single quotes).
Edit: You need quotes around string literals
mysql_query("SELECT * FROM usernames WHERE fullname='$value'",$db);
Im writing PHP scripts for using with my mySQL database. The only problem i have is binding variables for drop table/ create table and so on.
$stmt = $link->prepare("DROP TABLE ?");
$stmt->bind_param('s','testing');
$stmt->execute();
is not working. I tried also:
SELECT * FROM (SELECT MAX(name) from profiles where name='testing') <- is working
DROP TABLE (SELECT MAX(name) from profiles where name='testing') <- dont work
Binding a parameter is not the same as just replacing a portion of the string : you cannot just bind anything you want.
In this case : you cannot use a bound parameter for a table name -- you'll have to use string concatenations to build your query, instead of using a prepared statement.
As a reference, quoting PREPARE Syntax :
Parameter markers can be used only
where data values should appear, not
for SQL keywords, identifiers, and so
forth.
As far as I know, you can only bind to a parameter, not to any part of a query you want. You're essentially telling the database "hey, I'm going to pass you a value here, and I want you to do your magic to make sure it doesn't overstep its bounds". Things like table names or field names aren't values, they're part of the table structure itself.
In this case, you'll have to just use a use a simple $query = "DROP TABLE " . $table;. It should be easy enough to check against a list of known tables to ensure you're not injecting anything harmful. Anything that makes DDL changes shouldn't be taking input from the user anyway, as far as I'm concerned. These sorts of changes can be based on user input, but the actual construction of the query should be really well known and shouldn't need outside data to construct.
Also, I'm not really sure what you're trying to do with this query:
DROP TABLE (SELECT MAX(name) from profiles where name='testing');
It looks like you might be trying to delete a record, but that's entirely the wrong syntax for that. If you're trying to drop a table whose name comes from the result of another query, I really don't think you can do that either. I'm 99% sure that DROP TABLE expects only a literal table name value.
Are you sure you want to drop tables dynamically?
It is extremely unusual.
It seems you have wrong database design.
And now you faced a consequence.
It seems you should have one table users and delete rows from it, not tables.