How to do Doctrine2 calculated fields ordering - doctrine-orm

I've got the following doctrine2 query working nicely, it retrieves all 'markers' within some geographical radius.
$qb->select('marker')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->setParameter('distance', $radius);
Now I want to sort them by distance.
$qb->select('marker (DISTANCE(marker.location, POINT_STR(:point))) AS distance')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'DESC')
->setParameter('distance', $radius);
But unfortunately this does not work, I am wondering is this possible as distance is not a real property of my entity, but a calculated one?
What is the trick here?

Alternatively, try using HIDDEN in your call to select() :
$qb->select('m as marker, (DISTANCE(m.location, POINT_STR(:point))) as HIDDEN distance')
// note HIDDEN added here --->------->------->------->------->------->---^
->from('SndSpecialistLocator\Entity\Marker', 'm')
->where('DISTANCE(m.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'ASC')
->setParameter('distance', $radius)
->setMaxResults(5);
$query = $qb->getQuery();
$result = $query->execute();
Adding HIDDEN to the SELECT clause hides it from the results but allows it to be used in the orderby clause. Your $result should then contain the objects you want without having to do the extra array_walk.

Unfortunately, ordering by aliases is not possible.
What you can do* instead is to manually repeat the function in your orderBy statement:
$qb->select('marker (DISTANCE(marker.location, POINT_STR(:point))) AS distance')
->from('SndSpecialistLocator\Entity\Marker', 'marker')
->where('DISTANCE(marker.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('DISTANCE(marker.location, POINT_STR(:point))', 'DESC')
->setParameter('distance', $radius);
*We will probably all end up in dev-hell for stepping off the holy path of DRY

Found the solution.
$qb->select('m as marker, (DISTANCE(m.location, POINT_STR(:point))) as distance')
->from('SndSpecialistLocator\Entity\Marker', 'm')
->where('DISTANCE(m.location, POINT_STR(:point)) < :distance')
->setParameter('point', $point)
->orderBy('distance', 'ASC')
->setParameter('distance', $radius)
->setMaxResults(5);
$query = $qb->getQuery();
$result = $query->execute();
/**
* Convert the resulting associative array into one containing only the entity objects
*
* array (size=5)
* 0 =>
* array (size=2)
* 'marker' =>
* object(SndSpecialistLocator\Entity\Marker)[647]
* protected 'id' => int 43
* protected 'title' => string 'c5d07acdd47efbe38a6d0bf4ec64f333' (length=32)
* private 'location' =>
* object(SndSpecialistLocator\Orm\Point)[636]
* private 'lat' => float 52.2897
* private 'lng' => float 4.84268
* 'distance' => string '0.0760756823433058' (length=18)
* 1 => ...
*
* array (size=5)
* 0 =>
* object(SndSpecialistLocator\Entity\Marker)[647]
* protected 'id' => int 43
* protected 'title' => string 'c5d07acdd47efbe38a6d0bf4ec64f333' (length=32)
* private 'location' =>
* object(SndSpecialistLocator\Orm\Point)[636]
* private 'lat' => float 52.2897
* private 'lng' => float 4.84268
* 1 => ...
*
*/
array_walk($result, function (&$item, $key)
{
$item = $item['marker'];
});

Related

Is it possible to create an inner join between two tables showing all B that contain A?

I have two tables: Kanji, and Vocabulary. Imagine the kanji table looks like this:
目
一
人
And the vocabulary table looks like this:
目的
一番目
一人
二人
人々
注目
目標
一匹
I want to generate a table that finds all vocabulary which contains the kanji in the kanji table and list them together. So the end result would look like this:
人 一人
二人
人々
一 一人
一番目
一匹
目 目的
一番目
注目
目標
I'm not sure how to go about this. If I have just one kanji, I can use the QUERY function to generate all of the vocabularies which contain that one kanji. But can I create a dynamic table which essentially inner joins the "kanji" and "vocabulary" tables, looking for every instance of "vocabulary" contains "kanji"?
I tried using a QUERY to combine the two tables, but it won't work because the tables are mismatched in size:
=QUERY({C1:C296,D1:D224}, "SELECT Col2 WHERE Col1 contains Col2")
In the above example, the C column / Col2 is vocabulary, the D column / Col1 is kanji.
Is there a way to do this using Google Sheets?
The simplest workaround is to get intersections by 1 value:
=FILTER(D:D;REGEXMATCH(D:D;"目"))
The picture shows how to use the copy of this function to get all intersections
The other approach is to use big array-formula like:
=TRANSPOSE(SPLIT(TEXTJOIN(",";1;TRANSPOSE(ARRAYFORMULA(IF(REGEXMATCH(A1:A8;TRANSPOSE(B1:B3));A1:A8;))));","))
=ARRAYFORMULA(SORT(TRIM(SPLIT(TRANSPOSE(SPLIT(QUERY(TRANSPOSE(QUERY(TRANSPOSE(
IF(IFERROR(REGEXEXTRACT(IFERROR(REGEXEXTRACT(C1:C, REPT("(.)", LEN(C1:C)))),
TEXTJOIN("|", 1, A1:A)))<>"", "♦"&IFERROR(REGEXEXTRACT(IFERROR(
REGEXEXTRACT(C1:C, REPT("(.)", LEN(C1:C)))), TEXTJOIN("|", 1, A1:A)))&"♣"&C1:C, ))
,,999^99)),,999^99), "♦")), "♣"))))
seriously, much better way, just add a custom function:
/**
* inner join on equality
*
* #param {Range} items1 Table 1
* #param {Range} items2 Table 2
* #param {Integer} ix1 0-based index of key in Table1
* #param {Integer} ix2 0-based index of key in Table2
*
* #customfunction
* #author marc meyer (marqueymarc)
*/
function innerJoinEQ(items1 = [[]], items2 = [[]], ix1= 0, ix2= 0) {
var res = [];
var item2ix = 0;
var map = new Map();
items1 = Array.isArray(items1)? items1: [[items1]];
items2 = Array.isArray(items2)? items2: [[items2]];
items2.forEach((item2) => {
let entries = map.get(item2[ix2]) || [];
entries.push(item2);
map.set(item2[ix2], entries);
});
items1.forEach((item1) => {
let entries = map.get(item1[ix1]) || [];
entries.forEach((rightPart) => {
let cp = rightPart.slice();
cp.splice(ix2, 1);
res.push([...item1, ...cp]);
})
});
return res;
}

Modify formula stored in string via VBA

(I have hundreds of rows need to change. The data is like this
activity_id equation
5225518 D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560))*(1-S1965)*(1-C0151)
7306234 D0300_BCD * C0502 * (1-(S0191))*(1-S1965)
8293425 D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960))*(1-S1965)
9119429 D0793_DEF * S2605 * (1-S0290)*(1-S1965)
I need to combine the two "1-something" together and there is a pattern.
When ever there is a (1-(Sxxx+Sxxxx))*(1-(Sxxx+Sxxxx)),
combine them into one which is (1-(Sxxx+Sxxxx +Sxxx+Sxxxx))
So I only need to change everything with Sxxxx, you can ignore Cxxxx, Dxxxx, Pxxxx, ...
SO I need to change it to:
activity_id equation
5225518 D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560+S1965))*(1-C0151)
7306234 D0300_BCD * C0502 * (1-(S0191+S1965))
8293425 D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960+S1965))
9119429 D0793_DEF * S2605 * (1-(S0290+S1965))
The following regexp certainly may and arguably should be improved.
Option Explicit
Private m_Rex As RegExp
Private Const SEARCH_PATTERN As String = "\(1-\(?((S\d{4}\+?)+)\)?\)\*\(1-\(?((S\d{4}\+?)+)\)?\)"
' $0 $2
Private Const REPLACE_PATTERN As String = "(1-($1+$3))"
Public Function Simplify(ByVal AVeryParticularFormula As String) As String
If m_Rex Is Nothing Then
Set m_Rex = New RegExp
m_Rex.Global = False
m_Rex.MultiLine = False
m_Rex.IgnoreCase = False
m_Rex.Pattern = SEARCH_PATTERN
End If
Do
Simplify = m_Rex.Replace(AVeryParticularFormula, REPLACE_PATTERN)
If Simplify = AVeryParticularFormula Then Exit Do
AVeryParticularFormula = Simplify
Loop
End Function
? Simplify("D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560))*(1-S1965)*(1-C0151)")
D0312_ABC * (S3765+S3790+S3762+S3763+S3770+S3764+S4480) * (1-(S2820+S0560+S1965))*(1-C0151)
? Simplify("D0300_BCD * C0502 * (1-(S0191))*(1-S1965)")
D0300_BCD * C0502 * (1-(S0191+S1965))
? Simplify("D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960))*(1-S1965)")
D0798_CDE * P0692 * (1-(S0191+S2820+S0560+S0290+S0960+S1965))
? Simplify("D0793_DEF * S2605 * (1-S0290)*(1-S1965)")
D0793_DEF * S2605 * (1-(S0290+S1965))

Updating association

I have two entities with a many-to-many relationship (Scenario and Step). I needed to save the order of the steps within a scenario so i added a third entity called ScenarioToStep with a displayOrder property.
class ScenarioToStep
{
/**
* #ORM\Id
*
* #ORM\ManyToOne(targetEntity="Hakisa\Bundle\GettingStartedBundle\Entity\Scenario", inversedBy="scenarioToStep")
* #ORM\JoinColumn(name="scenario_id", referencedColumnName="id")
*
* #var Hakisa\Bundle\GettingStartedBundle\Entity\Scenario
*/
private $scenario;
/**
* #ORM\Id
*
* #ORM\ManyToOne(targetEntity="Hakisa\Bundle\GettingStartedBundle\Entity\Step", inversedBy="stepToScenario")
* #ORM\JoinColumn(name="step_id", referencedColumnName="id")
*
* #var Hakisa\Bundle\GettingStartedBundle\Entity\Step
*/
private $step;
/**
* Order of the step in the scenario
*
* #ORM\Id
*
* #ORM\Column(type="integer")
*
* #var integer
*/
private $displayOrder;
The 3 properties makes the primary key so i can have a step multiple times in a scenario
I have a UI where i can add steps via drag n drop, remove steps or sort step. This UI send an array to PHP where the index is the position and the value is the id of the step.
array(
0 => 1,
1 => 2,
2 => 3,
3 => 1,
4 => 4
)
I want to update my Scenario entity to match this new ordering. I first tried something like:
$scenario->getScenarioToStep()->clear();
foreach ($tabIds as $pos => $stepId) {
$sts = new ScenarioToStep();
$sts->setScenario($scenario)->setStep($step)->setOrder($pos);
$scenario->getScenarioToStep()->add($sts);
}
but i end up with duplicate key errors
if i do similar thing but instead of instanciating a new scenario i ask entity manager for a reference (proxy) then the association table is always empty
Thanks to anyone who has an idea on how i should perform my save

Can't use same parameter twice in custom function

I wrote a custom DQL function. In this function I use the same parameter twice:
public function getSql(SqlWalker $sqlWalker)
{
$point1_lat = $this->point1_lat->dispatch($sqlWalker);
$point1_lon = $this->point1_lon->dispatch($sqlWalker);
$point2_lat = $this->point2_lat->dispatch($sqlWalker);
$point2_lon = $this->point2_lon->dispatch($sqlWalker);
$unitFactor = 6366.56486; // earth radius in km
return "
$unitFactor *
2 *
ASIN(
SQRT(
POWER(
SIN(($point1_lat - $point2_lat) * pi()/180/2),
2
) +
COS($point1_lat * pi()/180) *
COS($point2_lat * pi()/180) *
POWER(
SIN(($point1_lon - $point2_lon) * pi()/180/2),
2
)
)
)
";
}
This is how the query is executed:
$q = \App::get()->getEntityManager()->createQuery('
SELECT
s,
GEO_DISTANCE(
:lat,
:lng,
s.glat,
s.glng
) AS distance
FROM
\Application\Geo\Entity\Street s
');
$q->setMaxResults(10);
$q->setParameters(array(
'lat' => 52.25948,
'lng' => 6.76403,
));
$result = $q->getResult();
This however gives me the following exception:
Message: SQLSTATE[HY093]: Invalid parameter number: number of bound
variables does not match number of tokens
The following SQL is returned by getSql():
6366.56486 *
2 *
ASIN(
SQRT(
POWER(
SIN((? - g0_.glat) * pi()/180/2),
2
) +
COS(? * pi()/180) *
COS(g0_.glat * pi()/180) *
POWER(
SIN((? - g0_.glng) * pi()/180/2),
2
)
)
)
So I guess the exception is thrown because the named parameters are returned as indexed parameters. Is this a bug in doctrine or am I doing something wrong?
This is not a bug, you can only use each parameter once, as the dispatch() function puts the value on the stack once, to match one ? placeholder in the query.
As a workaround, you can call dispatch() several times:
public function getSql(SqlWalker $sqlWalker)
{
$point1_lat_a = $this->point1_lat->dispatch($sqlWalker);
$point1_lat_b = $this->point1_lat->dispatch($sqlWalker);
$point1_lon = $this->point1_lon->dispatch($sqlWalker);
$point2_lat_a = $this->point2_lat->dispatch($sqlWalker);
$point2_lat_b = $this->point2_lat->dispatch($sqlWalker);
$point2_lon = $this->point2_lon->dispatch($sqlWalker);
$unitFactor = 6366.56486; // earth radius in km
return "
$unitFactor *
2 *
ASIN(
SQRT(
POWER(
SIN(($point1_lat_a - $point2_lat_a) * pi()/180/2),
2
) +
COS($point1_lat_b * pi()/180) *
COS($point2_lat_b * pi()/180) *
POWER(
SIN(($point1_lon - $point2_lon) * pi()/180/2),
2
)
)
)
";
}

How create select/options tag from find('threaded') in CakePHP

How to create select/option html tag from find('threaded') data in CakePHP?
Function find() return results like this:
Array
(
[0] => Array
(
[Forum] => Array
(
[id] => 1
[name] => Forum
)
[children] => Array
(
[0] => Array
(
[Forum] => Array
(
[id] => 3
[name] => Programowanie
[parent_id] => 1
)
)
[1] => Array
(
[Thread] => Array
(
[id] => 11
[name] => Nowe forumowisko
[parent_id] => 1
)
)
)
)
[1] => Array
(
[Forum] => Array
(
[id] => 4
[name] => Nauka
[parent_id] => 0
)
[children] => Array
(
)
)
)
How?
there is a build in method for tree like results to be made into a list for select option tags:
$this->Model->generateTreeList($conditions, null, null, ' - ', $recursive);
instead of using find(threaded)
that is if you attached the Tree behavior to it (which you probably should have since its obviously a tree like model).
But if you want to keep your find(threaded) method you need to manually transform it via recursive method.
thanks DSkinner, very informative..
i have modified it to be more generic:
/**
* Returns an indented html select based on children depth
*
* #param array $data_array - Array of data passed in from cake's find('threaded') feature
* #param array $model - the model name
* #param array $key - the key field on the model
* #param array $value - the value field on the model
* #param array $list - Used internally, contains array to be returned
* #param int $counter - Used Internally, counter for depth
* #return array
*/
public function threaded_to_list($data_array, $model=null, $key='id', $value='name',
&$list = array(), $counter = 0, $separator='__')
{
if ( ! is_array($data_array))
return array();
foreach ($data_array AS $data)
{
$list[$data[$model][$key]] = str_repeat($separator, $counter).$data[$model][$value];
if ( ! empty($data['children']))
{
$this->threaded_to_list($data['children'], $model, $key, $value, $list, $counter + 1);
}
}
return $list;
}
Here's what worked for me.
Make sure you replace:
{SELECT_ID} with the value of the drop down
{SELECT_LABEL} with what is displayed as the option
{MODEL_NAME}
with your model name
/**
* Returns an indented html select based on children depth
*
* #param array $data_array - Array of data passed in from cake's find('threaded') feature
* #param array $list - Used internally, contains array to be returned
* #param int $counter - Used Internally, counter for depth
* #return array
*/
public function drop_down_from_threaded($data_array, &$list = array(), $counter = 0)
{
if ( ! is_array($data_array))
return array();
foreach ($data_array AS $data)
{
$list[$data[{SELECT_ID}]] = str_repeat(' ', $counter).$data[{SELECT_LABEL}];
if ( ! empty($data['children']))
{
$this->drop_down_from_threaded($data['children'], $list, $counter + 1);
}
}
return $list;
}
/**
* Get the data from the find('threaded') and pass it to our new function
*/
$results = $this->{MODEL_NAME}->find('threaded');
$results = $this->drop_down_from_threaded($results);
This may not work 100% for everyone, it works for me, but it should help give you something to start with.
What are "children"? Seems like your tree is spawned across one-to-many relations
It is not necessary to use the Tree behavior to use this method - but all desired results must be possible to be found in a single query.