Why is this SQL statement being read incorrectly? - mysql

Why is this SQL Statement
$array = $wpdb->get_results('SELECT * FROM wp_before_after WHERE patient = '.$patientName );
Generating this error?
WordPress database error: [Unknown column 'sarah' in 'where clause']
SELECT * FROM wp_before_after WHERE patient = sarah-jordon
It's like it's swapping round 'patient' and 'sarah-jordon', and thinking sarah-jordon is a column in the database.

You are missing quotes around your value.
$array = $wpdb->get_results('SELECT * FROM wp_before_after WHERE patient = "'.$patientName . '"');
But it will be more robust if you use a parameterized query.
Edit
I checked quickly in wordpress reference, and they have a prepare method

While A.D.'s answer is correct...
$array = $wpdb->get_results('SELECT * FROM wp_before_after WHERE patient = "'.$patientName . '"');
... and makes mention that the OP example is not really robust/secure (vulnerable to SQL injections) I thought it would be worthwhile to post an example that is secure using the prepare statement:
// Usage: $wpdb->prepare( 'query' [, value_parameter, value_parameter ... ] );
// Example:
$patient_name = .$patientName;
$patient = $wpdb->get_var(
$wpdb->prepare( "SELECT * FROM wp_before_after WHERE patient = %d", $patient_name ));
Documentation can be found here
The reason for using prepare is it prevents SQL Injection Attacks on queries that take parameters. For example, in the OP example, if someone were to enter..
sarah; DROP TABLE wp_before_after
or maybe less insidiously:
sarah OR 1=1
.. into the Patient Name field, that SQL would be executed and presumably drop your wp_before_after table or return all of the records in your patient table.
The prepare method SQL escapes the values prior to executing the query -- and that prevents your variables/parameters from being potentially read as SQL. It's basically saying "hey, make sure you read these as values, not part of the query."
As a general rule of thumb, you want to use prepare in all circumstances where a query takes user input as a parameter. You do not want to use prepare in circumstances where no user input is needed -- for example, getting all patients with a first name starting with 's'.

Related

JOIN two SELECTs with Doctrine

I need to write this query with Doctrine. How can I write it down using QueryBuilder?
SELECT charges.id, charges.currency, charges.total_transactions,
charges.total_volume, charges.commission, refunds.total_payouts
FROM
(SELECT ...very long query...) charges
LEFT JOIN
(SELECT ...very long query...) refunds
ON charges.id = refunds.id AND charges.currency = refunds.currency
You can use Native SQL and map results to entities:
use Doctrine\ORM\Query\ResultSetMapping;
$rsm = new ResultSetMapping;
$rsm->addEntityResult('AppBundle:Charges', 'charges')
->addEntityResult('AppBundle:Refunds', 'refunds')
->addFieldResult('charges', 'id', 'id')
->addFieldResult('charges', 'currency', 'currency')
->addFieldResult('charges', 'total_transactions', 'total_transactions')
->addFieldResult('charges', 'total_volume', 'total_volume')
->addFieldResult('charges', 'commission', 'commission')
->addFieldResult('refunds', 'total_payouts', 'total_payouts')
;
$sql = "
SELECT
charges.id,
charges.currency,
charges.total_transactions,
charges.total_volume,
charges.commission,
refunds.total_payouts
FROM
(SELECT ...very long query...) charges
LEFT JOIN
(SELECT ...very long query...) refunds ON charges.id = refunds.id AND charges.currency = refunds.currency
WHERE some_field = ?
";
$query = $this->getEntityManager()->createNativeQuery($sql, $rsm);
$query->setParameter(1, $name);
$entities = $query->getResult();
You can use DQL like this:
$dql = "SELECT ...";
$q = $entityManager->createQuery($dql)->setParameters($arrayParameters);
$result = $q->execute();
or QueryBuilder for each sub-query, like:
// subquery 1
$subQuery1 = $entityManager->createQueryBuilder()
->select('...')
->from('...')
->getDQL()
;
// subquery 2
$subQuery2 = ...
// etc
// ...
// main query
$query = $entityManager->createQueryBuilder()
->select('...')
->from('...', $subQuery1)
->leftJoin('...', $subQuery1->getDQL()),
->where()
;
PS: I just try provide gist for you... hope now you have clue...
Now I found out that it's impossible.
Comment created by stof:
DQL is about querying objects. Supporting subselects in the FROM clause means that the DQL parser is not able to build the result set mapping anymore (as the fields returned by the subquery may not match the object anymore).
This is why it cannot be supported (supporting it only for the case you run the query without the hydration is a no-go IMO as it would mean that the query parsing needs to be dependant of the execution mode).
In your case, the best solution is probably to run a SQL query instead (as you are getting a scalar, you don't need the ORM hydration anyway)
Source: https://github.com/doctrine/doctrine2/issues/3542

how to get latest date from two conditions?

I have drop down where the drop down is list of namaJabatan
my table - infojawatan
ID - PK of the table
namaJabatan - where the condition appear ($search - its up to where the user select from Dropdown)
tarikhKemaskini - where i want to get the latest date of row
my query
$sql = "SELECT * FROM infojawatan WHERE namaJabatan = '$search' && tarikh Kemaskini IN (SELECT MAX(tarikhKemaskini) FROM infojawatan GROUP BY ID)";
$sql_rs = mysql_query($sql);
while($row_Sql = mysql_fetch_array($sql_rs)) {
$tarikhKemaskini = $row_Sql['tarikhKemaskini'];
}
echo "Current Date :" .$tarikhKemaskini;
You have a few syntax errors in your SQL.
SQL spells out AND, not &&.
tarikhKemaskini is one word.
SELECT *
FROM infojawatan
WHERE namaJabatan = :namaJabatan AND
tarikhKemaskini IN (
SELECT MAX(tarikhKemaskini)
FROM infojawatan
GROUP BY ID
)
Note carefully that I used :namaJabatan there instead of hard coding $search. Hard coding variables into SQL leaves you open to a SQL Injection Attack where a malicious attacker can craft a search query that lets them get more information than they're allowed to, or even run arbitrary SQL queries.
Instead, use parameters, the :namaJabatan there, and pass your variables in when you execute the query.
Unfortunately the mysql_query interface doesn't support this. Fortunately it was deprecated and there are now better interfaces. Here's a breakdown. I'd recommend using PDO as it is a generic interface applicable to any SQL database. Then you can use the more secure and efficient prepared statements with bind parameters.
$stmt = $dbh->prepare("
SELECT *
FROM infojawatan
WHERE namaJabatan = :namaJabatan AND
tarikhKemaskini IN (
SELECT MAX(tarikhKemaskini)
FROM infojawatan
GROUP BY ID
)
")
$stmt->execute(array( ':namaJabatan' => $search));
while( $row = $stmt->fetch() ) {
echo $row['tarikhKemaskini'];
}

Is there a more efficient way to write this MySQL query?

I'm a newbie to mysql, I managed to scrape this together to get the result I wanted. Can it be coded better? Are there any security risks? Its being output in php.
$qwe = $product->virtuemart_product_id;
$id = mysql_real_escape_string($qwe);
$result = mysql_query('SELECT * FROM virtuemart_product_medias where virtuemart_product_id = ' . $id . ' LIMIT 1');
$row = mysql_fetch_assoc($result);
$matched = $row['virtuemart_media_id'];
$result2 = mysql_query('SELECT * FROM virtuemart_medias where virtuemart_media_id = ' . $matched . ' LIMIT 1');
$row2 = mysql_fetch_assoc($result2);
$matched2 = $row2['file_url_thumb'];
echo $matched2;
I don't know whether or not there is a security hole in the specific code you provided - that depends on what other validation exists elsewhere in your program, and what you consider to be a security hole. But the way you are coding means that there definitely could be security holes. Let's look at your first query:
$id = mysql_real_escape_string($qwe);
$result = mysql_query('SELECT *
FROM virtuemart_product_medias
WHERE virtuemart_product_id = ' . $id . ' LIMIT 1');
Imagine if $qwe is the string 0 OR 1=1 --. The mysql_real_escape_string only escapes certain characters such as quotes and backslashes.
mysql_real_escape_string() calls MySQL's library function mysql_real_escape_string, which prepends backslashes to the following characters: \x00, \n, \r, \\, ', " and \x1a.
The string 0 OR 1=1 -- that I mentioned above does not contain any of these characters so it will not be affected at all by mysql_real_escape_string. After you substitute in the value of $id, the resulting SQL query will look something like this:
SELECT *
FROM virtuemart_product_medias
WHERE virtuemart_product_id = 0 OR 1=1 -- LIMIT 1
As you can see, this will return all rows.
Long story short: Use PDO and parameterized queries.
Related
How can I prevent SQL injection in PHP?
Firstly, never use the mysql_* functions. They are deprecated and relying on them is highly discouraged. Use either MySQLi or PDO
The above query could be rewritten as
SELECT file_url_thumb FROM virtuemart_medias where virtuemart_media_id = (SELECT virtuemart_media_id FROM virtuemart_product_medias where virtuemart_product_id = ' . $id . ' LIMIT 1) LIMIT 1
Never do a SELECT *. Include only those fields in your query which you need in your code.
Use one query instead of two, and select only the fields you're using, like so:
SELECT `file_url_thumb` FROM virtuemart_medias where virtuemart_media_id = (SELECT `virtuemart_media_id` FROM virtuemart_product_medias where virtuemart_product_id = ' . $id . ' LIMIT 1) LIMIT 1
You can always use a join;
SELECT a.virtuemart_media_id, b.file_url_thumb
FROM virtuemart_product_medias a
LEFT JOIN virtuemart_medias b
ON a.virtuemart_media_id = b.virtuemart_media_id
WHERE virtuemart_product_id = $id
LIMIT 1
That'll always get you the virtuemart_media_id and, if it exists file_url_thumb.
Your query has a problem also, mysql_real_escape_string only escapes strings, since you're not quoting the $id in the query, it won't be handled as a string and the escaping will not help you. As other replies point out, you should really be using mysqli or PDO.
How about this:
SELECT a.file_url_thumb
FROM virtuemart_medias a
LEFT JOIN virtuemart_product_medias b on a.virtuemart_media_id=b.irtuemart_media_id
WHERE a.virtuemart_product_id=' . $id . ' LIMIT 1

how to translate a very long mysql query with select and join to zend framework 1.11 model

I have this mysql query:
SELECT
freeAnswers.*,
(SELECT `districtCode`
FROM `geodatas`
WHERE `zipCode` = clients.zipCode
GROUP BY `zipCode`
LIMIT 0, 1) as districtCode,
clients.zipCode,
clients.gender,
clients.startAge,
clients.endAge,
clients.mail,
clients.facebook,
surveys.customerId,
surveys.activityId,
surveys.name as surveyName,
customers.companyName,
activities.name as activityName
FROM freeAnswers,
clients,
surveys,
customers,
activities
WHERE freeAnswers.surveyId = surveys.id
AND surveys.customerId = customers.id
AND activities.id = surveys.activityId
AND clients.id = freeAnswers.clientId
AND customers.id = 1
ORDER BY activityName asc
LIMIT 0, 10
the query is correct on my mysql server but when I try to use it in Zend Framework 1.11 model
I get this error: Mysqli prepare error: Operand should contain 1 column(s)
Please, could anyone help me to make it run well?
Best Regards,
Elaidon
Here is some code that should work. Zend_Db_Select doesn't really provide a way to select from multiple tables in the FROM clause without using a JOIN so this feels a bit hackish to me in regards to one small part of the query. Your best bet will probably be to rewrite the query using JOINs where appropriate.
$subselect = $db->select()
->from('geodatas', 'districtCode')
->where('zipCode = clients.zipCode')
->group('zipCode')
->limit(1, 0);
$from = $db->quoteIdentifier('freeAnswers') . ', ' .
$db->quoteIdentifier('clients') . ', ' .
$db->quoteIdentifier('surveys') . ', ' .
$db->quoteIdentifier('customers') . ', ' .
$db->quoteIdentifier('activities');
$select = $db->select()
->from(array('activities' => new Zend_Db_Expr($from)),
array('freeanswers.*',
'districtCode' =>
new Zend_Db_Expr('(' . $subselect . ')'),
'clients.zipCode', 'clients.gender', 'clients.startAge',
'clients.endAge', 'clients.mail', 'clients.facebook',
'clients.customerId', 'clients.activityId',
'surveyName' => 'surveys.name', 'customers.companyName',
'activityName' => 'activities.name'))
->where('freeAnswers.surveyId = surveys.id')
->where('surveys.customerId = customers.id')
->where('activities.id = surveys.activityId')
->where('clients.id = freeAnswers.clientId')
->where('customers.id = ?', 1)
->order('activityName ASC')
->limit(10, 0);
The only reason I say it is hackish is because of the line:
->from(array('activities' => new Zend_Db_Expr($from)),
Since from() really only works with one table, I create a Zend_Db_Expr and specify the correlation as the last table name in the expression. If you don't pass a Zend_Db_Expr, it will either quote your comma separated table name incorrectly, or if you pass an array of table names, it just uses the first. When you pass a Zend_Db_Expr with no name, it defaults to use AS t which also doesn't work in your case. That is why I put it as is.
That returns the exact SQL you provided except for the last thing mentioned. Here is actually what it returns:
SELECT
`freeanswers`.*,
(SELECT `geodatas`.`districtCode`
FROM `geodatas`
WHERE (zipCode = clients.zipCode)
GROUP BY `zipCode`
LIMIT 1) AS `districtCode`,
`clients`.`zipCode`,
`clients`.`gender`,
`clients`.`startAge`,
`clients`.`endAge`,
`clients`.`mail`,
`clients`.`facebook`,
`clients`.`customerId`,
`clients`.`activityId`,
`surveys`.`name` AS `surveyName`,
`customers`.`companyName`,
`activities`.`name` AS `activityName`
FROM `freeAnswers`,
`clients`,
`surveys`,
`customers`,
`activities` AS `activities`
WHERE (freeAnswers.surveyId = surveys.id)
AND (surveys.customerId = customers.id)
AND (activities.id = surveys.activityId)
AND (clients.id = freeAnswers.clientId)
AND (customers.id = 1)
ORDER BY `activityName` ASC
LIMIT 10
So that will work but eventually you will want to rewrite it using JOIN instead of specifying most of the WHERE clauses.
When dealing with subqueries and Zend_Db_Select, I find it easy to write each subquery as their own queries before writing the final query, and just insert the subqueries where they need to go and Zend_Db handles the rest.
Hope that helps.

MYSQL: How to copy an entire row from one table to another in mysql with the second table having one extra column?

I have two tables with identical structure except for one column... Table 2 has an additional column in which I would insert the CURRENT_DATE()
I would like to copy all the values from table1 to table2.
If I use
INSERT INTO dues_storage SELECT * FROM dues WHERE id=5;
it throws an error pointing to the difference in the number of columns.
I have two questions:
How do I get around this?
How do I add the value for the additional date column (CURRENT_DATE()) in table2 within this same statement?
To refine the answer from Zed, and to answer your comment:
INSERT INTO dues_storage
SELECT d.*, CURRENT_DATE()
FROM dues d
WHERE id = 5;
See T.J. Crowder's comment
The safest way to do it is to fully specify the columns both for insertion and extraction. There's no guarantee (to the application) that either of these will be the order you think they may be.
insert into dues_storage (f1, f2, f3, cd)
select f1, f2, f3, current_date() from dues where id = 5;
If you're worried about having to change many multiple PHP pages that do this (as you seem to indicate in the comment to another answer), this is ripe for a stored procedure. That way, all your PHP pages simply call the stored procedure with (for example) just the ID to copy and it controls the actual copy process. That way, there's only one place where you need to maintain the code, and, in my opinion, the DBMS is the right place to do it.
INSERT INTO dues_storage
SELECT field1, field2, ..., fieldN, CURRENT_DATE()
FROM dues
WHERE id = 5;
Hope this will help someone... Here's a little PHP script I wrote in case you need to copy some columns but not others, and/or the columns are not in the same order on both tables. As long as the columns are named the same, this will work. So if table A has [userid, handle, something] and tableB has [userID, handle, timestamp], then you'd "SELECT userID, handle, NOW() as timestamp FROM tableA", then get the result of that, and pass the result as the first parameter to this function ($z). $toTable is a string name for the table you're copying to, and $link_identifier is the db you're copying to. This is relatively fast for small sets of data. Not suggested that you try to move more than a few thousand rows at a time this way in a production setting. I use this primarily to back up data collected during a session when a user logs out, and then immediately clear the data from the live db to keep it slim.
function mysql_multirow_copy($z,$toTable,$link_identifier) {
$fields = "";
for ($i=0;$i<mysql_num_fields($z);$i++) {
if ($i>0) {
$fields .= ",";
}
$fields .= mysql_field_name($z,$i);
}
$q = "INSERT INTO $toTable ($fields) VALUES";
$c = 0;
mysql_data_seek($z,0); //critical reset in case $z has been parsed beforehand. !
while ($a = mysql_fetch_assoc($z)) {
foreach ($a as $key=>$as) {
$a[$key] = addslashes($as);
next ($a);
}
if ($c>0) {
$q .= ",";
}
$q .= "('".implode(array_values($a),"','")."')";
$c++;
}
$q .= ";";
$z = mysql_query($q,$link_identifier);
return ($q);
}
Alternatively, you can use Inner Queries to do so.
SQL> INSERT INTO <NEW_TABLE> SELECT * FROM CUSTOMERS WHERE ID IN (SELECT ID FROM <OLD_TABLE>);
Hope this helps!
SET #sql =
CONCAT( 'INSERT INTO <table_name> (',
(
SELECT GROUP_CONCAT( CONCAT('`',COLUMN_NAME,'`') )
FROM information_schema.columns
WHERE table_schema = <database_name>
AND table_name = <table_name>
AND column_name NOT IN ('id')
), ') SELECT ',
(
SELECT GROUP_CONCAT(CONCAT('`',COLUMN_NAME,'`'))
FROM information_schema.columns
WHERE table_schema = <database_name>
AND table_name = <table_source_name>
AND column_name NOT IN ('id')
),' from <table_source_name> WHERE <testcolumn> = <testvalue>' );
PREPARE stmt1 FROM #sql;
execute stmt1;
Of course replace <> values with real values, and watch your quotes.
Just wanted to add this little snippet which works beautifully for me.
INSERT INTO your_target_table
SELECT *
FROM your_rescource_table
WHERE id = 18;
And while I'm at it give a big shout out to Sequel Pro, if you're not using it I highly recommend downloading it...makes life so much easier