How to search data in two phalcon tables - find

Someone could explain me How to search data in two phalcon tables:
I have this query:
$Q = $this->request->getPost("data");
$phql = 'SELECT b.idbank,b.name,m.description,m.date
FROM bank b
inner join movement m on b.idbank=m.idbank
WHERE b.estado = 1 and b.name like "%'.$Q.'%" or m.description like "%'.$Q.'%"
Order by b.idbank desc
';
$bank = $this->modelsManager->executeQuery($phql);
There would be some way to do it this way:
$Q = $this->request->getPost("data");
$bank = Bank::find([
"name like '%:dato:%'",
"bind" => [
"dato" => $Q
]
]);
$mov = $bank->getmovement([
"description like '%:dato:%'",
"bind" => [
"dato" => $Q
]
]);
$bank = $mov->bank;
I actually do not know how to do it.
What I did is not working for me.

You need to move the percents from the condition placeholder to your bind parameters. Your first query reworked:
$bank = Bank::find([
"name like :dato:",
"bind" => [
"dato" => '%'. $Q .'%'
]
]);
More examples in the Documentation.

Related

Why `select` does not replace existing columns?

DOC for select attribute:
select indicates which columns should be selected from the storage
This works as expected:
$rs->search( undef, { select => [ 'me.id', 'me.user_id' ] } )
->search( undef, { select => [ 'me.role_id' ] } )
$rs->as_query; # SELECT "me"."role_id" FROM "users_roles" "me"
But this does not:
$rs->search( undef, { prefetch => [ 'User' ] } )
->search( undef, { select => [ 'User.name' ] } )
$rs->as_query; # SELECT "User"."name", "User"."id", "User"."email", "User"."name" FROM "users_roles" "me" JOIN "users" "User" ON "User"."id" = "me"."user_id"
The prefetch implies +columns ( which is: +select and +as ).
I alter select in second search by requesting only one column: name
What did I miss?
Why I still get columns name, id, email, name instead of only one name?
If I rewrite prefetch by join. This works fine:
$rs->search( undef, {
,join => [ 'User' ]
,collaplse => 1
,'+columns' => [map
{ +{ "cds.$_" => "cds.$_" } }
$c->db->source('Right')->related_source('User')->columns
]
})->search( undef, { select => [ 'User.name' ] } )
$rs->as_query; # SELECT "User"."name" FROM "users_roles" "me" JOIN "users" "User" ON "User"."id" = "me"."user_id"
So it seems there is a bug with prefetch. Because join+collapse++columns is not same as prefetch:
DOC
This attribute is a shorthand for specifying a "join" spec, adding all columns from the joined related sources as "+columns" and setting "collapse" to a true value.
Also docs states:
For example, the following two queries are equivalent
As you can see join and prefetch are not equivalent

Extracting date parts using DBIC while keeping queries database agnostic

I use a MySQL database in production and a SQLite database for running tests. One part of my application is used to gather monthly statistics for a year. I've successfully done this, however it came at a cost of not being able to automate tests because I'm using MySQL specific functions when querying for the data:
my $criteria = {
status => ['Complete'],
'YEAR(completed_on)' => DateTime->now()->year(),
};
my $attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => [qw/MONTH(completed_on)/],
};
Notice I'm using YEAR and MONTH MySQL functions.
I know one way I can substitute the where clause to eliminate the use of MySQLs YEAR function, something like this:
my $dtf = $schema->storage->datetime_parser;
my $begin_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 1 ); #inception o_O
my $end_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 36[56] );
my $criteria = {
status => ['Complete'],
completed_on =>
-between => [
$dtf->format_datetime($begin_date),
$dtf->format_datetime($end_date),
]
};
Using the recommended way to query date fields using DBIC
But I'm stumped as to what to do with the group_by clause and how to make the grouping of this fields date value by month database agnostic as well. Wondering if anyone has any ideas?
Thanks!
Sometimes you will have to make engine specific code in DBIx::Class if you're trying to do special things. You can use $schema->storage->sqlt_type to make different SQL.
Note you can also use substr(completed_on,1,4) to get the year in SQLite.
This will solve your problem:
my $type = $schema->storage->sqlt_type;
my $criteria;
my $attributes;
if ($type eq 'MySQL') {
$criteria = {
status => ['Complete'],
'YEAR(completed_on)' => DateTime->now()->year(),
};
$attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => [qw/MONTH(completed_on)/],
};
}
elsif ($type eq 'SQLite') {
my $dtf = $schema->storage->datetime_parser;
my $begin_date = DateTime->from_day_of_year( year => DateTime->now()->year(), day_of_year => 1 ); #inception o_O
my $end_date = DateTime->from_day_of_year( year => DateTime->now()->year() + 1, day_of_year => 1 )->add( seconds => -1 );
$criteria = {
status => ['Complete'],
completed_on => {
-between => [
$dtf->format_datetime($begin_date),
$dtf->format_datetime($end_date),
]
}
};
$attributes = {
select => [ { count => 'title' }, 'completed_on' ],
as => [qw/num_completed datetime/],
group_by => ['substr(completed_on,6,2)'],
};
}

Use "Alias" in "Select" With Medoo

I'm using the Medoo framework.
I need this query:
Select a.name, b.name from section as a left join section as b on(a.idsection=b.section_idfather)
In the Medoo format:
$Data = $database->select("section",
[
"[>]section" => ["idsection" => "section_idfather"]
],
[
"section.name",
"section.name",
]
]);
How can I do this query in the right Medoo format??
So, I change a litty the original file to get assign an ALIAS to the Select Columns:
1) Add in the LINE: 127
protected function column_quote_as($string)
{
return str_replace('.', '.', $string);
}
2) Add in the LINE: 405 (original line)
foreach($columns AS $Columns){
$columnsTemp =explode('[>]',$Columns);
$columnsVal = ($columnsTemp[1]!='') ? '`' . $columnsTemp[0] . '` AS \'' . $columnsTemp[1] . '\'':'`' .$columnsTemp[0] . '`';
$ColumnsEnd[] = $columnsVal;
}
3) Change the LINE: 408 (original line)
is_array($ColumnsEnd) ? $this->column_quote_as( implode(', ', $ColumnsEnd) ) :
And now, the new format to take the query is:
$Data = $database->select("section",
[
"[>]section" => ["idsection" => "section_idfather"]
],
[
"section.name[>]SecName",
"section.name[>]SecNameFather",
]
]);
You can use parentheses to add an alias for a column name.
$data = $database->select("account", [
"user_id",
"nickname (my_nickname)"
]);
For joins:
$data = $database->select("post (content)", [
"[>]account (user)" => "user_id",
], [
"content.user_id (author_id)",
"user.user_id"
]);

DBIx::Class query fails, yet dump from DBIC_TRACE works

I cannot rewrite the current DB schema right now BTW, but that's beside the point to the issue I've hit, so please ignore the table structure :D
I'm running this DB query:
my $rs = $dbx->resultset('Result')->search(
{
'result_hnd' => 16078055,
'seasons.outdoor' => 'venue.outdoors',
'seasons.start_date' => { '<=' => 'meet.date_end' },
'seasons.end_date' => { '>=' => 'meet.date_begin' },
},
{
'join' => [
{
'team' => {
'league_teams' => {
'league' => 'seasons',
},
},
},
{
'meet' => 'venue'
},
],
'+select' => ['seasons.season_hnd','seasons.name','seasons.start_date','seasons.end_date','meet.date_begin','meet.date_end'],
'+as' => ['season_hnd','season_name','s_start','s_end','m_start','m_end'],
columns => ['result_hnd'],
group_by => ['seasons.season_hnd'],
}
);
When I run this, I get no results. With DBIC_TRACE on, I see the generated SQL as:
SELECT me.result_hnd, seasons.season_hnd, seasons.name, seasons.start_date, seasons.end_date, meet.date_begin, meet.date_end FROM track.result me JOIN track.team team ON team.team_hnd = me.team_hnd LEFT JOIN track.league_team league_teams ON league_teams.team_hnd = team.team_hnd LEFT JOIN track.league league ON league.league_hnd = league_teams.league_hnd LEFT JOIN track.season seasons ON seasons.league_hnd = league.league_hnd OR seasons.league_hnd = league.parent_league_hnd JOIN track.meet meet ON meet.meet_hnd = me.meet_hnd JOIN track.venue venue ON venue.venue_hnd = meet.venue_hnd WHERE ( ( result_hnd = ? AND seasons.end_date >= ? AND seasons.outdoor = ? AND seasons.start_date <= ? ) ) GROUP BY seasons.season_hnd: '16078055', 'meet.date_begin', 'venue.outdoors', 'meet.date_end'
When I copy and paste this statement into my MYSQL client (and interpolate the placeholders), like this:
SELECT me.result_hnd, seasons.season_hnd, seasons.name, seasons.start_date, seasons.end_date, meet.date_begin, meet.date_end
FROM track.result me
JOIN track.team team ON team.team_hnd = me.team_hnd
LEFT JOIN track.league_team league_teams ON league_teams.team_hnd = team.team_hnd
LEFT JOIN track.league league ON league.league_hnd = league_teams.league_hnd
LEFT JOIN track.season seasons ON seasons.league_hnd = league.league_hnd OR seasons.league_hnd = league.parent_league_hnd
JOIN track.meet meet ON meet.meet_hnd = me.meet_hnd
JOIN track.venue venue ON venue.venue_hnd = meet.venue_hnd
WHERE ( ( result_hnd = 16078055 AND seasons.end_date >= meet.date_begin AND seasons.outdoor = venue.outdoors AND seasons.start_date <= meet.date_end ) )
GROUP BY season_hnd;
I get the exact result I expect (7 records).
This is really bizarre. To all intents and purposes, isn't that exactly the same query? Am I missing something in my debugging? Or is something else happening at the DBIx::Class::ResultSet layer that isn't being dumped?
To tell SQL::Abstract that the value on the right is actually an identifier, you can do the following (as outlined in the docs):
{
'result_hnd' => 16078055,
'seasons.outdoor' => { -ident => 'venue.outdoors' },
'seasons.start_date' => { '<=' => { -ident => 'meet.date_end' } },
'seasons.end_date' => { '>=' => { -ident => 'meet.date_begin' } },
},
To all intents and purposes, isn't that exactly the same query?
(For all intents and purposes, ...)
No. In the log, you have something equivalent to
result_hnd = '16078055'
AND seasons.end_date >= 'meet.date_begin'
AND seasons.outdoor = 'venue.outdoors'
AND seasons.start_date <= 'meet.date_end'
or maybe
result_hnd = 16078055
AND seasons.end_date >= 'meet.date_begin'
AND seasons.outdoor = 'venue.outdoors'
AND seasons.start_date <= 'meet.date_end'
In your attempt, you used
result_hnd = 16078055
AND seasons.end_date >= meet.date_begin
AND seasons.outdoor = venue.outdoors
AND seasons.start_date <= meet.date_end
Sorry, I don't have the solution for you.

MongoDB & PHP - Returning a count of a nested array

Imagine I have a MonogDB collection containing documents as follows:
{name: 'Some Name', components: {ARRAY OF ITEMS}}
How can I return the name and the count of items in components?
Do I have to use a map/reduce?
I am using PHP's Mongo extension.
EDIT: Snippet of current code in PHP (working) but I just want count of the components
$fields = array(
'name', 'components'
);
$cursor = $this->collection->find(array(), $fields);
$cursor->sort(array('created_ts' => -1));
if (empty($cursor) == true) {
return array();
} else {
return iterator_to_array($cursor);
}
Thanks,
Jim
You could use map-reduce or you could use a simple group query as follows. Since I am assuming that your name property is a unique key, this should work even though it isn't a reason that you'd normally use the group function:
db.test.group({
key: { name:true },
reduce: function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
initial: { count: 0}
});
You mentioned that you have an array of components, but it appears that you are storing components as an object {} and not and array []. That is why I had to add the loop in the reduce function, to count all of the properties of the components object. If it were actually an array then you could simply use the .length property.
In PHP it would look something like this (from the Manual):
$keys = array('name' => 1);
$initial = array('count' => 0);
$reduce =<<<JS
function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
JS;
$m = new Mongo();
$db = $m->selectDB('Database');
$coll = $db->selectCollection('Collection');
$data = $coll->group($keys, $initial, $reduce);
Finally, I would strongly suggest that if you are trying to access the count of your components on a regular basis that you store the count as an additional property of the document and update it whenever it changes. If you are attempting to write queries that filter based on this count then you will also be able to add an index on that components property.
You could use db.eval() and write the calculation in JavaScript.
Jim-
These are two separate operations; Unless you want to leverage PHP's count on the results you get which you would then do something like:
$m = new Mongo();
$db = $m->selectDB('yourDB');
$collection = $db->selectCollection('MyCollection');
$cursor = $collection->find(array(), array("name"=>1, "components"=>1));
foreach($cursor as $key){
echo($key['name'].' components: '.count($key['components']);
}
Ran across this today, If your using the new driver with aggregate you can do this in php, ( given this schema )
{name: 'Some Name', components: {ARRAY OF ITEMS}}
In PHP:
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$group' => [
'_id' => null,
'total'=> ['$sum' => "\$components"]
]
]);
The trick here with PHP is to escape the $ dollar sign, this is basically what the mongo documentation says when using size or sum
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
https://docs.mongodb.com/manual/reference/operator/aggregation/sum/
The problem I had is mongo puts fields in as "$field" and PHP doesn't like that at all because of the way it does variable interpolation. However, once you escape the $, it works fine.
I think for this particular case you'd need to do something similar but with $project instead of $group Like this
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$project' => [
'name' => "\$name",
'total'=> ['$sum' => "\$components"]
]
]);
This is an old question but seeing as there is no answer picked, I'll just leave this here.