What is wrong with this concatenate statement? - php

I'm working with a MySQL server Server version: 5.6.42-cll-lve - MySQL Community Server (GPL) and I'm having some major issues with CONCAT()
public function get_urls() {
// Create query
$query = "SELECT a.Name, a.PrimaryVersion, e1.URL
FROM " . $this->table . " a
INNER JOIN
FunnelsFlows b ON a.Funnel_ID = b.Funnel_ID
INNER JOIN
BackendFlows c ON b.BackendFlow_ID = c.BackendFlow_ID
INNER JOIN
BackendLevels d ON CONCAT(c.Level, :lv) = d.BackendLevel_ID
LEFT JOIN
BackendPages e1 ON d.Upsell = e1.BackendPage_ID
LEFT JOIN
BackendPages e2 ON d.Downsell1 = e2.BackendPage_ID
LEFT JOIN
BackendPages e3 ON d.Downsell2 = e3.BackendPage_ID
WHERE
a.Funnel_ID = :fid
LIMIT 0,1";
// Prepare statement
$stmt = $this->conn->prepare($query);
// Bind ID
$stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
$stmt->bindValue(':lv', $this->lv, PDO::PARAM_STR);
// Execute query
$stmt->execute();
Running this code throws the following error: PHP Fatal error: Uncaught PDOException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'c.Level' in 'on clause' in ...
The entire database structure you can view here:
https://app.sqldbm.com/MySQL/Share/wBAF2JMRFFSoPPjIPdYZc0GFrngIE8md_DYjF4jNYw0

Delete the quotes (both for column name and for the value):
CONCAT(a.Level, :level)
Also, you use a wrong parameter name:
$stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);
// ^^^^^

Your query cannot work like that. You are trying to concat a field content CONCAT(c.Level, :lv) with a parameter :lv. This results in a string if there were a column Level. However, you rather want to build a dynamic column name like Level1 / Level2 aso.
Unfortunately this cannot be done by parameters. This is owing to the fact, that query plans are prepared. Changing column names at execution time would alter the query plan.
You either need to build dynamic SQL in such a case or write a complex ON clause.
The ON clause:
ON
(
(:lv = 1 AND c.Level1 = d.BackendLevel_ID)
OR
(:lv = 2 AND c.Level2 = d.BackendLevel_ID)
-- and so on
)
Dynamic SQL:
$sql = 'ON (c.Level' . inval($this->lv) . ' = d.BackendLevel_ID)'
Since PHP's intval function is considered to be safe, I would prefer the latter example in order to improve SQL performance.

What about this?
$query = "SELECT a.Name, b.URL
FROM " . $this->table . " a
INNER JOIN
FunnelsFlows b ON CONCAT(a.Level, ':lv') = b.Level_ID
WHERE
a.Funnel_ID = :fid
LIMIT 0,1";
// Prepare statement
$stmt = $this->conn->prepare($query);
// Bind ID
$stmt->bindParam(':fid', $this->fid, PDO::PARAM_INT);
$stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT);
// Execute query
$stmt->execute();
That was a quick response, so I'll explain now.
What I'm seeing it that you want to concatenate a table column with a string, right?
First you had a wrong ident :level which you are trying to bind with the following statement $stmt->bindParam(':lv', $this->lv, PDO::PARAM_INT); so you have to change it with :lv or chenge the binding statement to $stmt->bindParam(':level', $this->lv, PDO::PARAM_INT);.
Second - while you are trying to concatenate a column you have to remove the quotes so it should be CONCAT('a.Level', and if the second operator is a string it should be in single quotes, so the full CONCAT operator should be written as follow CONCAT(a.Level, ':lv')

Related

Query works in phpmyadmin but same query won't return in PHP script

This is NOT a duplicate. None of the already existing threads have the same problem as me.
I have a database that stores athlete performances. It contains sessions, each session has sets, each set has "tables" (such as 4x100m, 12x50m and so on), and each table has times. I also have a table for athletes. Each athlete has an ID, each time links with the athlete through the AthleteID. Every session, set, timetable and time also have each unique IDs, used to link them with each other.
I want to make it so that when passing a session ID, it will return all the athletes that have at least 1 time in that session. I made a page that gets requests and the session ID is passed as GET search data (will make it POST later on). The request system works fine, but the problem is in the query. To do it I used inner joins to connect each table. This is my query (it is not the fastest method, but that's for another thread):
$q = "SET #evID = " . $method['sessID'] . ";";
$q .= "SELECT `athletes`.* FROM `events`
INNER JOIN `sets` ON `sets`.`EventID` = `events`.`EventID`
INNER JOIN `timetables` ON `timetables`.`SetID` = `sets`.`SetID`
INNER JOIN `times` ON `times`.`TableID` = `timetables`.`TableID`
INNER JOIN `athletes` ON `athletes`.`ID` = `times`.`AthleteID`
WHERE `events`.`EventID` = #evID
AND `times`.`TimeID` IN(
SELECT MIN(`TimeID`)
FROM `times`
WHERE `TableID` IN(
SELECT `TableID`
FROM `timetables`
WHERE `SetID` IN(
SELECT `SetID`
FROM `sets`
WHERE `EventID` = #evID
)
)
GROUP BY `AthleteID`
)";
Every single time I ran that in phpmyadmin it returned all the athletes, and the data was correct. However, when I run it in my script, the query value is false (such as if there is an error). I tried debugging like this:
$r = $db -> query($q);
var_dump($q);
var_dump($r);
var_dump($db->error);
The query is returned just fine (only difference is lack of newline characters), and when I copy what's returned in phpmyadmin the data is just the same. The rest however:
bool(false)
string(228) "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'SELECT `athletes`.* FROM `events` INNER JOIN `sets` ON `sets`.`EventID` = `...' at line 1"
Other users with the same problem have not really gone that far to find out if they're wrong, but I have. This post is not a duplicate, and I didn't find any solutions online. Could this be a problem with the amount of queries in a single string? (There is one for setting #evID and one for the actual selection). Please explain the solution and methods kindly as I'm only 13 and still learning...
As #NigelRen has suggested, please use parameterized prepared statement.
Assuming that
$sessionid is storing the value for EventID, and assuming that this variable is of integer type; and
$conn is the connection
Then for Mysqli, you can use:
//$q = "SET #evID = " . $method['sessID'] . ";";
$sql = "SELECT `athletes`.* FROM `events`
INNER JOIN `sets` ON `sets`.`EventID` = `events`.`EventID`
INNER JOIN `timetables` ON `timetables`.`SetID` = `sets`.`SetID`
INNER JOIN `times` ON `times`.`TableID` = `timetables`.`TableID`
INNER JOIN `athletes` ON `athletes`.`ID` = `times`.`AthleteID`
WHERE `events`.`EventID` = ?
AND `times`.`TimeID` IN(
SELECT MIN(`TimeID`)
FROM `times`
WHERE `TableID` IN(
SELECT `TableID`
FROM `timetables`
WHERE `SetID` IN(
SELECT `SetID`
FROM `sets`
WHERE `EventID` = ?
)
)
GROUP BY `AthleteID`
)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $sessionid, $sessionid);
$stmt->execute();
$result = $stmt->get_result(); // get the mysqli result
$row = $result->fetch_assoc(); // fetch data
// do other things you want , such as echo $row['fieldname1'];

PDO changes query

I want to have result of my query as it was before I replaced db connection using PDO. How can I get the query as i t was before I implemented PDO?
This is my query:
$query =
"SELECT
`id_affirmation`,
`affirmation`,
`author`,
`user_rate`,
am.date,
am.time,
hua.date,
hua.time
FROM `affirmation_male` am
JOIN `history_user_affirmation` hua ON am.id_affirmation = ua.affirmation_id
WHERE hua.user_id = '" . $id_user . "'
ORDER BY
STR_TO_DATE(hua.date, '%d-%m-%Y') DESC,
hua.time DESC";
For some reason the result of query when I use PDO is i got date from affirmation_male. Do you know why?
Your query returns two columns that have the same name, hence PDO gets lost when it fetches the results; since each records is represented as an associative array, duplicate keys generate ambiguity (only one key will be retained).
You would need to alias those columns to remove ambiguity:
$query =
"SELECT
`id_affirmation`,
`affirmation`,
`author`,
`user_rate`,
am.date am_date,
am.time am_time,
hua.date AS hua_date,
hua.time AS hua_time
FROM `affirmation_male` am
JOIN `history_user_affirmation` hua ON am.id_affirmation = hua.affirmation_id
WHERE hua.user_id = '" . $id_user . "'
ORDER BY
STR_TO_DATE(hua.date, '%d-%m-%Y') DESC,
hua.time DESC";
Notes:
it would also be a good idea to prefix the first columns in the query with the alias of the table they belong to, as this makes the query more readable (and will avoid conflicts if ever these columns names were available in more than one table coming into play in the query)
you could remove backticks to make the query more readable, as the column and table names that you are quoting do not seem to contain any special characters

pg_query_params is not substituting parameters in the query

I want my application to dynamically change which database and schema name it uses depending on whether it's running on a local machine, staging environment, or production. I thought pg_query_params would be a great choice because you can dynamically substitute things into your queries by parameterizing them. The idea is the schema name will be dynamically inserted into the query. But, pg_query_params() doesn't seem to be substituting the values in the array, causing a query syntax error.
I have verified that the query itself is valid by using pg_query and hardcoding the schema name in versus parameterizing it. When I look at the logs, I see the following error:
PHP Warning: pg_query_params(): Query failed: ERROR: syntax error at or near "$1. Line 2 FROM $1.module__c
So, clearly, pg_query_params isn't actually substituting the parameters in (at least from what I can see)
As a result, pg_query_params() returns false, and breaks the whole page.
The code first gets an array from my databaseInfo() function (it returns an array for the schema name and connection resource. I've verified that this part of my code is working and returning the expected information.
$database = databaseInfo();
if (isset($projSFID)) {
$placeholder = $projSFID;
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module__c
INNER JOIN $1.teproject__c ON $1.module__c.associated_project__c = $1.teproject__c.sfid
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.teproject__c.sfid = $2 AND $1.module__c.published__c=TRUE";
} elseif (isset($trackSFID)) {
$placeholder = $trackSFID;
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module_track_association__c
INNER JOIN $1.module__c ON $1.module_track_association__c.module__c = $1.module__c.sfid
INNER JOIN $1.track__c ON $1.module_track_association__c.track__c = $1.track__c.sfid
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.track__c.sfid = $2
ORDER BY $1.module_track_association__c.navigation_sequence__c";
} else {
$placeholder = '';
$query = "SELECT $1.module__c.name, $1.module__c.module_title__c, $1.module__c.number_of_units__c, $1.module__c.duration_minutes__c, $1.module__c.module_bg_hex__c, $1.module__c.module_description__c, $1.lms_asset__c.lms_asset_url__c
FROM $1.module__c
INNER JOIN $1.lms_asset__c ON $1.module__c.module_image_url__c = $1.lms_asset__c.sfid
WHERE $1.module__c.published__c=TRUE AND $1.module__c.display_on_frontend_filtered_only__c=FALSE $2";
echo $query;
}
$result = pg_query_params($database['connection'], $query, array($database['schema'],$placeholder));
The expected result is that $1 will be the name of the schema and $2 will be the value of the placeholder as determined by the conditional logic.

PHP Prepared Statement variable binding error with subquery

I have a query with a few subqueries like so
SELECT ...
FROM (SELECT ...
FROM ...
GROUP BY ...) as speedLimitCalc INNER JOIN
(SELECT ...
FROM date a INNER JOIN HOURLY_TEST b ON a.[FULL_DAY_DT] = b.DATE
WHERE (b.DATE BETWEEN '".$date_s."' AND '".$date_e."')
AND HOUR BETWEEN ".$time_s." AND ".$time_e."
AND(LKNO BETWEEN '".$lkno_s."' and '".$lkno_e."')
AND RDNO= '".$rdno."'
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (".$dayquery.")
GROUP BY RDNO, LKNO, PRESCRIBED_DIRECTION, CWAY_CODE) as origtable ON ...
,(SELECT ...
FROM [Dim_date]
WHERE (FULL_DAY_DT BETWEEN '".$date_s."' AND '".$date_e."')
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (".$dayquery.") ) as c
ORDER BY ...
where I am inserting variables in the inner query where clause.
I am trying to parametrize this query using odbc_prepare and odbc_execute, however I am running into issues of binding the variables. At present, when I use the following
$result = odbc_prepare($connection, $query);
odbc_execute($result)or die(odbc_error($connection));
to run this query, everything works fine. However, when I try to bind a variable, such as
AND RDNO= ?
...
odbc_execute($result, array($rdno))or die(odbc_error($connection));
I get the following error message.
PHP Warning: odbc_execute() [/phpmanual/function.odbc-execute.html]: SQL error: [Microsoft][ODBC SQL Server Driver]Invalid parameter number, SQL state S1093 in SQLDescribeParameter
My guess is that it's because I'm binding a variable in a subquery, since this procedure works when the Where clause is in the top Select query.
I was wondering whether anyone else has encountered this issue before, and how they solved it? Thanks
Fixed the issue by removing the need for parameters in the subquery by separating the query into multiple queries using temporary tables.
$query = "SELECT ...
INTO ##avgspeedperlink
FROM Date a INNER JOIN HOURLY_TEST ON a.[FULL_DAY_DT] = b.DATE
WHERE (b.DATE BETWEEN ? AND ?)
AND HOUR BETWEEN ? AND ?
AND(LKNO BETWEEN ? and ?)
AND RDNO= ?
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (?,?,?,?,?,?,?)
GROUP BY RDNO, LKNO, PRESCRIBED_DIRECTION, CWAY_CODE";
$result = odbc_prepare($connection, $query);
odbc_execute($result, array($date_s,$date_e,$time_s,$time_e,$lkno_s,$lkno_e,$rdno,$daysanitised[0],$daysanitised[1],$daysanitised[2],$daysanitised[3],$daysanitised[4],$daysanitised[5],$daysanitised[6]))or die(odbc_error($connection));
$query = "SELECT ...
INTO ##daysinperiod
FROM [RISSxplr].[dbo].[Dim_date]
WHERE (FULL_DAY_DT BETWEEN ? AND ?)
AND pub_hol IN (".$pubholquery.")
AND school_hol IN (".$schholquery.")
AND day_no IN (?,?,?,?,?,?,?)";
$result = odbc_prepare($connection, $query);
odbc_execute($result, array($date_s,$date_e,$daysanitised[0],$daysanitised[1],$daysanitised[2],$daysanitised[3],$daysanitised[4],$daysanitised[5],$daysanitised[6]))or die(odbc_error($connection));
$query = "SELECT ...
FROM ##avgspeedperlink, ##daysinperiod
ORDER BY LKNO, OUTBOUND
drop table ##avgspeedperlink
drop table ##daysinperiod";
Note that I had to use double ## for making the temporary tables (single # means that table is local to the query, ## means that the temporary table becomes global for multiple queries).

SQL left join from a mix of tables and columns throwing a "SQLSTATE[42000]: Syntax error or access violation 8120" error

this is my query:
$sql = "SELECT sum(CommitmentItemInvoices.Amount) AS PendTotal, CommitmentItemInvoices.Amount AS Amount_Pending , CommitmentItems.*, CommitmentItemInvoices.*
FROM CommitmentItemInvoices
LEFT JOIN CommitmentItems on CommitmentItems.commitmentItemId = CommitmentItemInvoices.commitmentItemId
WHERE (CommitmentItemInvoices.Status='new' AND CommitmentItemInvoices.commitmentItemId = :commitId)";
try{
//prepare statement
$stmt = $con->prepare ($sql);
//bind values to :username
$stmt->bindValue("commitId", $commitId, PDO::PARAM_STR);
$stmt->execute();
}catch(PDOException $e){
echo "Error: ".$e->getMessage();
}
I am getting the following error:
Error: SQLSTATE[42000]: Syntax error or access violation: 8120 [Microsoft][ODBC SQL Server Driver][SQL Server]Column 'CommitmentItemInvoices.Amount' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. (SQLExecute[8120] at ext\pdo_odbc\odbc_stmt.c:254)
Any idea as to what I am doing wrong in the SQL query?
You need a GROUP BY statement to allow the aggregate to work:
SELECT sum(cii.Amount) AS PendTotal
FROM CommitmentItemInvoices cii
LEFT JOIN CommitmentItems ci on
ci.commitmentItemId = cii.commitmentItemId
WHERE (cii.Status='new'
AND cii.commitmentItemId = :commitId)
#xQbert makes an excellent suggestion. Pull out all the fields and you'll see that the query works. Then add them one by one into the select and group by portion of your query. You'll see how the aggregation works.
The * in your query doesnt work because the sql engine needs to know how to aggregate your sum. You can aggregate by many fields, but they each need to be specified in the select AND in the group by.
You will likely get different results as you add more fields to group by.
Read up on GROUP BY to determine which fields you want to group to get the correct aggregation.

Categories