Doctrine decode function - oracle

In my Symfony2 project, I want to get the sum of a column (Field1) only for some lines (Field2 = 1):
id Field1 Field2
1 10 1
2 20 1
3 30 0
4 40 1
5 50 0
In this example, I want a result of 70 (sum of lines 1, 2 and 4).
Since I Work with Oracle, I have this query working well in my SQL GUI :
SELECT
SUM(decode(Field2, 1, Field1, 0)) AS total
FROM myTable
Since Doctrine doesn't recognize Oracle decode function, I Tried to create mine :
namespace MyBundle\DQL;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
/**
* "DECODE" "(" Expression "," Search "," Result "," Default ")"
*/
class DecodeFunction extends FunctionNode
{
public $expression;
public $search;
public $result;
public $default;
public function getSql(SqlWalker $sqlWalker)
{
return 'decode(' . $this->expression->dispatch($sqlWalker) . ','
. $this->search->dispatch($sqlWalker) . ','
. $this->result->dispatch($sqlWalker) . ','
. $this->default->dispatch($sqlWalker) . ')';
}
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->expression = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->search = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->result = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->default = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
}
config.yml
doctrine:
orm:
entity_managers:
default:
auto_mapping: true
...
dql:
string_functions:
decode: MyBundle\DQL\DecodeFunction
And my querybuilder :
$qb = $this->createQueryBuilder('e');
$qb
->select('SUM(decode(e.Field2, :expect, e.Field1, :default)')
->setParameter('expect', 1)
->setParameter('default', 0);
But I got this error :
[Semantical Error] line 0, col 62 near 'Field2,': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
So what I'm doing wrong?
Maybe is there a simpler solution?
Thanks

Related

HOW TO CHANGE DATATABLE ROW GROUP COLOR

I want to change the colour of the grouped row in a datatable. I just can't seem to get it to work the way I would like and I'm unsure how to get it too. I have done a drawback and get this to work in the group header but for the life of me I can't get it to work in the grouped row footer.
rowGroup: {
/// Group Header Colour
//startRender: null, SET TO NULL IF GROUP HEADER NOT REQUIRED
startRender: function(rows, group) {
// colour the group header and uncomment to show
// return $('<tr class="group group-start"><td class="' + (group == '1' ? 'green' : (group == '0' ? 'green' : 'green')) + '" colspan="9">' + group + ' ' + ' ('+rows.count() + '</td></tr>');
},
// Below used for Grouping and summing at end of each dept in this case
endRender: function ( rows, group ) {
var filteredData = $('#giftcards').DataTable()
.rows()
.data()
.filter( function ( data, index ) {
return data[groupColumn] == group ? true : false;
} )
.pluck(6) // the column we are counting in this case retail value per dept
.sum();
//return 'Dept: <tr class="rowgroup"><td colspan="9">' + group + '('+ rows.count() + ' rows on page , £' + roundTo(filteredData,2) + 'Retail Sale value for all pages)'; /// Needs multiplied by PD.TotalShelfstockQuantity
return 'Dept: <tr class="group group-start"><td class="' + (group == '1' ? 'green' : (group == '0' ? 'green' : 'green')) + '" colspan="11">' + group + ' ' + '('+ rows.count() +')'
+ ' rows on page , <larger>£ </larger>' + roundTo(filteredData,2) + ' Retail Sale value for all pages)' + '</td></tr>'; /// Needs multiplied by PD.TotalShelfstockQuantity
// $(row).css("background-color", "green");
},
dataSrc: groupColumn,
//backgroundColor = styleEqual('Dept','red')
},`
```
THE BELOW PIECE OF CODE SEEMS TO BE ALMOST THERE - HOWEVER, IT IS ONLY SHOWING IN A FEW COLUMNS - I am not a programmer by the way.
```
return 'Dept: <tr class="group group-start"><td class="' + (group == '1' ? 'green' : (group == '0' ? 'green' : 'green')) + '" colspan="11">' + group + ' ' + '('+ rows.count() +')'
+ ' rows on page , <larger>£ </larger>' + roundTo(filteredData,2) + ' Retail Sale value for all pages)' + '</td></tr>';
```
Thanks in advance. Below is the status as is at minute. I would like the green to populate the entire row.
[![enter image description here](https://i.stack.imgur.com/P46Rz.png)](https://i.stack.imgur.com/P46Rz.png)
I have tried various solutions that were on this forum but I am missing something. I know that' I'm close but no cigar.

How to "orderBy" a query with a variable parameter?

I'm working on Symfony4 with doctrine.
On my repository, i get informations but i can't do an order by with a variable parameter( distance between a user an many other users)
public function findByThemeAndDistance($theme,$distance,$user){
$latUser=$user->getAddress()->getLnt();
$lngUser = $user->getAddress()->getLgt();
return $this->createQueryBuilder('a')
// ->select('a as activity,dist(distance) as distance' )
->join('a.author','u')
->join('u.address','add')
->andWhere('a.theme=:val')
->andWhere( '(6378 * acos(cos(radians( add.lnt)) * cos(radians(' . $latUser . ')) * cos(radians(' . $lngUser . ') - radians(add.lgt)) + sin(radians(add.lnt )) * sin(radians(' . $latUser . '))))< :distance')
->setParameter('distance', $distance)
->setParameter('val',$theme)
->orderBy('distance','ASC')
->getQuery()
->getResult()
;
}
I try to get e result ordered by distance but I get This :
Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got '1' and
when i erase the alais, I get This : [Semantical Error] line 0, col
287 near 'distance ASC': Error: 'distance' is not defined.
As the distance field does not exist on your entity you have to add the select and order by its alias
->addSelect('dist(distance) as distance')
->orderBy('distance','ASC')

Spring boot 2 #Query named parameter binding value resolution messes up after upgrade from 1.5

We have the following working query using SpringBoot 1.5:
#Query(value = "SELECT DISTINCT c FROM Customer c INNER JOIN c.industry i WHERE " +
"c.role IN :roleFilter " +
"AND (:#{#industryFilter.size()} = 1 OR i.id IN :industryFilter) " +
"AND (:searchString IS NULL " +
"OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') " +
"OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) " +
"AND (:includeDeleted = true OR c.deletedDate is NULL)",
countQuery = "SELECT COUNT(DISTINCT c) FROM Customer c INNER JOIN c.industry i WHERE " +
"c.role IN :roleFilter AND " +
"(:#{#industryFilter.size()} = 1 OR i.id IN :industryFilter) " +
"AND (:searchString IS NULL " +
"OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') " +
"OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) " +
"AND (:includeDeleted = true OR c.deletedDate is NULL)")
Page<Customer> findCustomers(#Param("roleFilter") Set<Role> roleFilter,
#Param("industryFilter") Set<String> industryFilter,
#Param("searchString") String searchString,
#Param("includeDeleted") boolean includeDeleted, Pageable pageable);
Please note how we pass the input to the LIKE: CONCAT('%', :searchString, '%')
After upgrading from springBootVersion = '1.5.17.RELEASE' to springBootVersion = '2.1.3.RELEASE' (we use Gradle) that query will fail at runtime with an exception:
org.hibernate.QueryException: Named parameter not bound : includeDeleted
Replacing CONCAT('%', :searchString, '%') with %:searchString% fixes the problem.
The question I have is: why?
By going into debug mode and following the full callstack, I could see the parameters being correctly retrieved from the method invocation as observed in JdkDynamicAopProxy at line 205 makes a call Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); that results in:
argsToUse = {Object[5]#15562}
0 = {HashSet#15491} size = 4
1 = {HashSet#15628} size = 1
2 = null
3 = {Boolean#15629} false
4 = {PageRequest#15490} "Page request [number: 0, size 20, sort: name: ASC,name2: ASC]"
So far so good. Then, we keep going and the method to call is also correctly resolved:
parameterTypes = {Class[5]#15802}
0 = {Class#198} "interface java.util.Set"
1 = {Class#198} "interface java.util.Set"
2 = {Class#311} "class java.lang.String"
3 = {Class#15811} "boolean"
4 = {Class#9875} "interface org.springframework.data.domain.Pageable"
Then we go a bit further and we get to RepositoryFactorySupport line 599 calling private Object doInvoke(MethodInvocation invocation) throws Throwable which uses private final Map<Method, RepositoryQuery> queries; from the inner class public class QueryExecutorMethodInterceptor implements MethodInterceptor (I am unsure when/how was this variable created and populated), which contains all the queries annotated with #Query in my repository interface.
For our specific case, it contains an entry (last one) that matches the query I am invoking (findCustomers):
queries = {HashMap#16041} size = 3
0 = {HashMap$Node#16052} "public abstract com.swisscom.psp.domain.Customer com.swisscom.psp.repository.CustomerRepository.getOne(java.lang.String)" ->
1 = {HashMap$Node#16055} "public abstract boolean com.swisscom.psp.repository.CustomerRepository.existsWithRole(java.lang.String,java.util.Set)" ->
2 = {HashMap$Node#16058} "public abstract org.springframework.data.domain.Page com.swisscom.psp.repository.CustomerRepository.findCustomers(java.util.Set,java.util.Set,java.lang.String,boolean,org.springframework.data.domain.Pageable)" ->
And expanding that entry I can see where the error comes from, the binding for the :includeDeleted named parameter is simply not there:
value = {SimpleJpaQuery#16060}
query = {ExpressionBasedStringQuery#16069}
query = "SELECT DISTINCT c FROM Customer c INNER JOIN c.industry i WHERE c.role IN :roleFilter AND (:__$synthetic$__1 = 1 OR i.id IN :industryFilter) AND (:searchString IS NULL OR CONCAT_WS(' ', c.name, c.name2) LIKE CONCAT('%', :searchString, '%') OR CONCAT_WS(' ', c.name2, c.name) LIKE CONCAT('%', :searchString, '%')) AND (:includeDeleted = true OR c.deletedDate is NULL)"
bindings = {ArrayList#16089} size = 6
0 = {StringQuery$InParameterBinding#16092} "ParameterBinding [name: roleFilter, position: null, expression: null]"
1 = {StringQuery$ParameterBinding#16093} "ParameterBinding [name: __$synthetic$__1, position: null, expression: #industryFilter.size()]"
2 = {StringQuery$InParameterBinding#16094} "ParameterBinding [name: industryFilter, position: null, expression: null]"
3 = {StringQuery$ParameterBinding#16095} "ParameterBinding [name: searchString, position: null, expression: null]"
4 = {StringQuery$ParameterBinding#16096} "ParameterBinding [name: searchString, position: null, expression: null]"
5 = {StringQuery$ParameterBinding#16097} "ParameterBinding [name: searchString, position: null, expression: null]"
Now, I have the fix as mentioned earlier, but I would still very much like to know the following for future reference:
when and how is the private final Map<Method, RepositoryQuery> queries variable created and populated?
what exactly is causing this error? Did I miss something in the upgrade process? Am I using/mixing deprecated logic/wrong logic and should change the code further?
Our DB is MariaDB 10.1.36
EDIT: In all the places where this behaviour occurred (in some it still occurs), the unbound parameter is always the last one
EDIT2: Someone else also has a similar behaviour after the upgrade, why does this happen? reference
EDIT3: reference and also this weird behaviour has been reported. Interesting enough, I do not get the exception IF I pass already concatenated input to :searchString (eg: %SOMETHING%) and I do get the exception if I leave %:searchString% instead. And yes, moving those parameters in the end solves some errors I had with binding.
EDIT4: Maybe related bug?
Clearly there is something strange going on, so: how does this binding resolution happen exactly?
Thanks in advance and have a nice day
Actually, as far as I know, neither of your two approaches is the correct one to use here for handling LIKE with a wildcard placeholder. Instead, the LIKE expression should be:
LIKE :searchString
To this parameter :searchString you should be binding:
String searchString = "bananas";
String param = "%" + searchString + "%";
// then bind param to :searchString
That is, you bind the entire string, with the % wildcard, together. Then, let the database worry about how to escape it.

Doctrine distances function

I try to use a function which calculate distance between $user lat/lng and coordinates in my BDD with limited distance.
It's an SQL request and I try to use Doctrine to implement it.
Here is my code
$config = new \Doctrine\ORM\Configuration();
$config->addCustomNumericFunction('COS', 'DoctrineExtensions\Query\Mysql\Cos');
$config->addCustomNumericFunction('ACOS', 'DoctrineExtensions\Query\Mysql\Acos');
$config->addCustomNumericFunction('RADIANS', 'DoctrineExtensions\Query\Mysql\Radians');
$config->addCustomNumericFunction('SIN', 'DoctrineExtensions\Query\Mysql\Sin');
$maxLat = $form_citylat + rad2deg($rad / $R);
$minLat = $form_citylat - rad2deg($rad / $R);
$maxLng = $form_citylng + rad2deg(asin($rad / $R) / cos(deg2rad($form_citylat)));
$minLng = $form_citylng - rad2deg(asin($rad / $R) / cos(deg2rad($form_citylat)));
$qb = $this->createQueryBuilder('u')->select('u.lat, u.lng')
->addSelect('acos(sin(:lat)*sin(radians(u.Lat)) + cos(:lat)*cos(radians(u.Lat))*cos(radians(u.lng)-:lng)) * :R As D')
->where('lat Between :minlat And :maxlat And lng Between :minlng And :maxlng And acos(sin(:lat)*sin(radians(u.Lat)) + cos(:lat)*cos(radians(u.Lat))*cos(radians(u.Lng)-:lng)) * :R < :rad')
->setParameter('lat',deg2rad($form_citylat))
->setParameter('lng',deg2rad($form_citylng))
->setParameter('minlat',$minLat)
->setParameter('minlng',$minLng)
->setParameter('maxlat',$maxLat)
->setParameter('maxlng',$maxLng)
->setParameter('rad',$rad)
->setParameter('R',$R)
->orderBy('D');
return $qb->getQuery()->getResult();`
But I got this error message :
[Syntax Error] line 0, col 40: Error: Expected
Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got '.'
I tried different options but it doesn't work.
Anyone has an answer please ?
public function findByThemeAndDistance($theme,$distance,$user){
$latUser=$user->getAddress()->getLnt();
$lngUser = $user->getAddress()->getLgt();
return $this->createQueryBuilder('a')
//->select('a as activity,dist('.$distance.') as distance' )
->join('a.author','u')
->join('u.address','add')
->andWhere('a.theme=:val')
->andWhere( '(6378 * acos(cos(radians( add.lnt)) * cos(radians(' . $latUser . ')) * cos(radians(' . $lngUser . ') - radians(add.lgt)) + sin(radians(add.lnt )) * sin(radians(' . $latUser . '))))< :distance')
->setParameter('distance', $distance)
->setParameter('val',$theme)
->orderBy( 'a.author','ASC')
->getQuery()
->getResult();
}

A more elgant way to roll up multiple rows?

I'm new to codeigniter (although I suppose this isn't an exclusively CI question) and have a method in a model which selects data from two tables joined by id.
table 1 (tblclients) looks like this:
+----+------------+
+ id + c_name +
+----+------------+
+ 1 + Joe Bloggs +
+ 2 + Jim Bloggs +
+ 3 + Tim Bloggs +
+----+------------+
table 2 (tblstars) looks like this:
+----+------------+
+ id + s_date +
+----+------------+
+ 1 + 27/01/12 +
+ 1 + 15/02/12 +
+ 1 + 18/02/12 +
+ 2 + 03/01/12 +
+ 2 + 11/02/12 +
+ 2 + 15/02/12 +
+ 3 + 01/01/12 +
+ 3 + 19/02/12 +
+----+------------+
I want to 'roll up' the joined data into one line for each row in tblclients so I can output, for example:
+----+------------+--------------------------------+
+ id + Name + Dates +
+----+------------+--------------------------------+
+ 1 + Joe Bloggs + 27/01/12 15/02/12 18/02/12 +
+ 2 + Jim Bloggs + 03/01/12 11/02/12 15/02/12 +
+ 3 + Tim Bloggs + 01/01/12 19/02/12 +
+----+------------+--------------------------------+
Now I've 'solved' the problem by using the following in my model:
function get_clients_concat()
{
$query = $this->db
->select('tblclients.id, tblclients.c_name, GROUP_CONCAT(tblstars.s_date SEPARATOR "#") AS star_dates', NULL, FALSE)
->join('tblstars', 'tblstars.id = tblclients.id', 'left')
->order_by('tblclients.id')
->group_by('tblclients.id')
->get('tblclients');
return $query->result();
}
and then exploding the array (created by the GROUP_CONCAT) in my view and doing some processing with it there ... but it seems really CLUNKY.
Is there a better solution?
If you want to get all that data in a single query (with each row's id unique), then yes--that's the way to go.
If you need to sort or filter the results, you'll run into performance bottlenecks when the tables fill up.
It seems odd though--why wouldn't you select from tblstars (joining tblclients) instead, then use application logic to index the dates by c_name/id?
<?php
// Select data from tables
$data = $this->db
->select('tblclients.id, tblclients.c_name, tblstars.s_date')
->join('tblclients', 'tblclients.id = tblstars.id')
->order_by('tblstars.id')
->get('tblstars');
// Index data by client id
// (keeping record of client name and dates array for each)
$clients = array();
foreach ($data->result() as $result)
{
if (empty($clients[$result->id]))
{
$clients[$result->id] = array(
'name' => $result->c_name,
'dates' => array($result->s_date)
);
}
else
{
$clients[$result->id]['dates'][] = $result->s_date;
}
}
return $clients;

Resources