I have a database containing a number of tables. I also have a web-based application that is connected to it. For now I am using prepared queries.
I am trying to create a general query that will be able to take a variable number of input columns from different tables. (Basically the user checks boxes that create php variables that I need to pass to the query).
If I know that there will be let say 2 columns needed, it's easy, I only need to do something like,
SELECT $col1, $col2 FROM $table1 INNER JOIN $table2;
How can I change that so that any number of variables can be input in the query?
Thanks in advance,
Zohm
EDIT: I think I could maybe do an if/else condition in PHP or a switch and each case would correspond to a number of input columns, and each case would have its query. But this would do a lot of cases (the user could click 10 boxes for example) and It does not really seems right to do that. Moreover in the future if I need more cases I will have to change the code, which I rather not have to do.
i assume your form elements names are the table fields you are looking for. if this is the case you can loop through the post to build your fields table. Im not sure how you are setting a variable to have 2 values...
$_POST['field1'] = "true";
$_POST['field2'] = "true";
$_POST['field3'] = "false";
$_POST['field4'] = "true";
$fields = "";
foreach ($_POST as $key => $value)
{
$fields .= $key . ",";
}
which will end up with
$fields = "field1,field2,field4,";
now you have to remove that last comma,
$fields = substr($fields,0,-1);
so then you can:
$query = "SELECT " . $fields . " from table1...and so on.
Related
I'm hoping someone can give me a suggestion on a challenge I am facing. I am not sure that I'm able to do this the way I envision, so looking for advice from those more experienced.
I have a database table with around 20 columns. It's a lot of columns and unfortunately I cannot change that. The goal is to take a form submission and insert it into this table. So what I have is, the field names are identical to the column names in the database.
To try and keep the code cleaner, I would like to just pull the entire form (key and value) in, instead of doing the traditional $varWhatever = $_POST['whatever']; 20 times. Using something like this: foreach ($_POST as $key => $value)
Now my question is, if at all possible, how can I run that foreach loop in a way that will let me put the keys and values into a single SQL statement?
"INSERT INTO table_name (Loop all keys here) VALUES (Loop related values here)"
Is this even possible, or should I just go back to the more traditional way I mentioned above?
One way I am thinking is, before starting the loop, I could create the empty row and grab it's ID, then within the loop, I could run an update query on the row matching the ID. Sounds sloppy though.
Here is a solution I came up with. You first have to define an array of field names that acts as a whitelist of expected inputs. Then you just loop through that array to build a parameters array to bind the submitted values. And implode the array with a comma when building the query.
$fields = array('field1','field2','field3');
$binds = array();
foreach ($fields as $field) {
$binds[":$field"] = $_POST[$field];
}
$sql = "INSERT INTO table_name (" . implode(',',$fields) . ") VALUES (" . implode(',',array_keys($binds)) . ")";
$db->prepare($sql);
$db->execute($binds);
This assumes you are using PDO.
Yes, you can loop for all keys (eg. do an array_keys), but I don't recommend blindly taking any submission parameter and putting it into a SQL query.
Instead, I would keep a list of all valid columns of the form and work with that, remembering that each value needs sanitization, too.
For example:
<?php
$columns = array('column1', 'column2', 'column3', …);
foreach ($columns as $column) {
if (!isset($_POST[$column])) {
die("No data for column $column\n");
}
}
if (!check_csrf($_POST['csrt_token'])) { … }
# (setup database connection)
$SQL = "INSERT INTO table_name (" . implode(", ", $columns) . ") VALUES (";
foreach ($column as $column) {
$SQL .= "'" . $mysqli->real_escape_string($_POST[$column]) . "',";
}
$SQL[strlen($SQL)-1] = ')';
$mysqli->query($SQL);
I have checkboxes that represent the condition of a product. When a user checks for example Excellent, the value 1 is stored in a GET-variable like this:
...index.php?condition=1
Now, when the user checks multiple boxes (which has to be possible), it looks like this:
...index.php?condition=1,2,3
Obviously, I have to query my database in order to show the products corresponding to the user's choice(s).
if the user only checks one box, the statement is simple:
if (!empty($_GET['condition'])){
$sql = $sql . " AND (Condition = '".$_GET['condition']."')";
}
But what if the user checks multiple boxes? How would the statement have to look? (is it even possible to solve it this way?
thanks!
This is the script that creates the address:
<script>
$('#condition_select input:checkbox').change(function() {
var condition = $("#condition_select input:checkbox:checked").map(function() {
return this.value;
}).get().join(',');
$("#submit_checkboxes").find("a").attr('href', 'index.php?condition=' + condition);
}).change();
</script>
HTML:
<div class="mutliselect" id="condition_select">
<form method="POST" action="">
<ul>
<li><input type="checkbox" id="d1" value="1"'; if(strpos($_GET['condition'],'1') !== false){ echo 'checked="checked"';} echo'/><label for="d1" class="checkbox_title">Neu</label></li>
<!-- and 6 more of these -->
Is there a way to solve this without having to create a separate GET-variable for each checkbox?
UPDATE (once more updated accrding to comments and questions updates):
Question updated, so I will update my answer to. Use explode (http://php.net/manual/en/function.explode.php), to get all values, and implode to formulate query:
$values = explode(',', $_GET['condition']);
// VALIDATE each of values to match Your type.
if (!empty($values)) {
// $values = [1, 2, 3]
foreach ($values as $key => $value) {
$values[$key] = '(condition = \''.$value.'\')';
}
// $values = ['(condition = \'1\')', '(condition = \'2\')', '(condition = \'3\')']
$query = implode(' OR ', $values);
// $query = '(condition = \'1\') OR (condition = \'2\') OR (condition = \'3\')',
$sql = $sql . ' AND ( '.$query.' )';
}
This is Your solution I think (MYSQL multiples AND: http://forums.mysql.com/read.php?10,250552). Example of explode working:
$string = '1,2,3,4,asd,fgh';
$array = explode(',', $string);
print_r($array[0]); // 1
print_r($array[3]); // 4
print_r($array[5]); // 'fgh'
This is a fragment of answer before edits, but I think it is worth to left it here:
Hoverwer, there are many security holes in it!!!
YOU MUST verify the user data. If someone pass in the address for example:
index.php?condition=; SHOW TABLES FROM database;
It can potentialy show some data. As You can see, user can also go to address with delete statemant, or INSERT INTO users... etc.
So in that case YOU SHOULD switch to PDO (for example) http://php.net/manual/en/pdo.prepare.php, and make prepared statements. Verify user data so if You need the number, he can pass only number.
You can use isset also: isset vs empty vs is_null. It is not a must, but sometimes can be useful. With each form you can also check for isset ($_POST['submit']) as many robots that spam POST forms, sometimes just ommit the submit button. It will decrease the amount of requests I think.
Remember that using POST and GET forms ALWAYS allow user to send his own POST / GET requests. In that case, server side verification is a MUST.
PS. Sorry for capital letters, they are used only to make it really STRONG. Verify user data ;).
Best regards.
I have an array containing the names of form input names:
$minmax = array('bed_min', 'bed_max', 'rec_min', 'rec_max', 'bath_min', 'bath_max', 'value_min', 'value_max');
The names are identical to the corresponding columns in a database. Instead of using an sql query like so:
$bed_min=$_POST['bed_min'];
$bed_max=$_POST['bed_max'];
$rec_min=$_POST['rec_min'];
$rec_max=$_POST['rec_max'];
$bath_min=$_POST['bath_min'];
$bath_max=$_POST['bath_max'];
$value_min=$_POST['value_min'];
$value_max=$_POST['value_max'];
$query = "UPDATE first_page SET bed_min='$bed_min', bed_max='$bed_max', rec_min='$rec_min', rec_max='$rec_max', bath_min='$bath_min', bath_max='$bath_max', value_min='$value_min', value_max='$value_max', WHERE email_address='$email' ";
Is there a way to automate all this into a smaller lump of code? I know the POST values should not be added to the query diectly, so maybe a loop to assign the POST values to a corresponding array of variables using something like:
foreach ($minmax as $var){
$var = $_POST[$var]
}
(nb i dont think this snippet will work but ive added it because I think with a bit of editing it might!)
After the list of variables have been assigned the POST values, do the update in the $query using two arrays, one with the list of values and one with the list of database columns. Again I dont know how this will work, so pointers would be helpful!
You don't really need the $minmax array of form input names since you can get those from the keys of $_POST.
If the values are all numbers, like they seem to be, then you could do it all in one line like this:
$query = "UPDATE first_page SET " . vsprintf(implode("='%d', ", array_keys($sanitized_POST))."='%d'", array_values($sanitized_POST))." WHERE email_address='$email'";
That's assuming you have already sanitized the items from $_POST into a new array named $sanitized_POST. I know you said in the above comment to ignore sanitization, but I thought I'd add it so you know I'm not suggesting to use the values straight from $_POST.
You could sanitize the $_POST array with something like this:
$sanitized_POST = array_map(function($item) {
return mysqli::real_escape_string($item);
}, $_POST);
Honestly though, you should try to come up with a solution that uses prepared statements.
On a side note, if you have the sanitized post array, then this one line will essentially do what Quixrick has done with variable variables in one of the other answers:
extract($sanitized_POST);
If you assume that all of the values in post have the same names (array keys) as your columns, then something like this could work for you:
$query = "UPDATE first_page SET ";
foreach ($_POST as $key => $var){
$query .= " $key='$var',";
}
$query = rtrim($query,',') . " WHERE email_address='$email' ";
You probably want to use 'Variable Variables' here. Basically, you'd use a double dollar sign $$ to create a variable with the name of the array value.
$_POST['bed_min'] = 'Rick';
$minmax = array('bed_min', 'bed_max', 'rec_min', 'rec_max', 'bath_min', 'bath_max', 'value_min', 'value_max');
foreach ($minmax as $var){
$$var = $_POST[$var];
}
print "<BR>BED MIN: ".$bed_min;
This outputs:
BED MIN: Rick
$url = mysql_real_escape_string($_POST['url']);
$shoutcast_url = mysql_real_escape_string($_POST['shoutcast_url']);
$site_name = mysql_real_escape_string($_POST['site_name']);
$site_subtitle = mysql_real_escape_string($_POST['site_subtitle']);
$email_suffix = mysql_real_escape_string($_POST['email_suffix']);
$logo_name = mysql_real_escape_string($_POST['logo_name']);
$twitter_username = mysql_real_escape_string($_POST['twitter_username']);
with all those options in a form, they are pre-filled in (by the database), however users can choose to change them, which updates the original database. Would it be better for me to update all the columns despite the chance that some of the rows have not been updated, or just do an if ($original_db_entry = $possible_new_entry) on each (which would be a query in itself)?
Thanks
I'd say it doesn't really matter either way - the size of the query you send to the server is hardly relevant here, and there is no "last updated" information for columns that would be updated unjustly, so...
By the way, what I like to do when working with such loads of data is create a temporary array.
$fields = array("url", "shoutcast_url", "site_name", "site_subtitle" , ....);
foreach ($fields as $field)
$$field = mysql_real_escape_string($_POST[$field]);
the only thing to be aware of here is that you have to be careful not to put variable names into $fields that would overwrite existing variables.
Update: Col. Shrapnel makes the correct and valid point that using variable variables is not a good practice. While I think it is perfectly acceptable to use variable variables within the scope of a function, it is indeed better not use them at all. The better way to sanitize all incoming fields and have them in a usable form would be:
$sanitized_data = array();
$fields = array("url", "shoutcast_url", "site_name", "site_subtitle" , ....);
foreach ($fields as $field)
$sanizited_data[$field] = mysql_real_escape_string($_POST[$field]);
this will leave you with an array you can work with:
$sanitized_data["url"] = ....
$sanitized_data["shoutcast_url"] = ....
Just run a single query that updates all columns:
UPDATE table SET col1='a', col2='b', col3='c' WHERE id = '5'
I would recommend that you execute the UPDATE with all column values. It'd be less costly than trying to confirm that the value is different than what's currently in the database. And that confirmation would be irrelevant anyway, because the values in the database could change instantly after you check them if someone else updates them.
If you issue an UPDATE against MySQL and the values are identical to values already in the database, the UPDATE will be a no-op. That is, MySQL reports zero rows affected.
MySQL knows not to do unnecessary work during an UPDATE.
If only one column changes, MySQL does need to do work. It only changes the columns that are different, but it still creates a new row version (assuming you're using InnoDB).
And of course there's some small amount of work necessary to actually send the UPDATE statement to the MySQL server so it can compare against the existing row. But typically this takes only hundredths of a millisecond on a modern server.
Yes, it's ok to update every field.
A simple function to produce SET statement:
function dbSet($fields) {
$set='';
foreach ($fields as $field) {
if (isset($_POST[$field])) {
$set.="`$field`='".mysql_real_escape_string($_POST[$field])."', ";
}
}
return substr($set, 0, -2);
}
and usage:
$fields = explode(" ","name surname lastname address zip fax phone");
$query = "UPDATE $table SET ".dbSet($fields)." WHERE id=$id";
I have two dynamic tables (tabx and taby) which are created and maintained through a php interface where columns can be added, deleted, renamed etc.
I want to read all columns simulataneously from the two tables like so;-
select * from tabx,taby where ... ;
I want to be able to tell from the result of the query whether each column came from either tabx or taby - is there a way to force mysql to return fully qualified column names e.g. tabx.col1, tabx.col2, taby.coln etc?
In PHP, you can get the field information from the result, like so (stolen from a project I wrote long ago):
/*
Similar to mysql_fetch_assoc(), this function returns an associative array
given a mysql resource, but prepends the table name (or table alias, if
used in the query) to the column name, effectively namespacing the column
names and allowing SELECTS for column names that would otherwise have collided
when building a row's associative array.
*/
function mysql_fetch_assoc_with_table_names($resource) {
// get a numerically indexed row, which includes all fields, even if their names collide
$row = mysql_fetch_row($resource);
if( ! $row)
return $row;
$result = array();
$size = count($row);
for($i = 0; $i < $size; $i++) {
// now fetch the field information
$info = mysql_fetch_field($resource, $i);
$table = $info->table;
$name = $info->name;
// and make an associative array, where the key is $table.$name
$result["$table.$name"] = $row[$i]; // e.g. $result["user.name"] = "Joe Schmoe";
}
return $result;
}
Then you can use it like this:
$resource = mysql_query("SELECT * FROM user JOIN question USING (user_id)");
while($row = mysql_fetch_assoc_with_table_names($resource)) {
echo $row['question.title'] . ' Asked by ' . $row['user.name'] . "\n";
}
So to answer your question directly, the table name data is always sent by MySQL -- It's up to the client to tell you where each column came from. If you really want MySQL to return each column name unambiguously, you will need to modify your queries to do the aliasing explicitly, like #Shabbyrobe suggested.
select * from tabx tx, taby ty where ... ;
Does:
SELECT tabx.*, taby.* FROM tabx, taby WHERE ...
work?
I'm left wondering what you are trying to accomplish. First of all, adding and removing columns from a table is a strange practice; it implies that the schema of your data is changing at run-time.
Furthermore, to query from the two tables at the same time, there should be some kind of relationship between them. Rows in one table should be correlated in some way with rows of the other table. If this is not the case, you're better off doing two separate SELECT queries.
The answer to your question has already been given: SELECT tablename.* to retrieve all the columns from the given table. This may or may not work correctly if there are columns with the same name in both tables; you should look that up in the documentation.
Could you give us more information on the problem you're trying to solve? I think there's a good chance you're going about this the wrong way.
Leaving aside any questions about why you might want to do this, and why you would want to do a cross join here at all, here's the best way I can come up with off the top of my head.
You could try doing an EXPLAIN on each table and build the select statement programatically from the result. Here's a poor example of a script which will give you a dynamically generated field list with aliases. This will increase the number of queries you perform though as each table in the dynamically generated query will cause an EXPLAIN query to be fired (although this could be mitigated with caching fairly easily).
<?php
$pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));
function aliasFields($pdo, $table, $delim='__') {
$fields = array();
// gotta sanitise the table name - can't do it with prepared statement
$table = preg_replace('/[^A-z0-9_]/', "", $table);
foreach ($pdo->query("EXPLAIN `".$table."`") as $row) {
$fields[] = $table.'.'.$row['Field'].' as '.$table.$delim.$row['Field'];
}
return $fields;
}
$fieldAliases = array_merge(aliasFields($pdo, 'artist'), aliasFields($pdo, 'event'));
$query = 'SELECT '.implode(', ', $fieldAliases).' FROM artist, event';
echo $query;
The result is a query that looks like this, with the table and column name separated by two underscores (or whatever delimeter you like, see the third parameter to aliasFields()):
// ABOVE PROGRAM'S OUTPUT (assuming database exists)
SELECT artist__artist_id, artist__event_id, artist__artist_name, event__event_id, event__event_name FROM artist, event
From there, when you iterate over the results, you can just do an explode on each field name with the same delimeter to get the table name and field name.
John Douthat's answer is much better than the above. It would only be useful if the field metadata was not returned by the database, as PDO threatens may be the case with some drivers.
Here is a simple snippet for how to do what John suggetsted using PDO instead of mysql_*():
<?php
$pdo = new PDO($dsn, $user, $pass, array(PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION));
$query = 'SELECT artist.*, eventartist.* FROM artist, eventartist LIMIT 1';
$stmt = $pdo->prepare($query);
$stmt->execute();
while ($row = $stmt->fetch()) {
foreach ($row as $key=>$value) {
if (is_int($key)) {
$meta = $stmt->getColumnMeta($key);
echo $meta['table'].".".$meta['name']."<br />";
}
}
}