New Zend framework changes in Zend/Db/Sql/columns causes problems - php

Our framework sits on top of the Zend framework. A change in the way columns() is working causes problems with our database calls. Before, it was fine to do something like:
$distanceFormula = "$earthRadius*ACOS(COS(RADIANS($lat)))";
$select->columns(array('distance' => $distanceFormula));
This created a query:
SELECT `items`.*, 6371*ACOS(COS(RADIANS(51.985103)) AS `distance`
NOW it creates a query:
SELECT `items`.*, `6371*ACOS(COS(RADIANS(51.985103))` AS `distance`
columns() puts ` (apostrophe) around everything so we get the following error:
Zend_Db_Statement_Mysqli_Exception
Mysqli prepare error: Unknown column '6371*ACOS.... etc
Is there a way to tell columns not to put the formula between apostrophes?

I found that if the formula is entered as Zend_Db_Expr, everything works fine.
$distanceFormula = new Zend_Db_Expr("($earthRadius*ACOS(COS(RADIANS($lat))");
$select->columns(array('distance' => $distanceFormula));

Related

Select month from a date field in laravel

I'm trying to perform a query in Laravel 7 that gets the month part of a date field. I tried the following queries
$test = MyModal::selectRaw('month("date_col") as temp')->get();
$test = MyModal::select(DB::Raw('month("date_col") as temp'))->get();
$test = DB::table('my_table')->select('month("date_col") as temp')->get();
All variation of the same query. If I dump the query log, the query is correct for all 3
select month("date_col") as temp from "my_table"
If I run this on my DB I get the result as well. But I keep getting this error in laravel:
Illuminate\Database\QueryException: SQLSTATE[HY000]: General error: 1 no such function: month (SQL: select month("date_col") as temp from "my_table")
Am I doing something wrong? And is there a way to do this query in laravel?
Edit: Sorry forgot to mention this is with Laravel's unit testing
I think the problem in the way you use Month function
you don't set quotations for the column name, it should be:
$test = MyModal::selectRaw('month(date_col) as temp')->get();
it should work
You can do something like this:
MyModal::select(DB::raw("MONTH(date_col) month"))->get();
Thanks to OMR's comment this solution finally worked
$test = MyModel::selectRaw('strftime("%m", "date_col") as temp')->get();
Update: It looks like what I had in the beginning worked fine and the unit test was the only part throwing this error. Since I was working with mysql while the test environment was using sqlite which apparently doesnt like the "month()" function which OMR did point out in his comment. So this solution works in the test but not in live. Couldn't figure out how to change the test environment to use in memory mysql instead of sqlite. If anyone knows how to please add an answer here.

Mysql Server timing out on specific locate queries

Im programming a search with ZF3 and the DB module.
Everytime i use more than 1 short keyword - like "49" and "am" or "1" and "is" i get this error:
Statement could not be executed (HY000 - 2006 - MySQL server has gone away)
Using longer keywords works perfectly fine as long as i dont use 2 or more short keywords.
The problem only occurs on the live server its working fine on the local test server.
The project table has ~2200 rows with all kind of data the project_search table has 17000 rows with multiple entries for each project , each looking like:
id, projectid, searchtext
The searchtext Column is fulltext. Here the relevant part of the php code:
$sql = new Sql($this->db);
$select = $sql->select(['p'=>'projects']);
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$join = $sql->select('project_search');
$join->columns(['projectid' => new Expression('DISTINCT(projectid)')]);
$join->group("projectid");
foreach($keywords as $keyword) {
$join->having(["LOCATE('$keyword', GROUP_CONCAT(searchtext))"]);
}
$select->join(
["m" => $join],
"m.projectid = p.id",
['projectid'],
\Zend\Db\Sql\Select::JOIN_RIGHT
);
}
Here the resulting Query:
SELECT p.*, m.projectid
FROM projects AS p
INNER JOIN (
SELECT projectid
FROM project_search
GROUP BY projectid
HAVING LOCATE('am', GROUP_CONCAT(searchtext))
AND LOCATE('49', GROUP_CONCAT(searchtext))
) AS m
ON m.projectid = p.id
GROUP BY p.id
ORDER BY createdAt DESC
I rewrote the query using "MATCH(searchtext) AGAINST('$keyword)" and "searchtext LIKE '%keyword%' with the same result.
The problem seems to be with the live mysql server how can i debug this ?
[EDIT]
After noticing that the error only occured in a special view which had other search related queries - each using multiple joins (1 join / keyword) - i merged those queries and the error was gone. The amount of queries seemed to kill the server.
Try refactoring your inner query like so.
SELECT a.projectid
FROM (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%am%'
) a
JOIN (
SELECT DISTINCT projectid
FROM projectsearch
WHERE searchtext LIKE '%49%'
) b ON a.projectid = b.projectid
It should give you back the same set of projectid values as your inner query. It gives each projectid value that has matching searchtext for both search terms, even if those terms show up in different rows of project_search. That's what your query does by searching GROUP_CONCAT() output.
Try creating an index on (searchtext, projectid). The use of column LIKE '%sample' means you won't be able to random-access that index, but the two queries in the join may still be able to scan the index, which is faster than scanning the table. To add that index use this command.
ALTER TABLE project_search ADD INDEX project_search_text (searchtext, projectid);
Try to do this in a MySQL client program (phpmyadmin for example) rather than directly from your php program.
Then, using the MySQL client, test the inner query. See how long it takes. Use EXPLAIN SELECT .... to get an explanation of how MySQL is handling the query.
It's possible your short keywords are returning a ridiculously high number of matches, and somehow overwhelming your system. In that case you can put a LIMIT 1000 clause or some such thing at the end of your inner query. That's not likely, though. 17 kilorows is not a large number.
If that doesn't help your production MySQL server is likely misconfigured or corrupt. If I were you I would call your hosting service tech support, somehow get past the front-line support agent (who won't know anything except "reboot your computer" and other such foolishness), and tell them the exact times you got the "gone away" message. They'll be able to check the logs.
Pro tip: I'm sure you know the pitfalls of using LIKE '%text%' as a search term. It's not scalable because it's not sargable: it can't random access an index. If you can possibly redesign your system, it's worth your time and effort.
You could TRY / CATCH to check if you get a more concrete error:
BEGIN TRY
BEGIN TRANSACTION
--Insert Your Queries Here--
COMMIT
END TRY
BEGIN CATCH
DECLARE #ErrorMessage NVARCHAR(4000);
DECLARE #ErrorSeverity INT;
DECLARE #ErrorState INT;
SELECT
#ErrorMessage = ERROR_MESSAGE(),
#ErrorSeverity = ERROR_SEVERITY(),
#ErrorState = ERROR_STATE();
IF ##TRANCOUNT > 0
ROLLBACK
RAISERROR (#ErrorMessage, -- Message text.
#ErrorSeverity, -- Severity.
#ErrorState -- State.
);
END CATCH
Although because you are talking about short words and fulltext it seems to me it must be related to StopWords.
Try running this query from both your dev server and production server and check if there are any differences:
SELECT * FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD;
Also check in my.ini (if that is the config file) text file if these are set to:
ft_stopword_file = ""
ft_min_word_len = 1
As stated in my EDIT the problem wasnt the query from the original Question, but some other queries using the search - parameter as well. Every query had a part like follows :
if(isset($filter['search'])) {
$keywords = preg_split('/\s+/', trim($filter['search']));
$field = 1;
foreach($keywords as $keyword) {
$join = $sql->select('project_search');
$join->columns(["pid$field" => 'projectid']);
$join->where(["LOCATE('$keyword', searchtext)"]);
$join->group("projectid");
$select->join(
["m$field" => $join],
"m$field.pid$field = p.id"
);
$field++;
}
}
This resulted in alot of queries with alot of resultrows killing the mysql server eventually. I merged those Queries into the first and the error was gone.

Laravel Fluent Query Builder Update Query

I want to add two columns while using update, like this:
Update purchase_stock inner join stock on purchase_stock.fkstockid=stock.stockid SET currentavailable=currentavailable+subquantity where fkorderid='1';
Here is the current Fluent code:
DB::table('purchase_stock')->join('stock','stock.stockid','=','purchase_stock.fkstockid')->where('fkorderid',$orderId)->update(array('currentavailable'=>'currentavailable'+'subquantity'));**
But it throws error as below:
"error":{"type":"Symfony\\Component\\Debug\\Exception\\FatalErrorException","message":"syntax error, unexpected '=>'"
Does any one have solution?
You were very close with your fluent attempt, but there were two issues:
The plus sign needs to be inside the quotes, since you want the math done in SQL, not in PHP.
Need a DB::raw() around the value so Laravel doesn't think you're actually trying to set it to the string "currentavailable + subquantity"
So the final product looks like this:
DB::table('purchase_stock')
->join('stock', 'stock.stockid', '=', 'purchase_stock.fkstockid')
->where('fkorderid', $orderId)
->update(['currentavailable' => DB::raw('currentavailable + subquantity')]);
Мaybe you need to set these two fields from which tables. Exampl. DB::table('purchase_stock')->join('stock','stock.stockid','=','purchase_stock.fkstockid')->where('fkorderid',$orderId)->update(array('stock.currentavailable'=>'stock.currentavailable'+'stock.subquantity'));
Ohk!
I already tried this one but it is not working
As of now I am using DB::statement('') and it's working
So I have written whole update query within statement and it's working as somewhere I have read that it will not be helpful to return result set but will work with insert or update.

Zend Framework 2 and SELECT count(*) query

I'm trying to do a query like this using Zend Framework 2:
SELECT count(*) as num FROM mytable
Here's the code I'm using to build my select statement (bear in mind I've imported the necessary classes):
$select = new Select();
$select->from('mytable')
->columns(array('num'=>'count(*)'), false);
This code doesn't work because the resulting query is as follows:
SELECT [count(*)] AS [num] FROM [mytable]
...which throws the following error:
Invalid column name 'count(*)'
This is caused by the square brackets around count(*). How can I get this to work properly, basically to have count(*) instead of [count(*)] in the SQL. Also, I know that you can do it with just a regular query, but I need this to work with the Select object. As far as I know, this used to work with the previous versions of Zend, I've seen plenty of solutions for those, but nothing for Zend Framework 2.
Somebody on another forum was kind enough to give me the answer for this. This is how it's done:
$select->columns(array('num' => new \Zend\Db\Sql\Expression('COUNT(*)')));
Yes, without new \Zend\Db\Sql\Expression('COUNT(*)'), just COUNT(*) leads to the following error statement:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'albs.COUNT(*)' in 'field list'
Having the
new \Zend\Db\Sql\Expression('COUNT(*)')
resolved it.
Could you try this code?
$this->num = $select->columns(array('num' => new \Zend\Db\Sql\Expression('COUNT(*)')));
return $this->num;

Mongo Doctrine Query Builder Select does not work. Bug?

$dm = $this->get('doctrine.odm.mongodb.document_manager');
$query = $dm->createQueryBuilder('MyBundle:Listing')
->select('title')
->field('coordinates')->geoNear(
(float)$longitude,
(float)$latitude
)->spherical(true);
$classifieds_array = $classifieds->toArray();
$data = array('success'=>true,'classifieds' => $classifieds_array,
'displaymessage' => $classifieds->count(). " Search Results Found");
Even though I am selecting just one field, for my result set, I am getting every thing back in collection along with title. Is this a bug?
NOTE: I commented out the ->field('coordinates')->geoNear((float)$longitude, (float)$latitude)->spherical(true) line and now the select seems to work. This is crazy.
The geoNear command in MongoDB doesn't seem to support filtering result fields, according to the documentation examples. Only a query option is supported to limit matched documents.
In your case, it also looks like mixing up the geoNear() and near() builder methods in Doctrine. Since you're operating on the coordinates field, the appropriate syntax would be near(). geoNear() is a top-level method to tell the builder you wish to use the command, which doesn't require a field name since it uses the one and only geospatial index on the collection.
For usage examples, I would advise looking at the query and builder unit tests in the Doctrine MongoDB library.

Categories