How do I insert a mysql spatial point with a yii model? - mysql

I have a model type that was generated from a mysql table that has address data and also a spatial POINT field named "coordinates". When a model is created or updated I want to geocode the address and store the latitude and longitude coordinates in the POINT field.
My understanding is the way to do this is to geocode the address in the model's beforeSave method. I have done this and have the coordinates in an associative array. Now my question is how can I insert this data into my coordinates field? This is what I'm trying:
public function beforeSave()
{
$singleLineAddress = $this->getSingleLineAddress();
$coords = Geocoder::getCoordinates($singleLineAddress);
// WORKS: using the following line works to insert POINT(0 0)
//$this->coordinates = new CDbExpression("GeomFromText('POINT(0 0)')");
// DOESN'T WORK: using the following line gives an error
$this->coordinates = new CDbExpression("GeomFromText('POINT(:lat :lng)')",
array(':lat' => $coords['lat'], ':lng' => $coords['lng'] ));
return parent::beforeSave();
}
When I do this I get the following error:
CDbCommand failed to execute the SQL statement: SQLSTATE[HY093]:
Invalid parameter number: number of bound variables does not match
number of tokens. The SQL statement executed was: INSERT INTO place
(city, state, name, street, postal_code, phone, created,
coordinates) VALUES (:yp0, :yp1, :yp2, :yp3, :yp4, :yp5,
UTC_TIMESTAMP(), GeomFromText('POINT(:lat :lng)'))

Small edit in #dlnGd0nG 's answer if you are using Yii 2
$this->coordinates = new yii\db\Expression("GeomFromText(:point)",
array(':point'=>'POINT('.$coords['lat'].' '.$coords['lng'].')'));

Try this instead
$this->coordinates = new CDbExpression("GeomFromText(:point)",
array(':point'=>'POINT('.$coords['lat'].' '.$coords['lng'].')'));

Related

SimpleJdbcCall for MySql Function yields "Can't set IN parameter for return value of stored function call"

Using the example from the Spring docs, I'm trying to return a value from a mySQL function. I keep getting the error Can't set IN parameter for return value of stored function call;.
I created a mySQL function that works fine (ran in MySQL Workbench). I've written a SimpleJdbcCall statement, set up the parameters as per Spring docs example but consistently get this error. If I turn the function into a procedure, the code works, I just have to retrieve the return value from the result set.
I used https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch13s05.html, section 13.5.8 as reference.
CREATE FUNCTION `ScenarioRegistration`(
environment VARCHAR(45),
username VARCHAR(15),
scenario_name VARCHAR(45)) RETURNS int(11)
A couple of SELECT statements followed by an INSERT then
RETURN scenario_id; // The inserted id
Java code:
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(getJdbcTemplate())
.withFunctionName("ScenarioRegistration")
.withoutProcedureColumnMetaDataAccess();
simpleJdbcCall.addDeclaredParameter(new SqlParameter("environment"
,Types.VARCHAR));
simpleJdbcCall.addDeclaredParameter(new SqlParameter("username"
,Types.VARCHAR));
simpleJdbcCall.addDeclaredParameter(new SqlParameter("scenario_name"
,Types.VARCHAR));
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("environment", environment)
.addValue("username", username)
.addValue("scenario_name", scenario);
simpleJdbcCall.setReturnValueRequired(true);
Integer scenario_id = simpleJdbcCall.executeFunction(
Integer.class, parameters);
All I want the routine to do is give me back the id of the newly inserted scenario.
What I get is:
SQL [{? = call scenarioregistration(?, ?)}]; Can't set IN parameter for return value of stored function call.
I find it interesting that it's taken my THREE input values and changed them to an output and TWO input values.
Anyone enlighten me as to the problem and how to fix it?
Thanks,
Steven.
I would refer to the latest docs here for your answer. It appears Spring is trying to infer the output because you didn't explicity specify one.
Per the docs above there are two valid approaches on calling the desired function with the SimpleJdbcCall:
Inferred Parameters
Because you've specified withoutProcedureColumnMetaDataAccess, Spring isn't going to look and see what the ins/outs are to your function. If you want it easy, just don't specify that and you should be able to do:
SqlParameterSource parameters = new MapSqlParameterSource()
.addValue("environment", environment)
.addValue("username", username)
.addValue("scenario_name", scenario);
Integer scenarioId = new SimpleJdbcCall(getJdbcTemplate())
.withFunctionName("ScenarioRegistration")
.executeFunction(Integer.class, parameters);
Explicit Parameters
If you want to keep withoutProcedureColumnMetaDataAccess turned off for whatever reason, you can do:
Integer scenarioId = new SimpleJdbcCall(getJdbcTemplate)
.withFunctionName("ScenarioRegistration")
.withoutProcedureColumnMetaDataAccess()
.useInParameterNames("environment", "username", "scenario_name")
.declareParameters(
new SqlOutParameter("scenario_id", Types.NUMERIC),
new SqlParameter("environment", Types.VARCHAR),
new SqlParameter("username", Types.VARCHAR),
new SqlParameter("scenario_name", Types.VARCHAR)
).executeFunction(Integer.class, parameters);
Note: It appears that order is critical in this example. The output parameter should be declared first, and the subsequent named IN parameters come last. That is, the order of the parameters ? are ordinal in [{? = call scenarioregistration(?, ?, ?)}])
Alternative NamedParameterJdbcTemplate Solution
Another way to invoke your function is via an actual JDBC call. This could hypothetically save you the grief of using the fine tuning of the SimpleJdbcCall.
Integer scenarioId = namedParameterJdbcTemplate.queryForObject(
"SELECT ScenarioRegistration(:environment, :username, :scenario_name)",
parameters,
Integer.class);

How to deal with Parsing Object from Join Query

I'm currently working on my first API with the Perfect framework. It's been a while since I made an API myself so I must admit my SQL and API logic is a little rusty.
I'm using a MySQL database for my implementation.
For sake of example I'll explain my database structure below;
I have a table which resembles an Object, let's call this Table A. Table A has a Varchar based id as primary key.
There are 2 other tables let's call them Table B and Table C. Table A has a one to many relation to both Table B and C. Where the id of table A is the foreign key.
What I'm trying to do is obtain everything with one query and cast it to an object in my backend.
By using outer joins I'm making the call to retrieve all the required data.
SELECT control.id, control.type, control.description, control.address, control.city, control.created, control.updated, control.latitude, control.longitude, images.id AS image_id, images.image, images.description AS image_description, updates.id AS update_id, updates.still_present, updates.created_at AS update_created
FROM Control control left outer join control_images images
ON control.id=images.control_id
left outer join Control_Updates updates
ON control.id=updates.control_id
Now is my question what would be the best way to store this data in an object that holds an array of updates and an array of images.
Before writing the join query I only attempted to get the values from Table A I used the following code to cast the results to my desired object.
let result = mysql.storeResults()
let checkResult = self.checkResult(result: result, response: response)
response = checkResult.response
var controls: [Control] = []
while let row = result?.next() {
let type = Types(rawValue: row[1].unwrap)!
let control = Control(id: row[0].unwrap, type: type, description: row[2].unwrap, address: row[3].unwrap, city: row[4].unwrap, latitude: Double(row[7].unwrap).unwrap, longitude: Double(row[8].unwrap).unwrap)
controls.append(control)
}
obviously this will just return duplicate objects apart from the images and updates of course.
I'm wondering if this is the best way to do it or if I should call a new query in the while loop
The best way to resolve this issue, by still only using one query and one loop is by using 'hashmaps'. I'm not familiar with Perfect framework, but in PHP it would look something like:
// Get results from the db:
$results = $db->execute($query, $params);
// Define map for controls:
$map = [];
// Loop over results/rows
foreach($results as $row){
// Get unique identifier for the Control model:
$controlId = $row['id'];
// Check if control is NOT already in map:
if(!isset($map[$controlId]){
// Add control to map:
$control = [
'id' => $controlId,
'description' => $row['description'],
'images' => []
// other fields
];
// Add control to map:
$map[$controlId] = $control;
}
else{
// Control exists, retrieve it from the map:
$control = $map[$controlId];
}
// Retrieve unique identifier of the image:
$imageId = $row['image_id'];
// Same tactic with hasmap, check if control already has the image, if not add it
if(!isset($control['images'][$imageId]){
// add the image to the hashmap:
}
else{
// Image is already added, the content from the 'update' data is not added yet, handle that part (also with a hash map)
}
}
Hope that helps you figure it out in Perfect framework

Insert query failing when using a parameter in the associated select statement in SQL Server CE

INSERT INTO voucher (voucher_no, account, party_name, rece_amt, particulars, voucher_date, voucher_type, cuid, cdt)
SELECT voucher_rec_no, #account, #party_name, #rece_amt, #particulars, #voucher_date, #voucher_type, #cuid, #cdt
FROM auto_number
WHERE (auto_no = 1)
Error:
A parameter is not allowed in this location. Ensure that the '#' sign is in a valid location or that parameters are valid at all in this SQL statement.
I've just stumbled upon this whilst trying to fix the same issue. I know it's late but, assuming that you're getting this error when attempting to execute the query via .net, ensure that you are setting the SqlCeParameter.DbType - if this is not specified, you get the exception you listed above.
Example (assume cmd is a SqlCeCommand - all the stuff is in the System.Data.SqlServerCe namespace):
SqlCeParameter param = new SqlCeParameter();
param.ParameterName = "#SomeParameterName";
param.Direction = ParameterDirection.Input;
param.DbType = DbType.String; // this is the important bit to avoid the exception
param.Value = kvp.Value;
cmd.Parameters.Add(param);
Obviously, you'd want to set the DB type to match the type of your parameter.

pdo binding parameters in loop

I have following sql statement:
$sql = "UPDATE houses SET title=:title ";
which I dynamically edit according to object "location", which could have several paramaters (some of them could be null, thefore they are omitted)
//are there any location parameters which need to be added to query?
if (isset($house->location)){
//go through them and add them to query
foreach ($house->location as $key=>$locationParameter) {
$sql.=','.$key.'=:'.$key;
}
//finish query
$sql.=" WHERE id=:id";
$stmt = $db->prepare($sql);
//bind all defined location parameters to query
foreach ($house->location as $key=>$locationParameter) {
$stmt->bindParam($key, $locationParameter);
}
} else {
//there are none location parameters, so prepare just the original query with title and id
$sql.=" WHERE id=:id";
$stmt = $db->prepare($sql);
}
//and finally bind the required parameters
$stmt->bindParam("title", $house->title);
$stmt->bindParam("id", $id);
$stmt->execute();
When I echoed the query (echo $sql) it looked just as I want and also all binded parameters were right, BUT when I run the query all database columns for location parameters are updated just with the last value from location object, for example:
$house->location->lat is 25.5
$house->location->lon is 28.755
$house->location->city is munich
After execution of query with this object, the columns in DB for lat, lon, and city are all filled with "munich".
Could you tell me, what am I doing wrong?
+var_dump($sql) ->
string 'UPDATE houses SET title=:title,lat=:lat,lon=:lon,city=:city WHERE id=:id'
without reading entire question though, just caught my eye
the columns in DB for lat, lon, and city are all filled with "munich".
quoting from PDO tag wiki:
If you don't know if you need bindValue() or bindParam(), go for the former. bindValue() is less ambiguous and has lesser side effects.
most likely a cause.

Invalid parameter number: number of bound variables does not match number of tokens

I have a table:
'objects' with few columns:
object_id:int, object_type:int, object_status:int, object_lati:float, object_long:float
My query is :
$stmt = $db->query('SELECT o.object_id, o.object_type, o.object_status, o.object_lati, o.object_long FROM objects o WHERE o.object_id = 1');
$res = $stmt->fetch();
PDO throws an error:
SQLSTATE[HY093]: Invalid parameter number: number of bound variables does not match number of tokens
When I remove column object_lati or object_long query is work fine.
Although this particular question is not a real one, as the code provided will never produce an error like this, it seems that Google is taking delight in sending visitors to this page. For whom is the answer that follows:
This problem can never be caused by query() method call, as it essentially belongs to prepared statement.
The error itself is pretty clear: "number of tokens" stands for the number of ? or :name tokens in the query (which also called "placeholders"), while "number of variables bound" stands for the variables that were ether bound via bindValue or `bindParam, or sent via execute (which is technically the same). So, when one tries to bind more variables than there were tokens defined in the query, PDO raises this error.
For example if there is a query prepared with no tokens defined, but we are trying to bind a variable to it:
$stmt = $db->prepare('SELECT * FROM objects o WHERE o.object_id = 1');
$stmt->execute(array($id));
then it will result in the very error message in question, as the number of bound variables (1) doesn't match the number of tokens (0).
To solve this problem one just have to carefully compare the number of tokens in the prepared query with the number of variables that were bound to the statement.
Try the statement like:
$stmt = $db->query('SELECT object_id, object_type, object_status, object_lati, object_long FROM objects o WHERE object_id = ? ', 1);
try this instead
$row = $db->fetchRow('SELECT o.object_id, o.object_type, o.object_status, o.object_lati, o.object_long FROM objects o WHERE o.object_id = 1');
// return false if no result found
or to get record set
$rs = $db->fetchAll('SELECT o.object_id, o.object_type, o.object_status, o.object_lati, o.object_long FROM objects o WHERE o.object_id = 1');
// returns array with results