Why `select` does not replace existing columns? - perl

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

Related

How to search data in two phalcon tables

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.

DBIx::Class: How can I sort on multiple substrings of a column?

I have a SQLite-database with table with a document number following this schema:
16-145-45
16-127-30
16-141-42
16-122-14
15-090-04
15-089-15
15-089-05
I'd like to sort the ResultSet on the first and last part of the number, like this. First, all documents starting with the highest two-digit prefix (16) sorted by the last 2 digits and then the same with the next block, and so on.
16-145-45
16-141-42
16-127-30
16-122-14
15-089-15
15-089-05
15-090-04
Is there a way to do this in DBIx::Class with some sort of custom order_by clause, or what would be the approach?
I have tried the following, which does not work, because the middle part of the number is also considered for sorting:
my #rs = $self->search(undef,
{
order_by => { -desc => 'me.number' }
}
);
If you want the database to sort the results, you have to use literal SQL.
Here's an example for Postgres (I added a space after the backslash to fix the syntax highlighting):
my #rs = $self->search(undef,
{
order_by => \ "split_part(number, '-', 1) || split_part(number, '-', 3) DESC",
}
);
Or, by creating an output column with the +select result set attribute:
my #rs = $self->search(undef,
{
'+select' => [
{ sort_key => \ "split_part(number, '-', 1) || split_part(number, '-', 3)" },
],
'+as' => [ qw(sort_key) ], # Make sort key accessible from DBIC.
order_by => { -desc => 'sort_key' },
}
);
Another approach is to retreive the whole unsorted result set, and sort it on the client side. DBIC doesn't have any specific features to help you with that, so simply use Perl's sort function.
Since the answer from #nwellnhof works like a charm, I just wanted to provide the corresponding syntax for SQLite, which does not know the split_part() function.
# SQL for filtering the doc number in SQLite
my #rs = $self->search(undef,
{
order_by => \ "SUBSTR(me.number, 1, 2) || SUBSTR(me.number, -2, 2) DESC"
}
);
You need to extract additional columns from the result set which are equal to the value of the function that you want to sort by. Then you can just put those columns in an order_by clause as normal
This assumes that your document number field is called docnum. It fetches all the columns from Table plus the two substrings of docnum called docnum1 and docnum3
my $rs = $schema->resultset('Table')->search(undef,
{
'+select' => [
{ substr => [ 'docnum', 1, 2 ], -as => 'docnum1' },
{ substr => [ 'docnum', -2 ], -as => 'docnum3' },
],
order_by => [ { -desc => 'docnum1' }, { -desc => 'docnum3' } ],
}
);

Alias the sum of two columns in a DBIx::Class resultset

SELECT me.id, me.date_created, me.date_updated, me.yes,
me.name, me.description, me.currency, me.locked, me.skip,
me.uri_part, me.user_id,
yes + currency as weight
FROM ideas me having ((weight < 5)) order by weight;
How can I generate that query in DBIx::Class without using literal SQL like this:
my $query = $rs->search({},
{
'+select' => \[
'yes + currency as weight',
],
rows => 1,
order_by => { -desc => [qw/weight name/] },
having => {
weight => { '<' => $self->yes + $self->currency },
},
});
use Data::Dumper;
warn Dumper($query->as_query);
I tried using -as, however, it seems to only be useful for working with columns generated from functions, as this:
'+select' => {
'yes + currency', '-as' => 'weight'
}
generates an error
"Odd number of elements in anonymous hash at
/data/TGC/lib/TGC/DB/Result/Idea.pm line 105, line 1000.
DBIx::Class::SQLMaker::_recurse_fields(): Malformed select argument -
too many keys in hash: SCALAR(0xbf14c40),weight"
Probably the most idiomatic thing I can think of in SQL Abstract expression, without straining too hard:
#!/usr/bin/env perl
use Modern::Perl;
use MySchema;
use Data::Dumper;
my $schema = MySchema->connect('dbi:SQLite:example.db');
my $rs = $schema->resultset('Sample')->search(
{
weight => { '<' => 5 },
},
{
'+select' => [
{ '' => \'`me`.`yes` + `me`.`currency`', -as => 'weight' }
]
}
);
say Dumper( $rs->as_query() );
Which is a contrived wrapping of the column names, but it does the job, sort of. Just don't know of any way to abstract the + here. But stil:
'(SELECT me.name, me.yes, me.currency, ( me.yes + me.currency ) AS weight FROM sample me WHERE ( weight < ? ))',
Unless you are just going for idiomatic perl, in which case:
{ '' => \(join " + ", qw/`me`.`yes` `me`.`currency`/), -as => 'weight' }
But either way seems a little contrived considering both forms are longer than the literal string.
Also note that weight is referenced in WHERE and not HAVING which is because that will blow up on various SQL engines.
The '+as' should be at the same level as '+select'
$idea = $rs->search({-and => [
name => { '<' => $self->name },
]},
{
'+select' => [
'yes + currency'
],
'+as' => [qw/weight/],
rows => 1,
order_by => ['weight', 'name'],
having => {
weight => { '<' => $self->yes + $self->currency },
},
})->single;

How can I expose many-to-many tag-style relationship in a Catalyst app?

I'm building a database application in Catalyst, using jqGrid to do the messy work of handling the display of data. I've got almost everything working, except for being able to filter search results by "tags". I have three tables, with relationships like this:
package MyApp::Schema::Result::Project;
...
__PACKAGE__->has_many(
"job_flags",
"MyApp::Schema::Result::ProjectFlag",
{ "foreign.project_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
...
__PACKAGE__->many_to_many(flags => 'project_flags', 'flag');
1;
and
package MyApp::Schema::Result::Flag;
...
__PACKAGE__->has_many(
"project_flags",
"MyApp::Schema::Result::ProjectFlag",
{ "foreign.flag_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
...
__PACKAGE__->many_to_many(projects => 'project_flags', 'project');
1;
and finally, the join table
package MyApp::Schema::Result::ProjectFlag;
...
__PACKAGE__->belongs_to(
"flag",
"MyApp::Schema::Result::Flag",
{ id => "flag_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
...
__PACKAGE__->belongs_to(
"project",
"MyApp::Schema::Result::Project",
{ id => "project_id" },
{ is_deferrable => 1, on_delete => "CASCADE", on_update => "CASCADE" },
);
...
1;
In my controller that provides the JSON data to jqGrid, I use Catalyst::TraitFor::Controller::jQuery::jqGrid::Search to translate the request parameters generated by jqGrid into DBIx::Class-style queries:
my $search_filter = $self->jqGrid_search($c->req->params);
my $project_rs = $c->model('DB::Project')->search(
$search_filter, {
join => 'project_flags',
group_by => 'id',
},
);
which is then passed on to the jqGrid page generator:
$project_rs = $self->jqgrid_page($c, $project_rs);
and then I iterate over the result set and build my jqGrid columns.
On the HTML side, I am able to build a JSON string like
{"groupOp":"AND","rules":[{"field":"project_flags.flag_id","op":"eq","data":"2"}]}
and, in this case, show Projects having a row in project_flags with flag id of 2.
I absolutely know I'm not doing this correctly! All of the documentation I can find on Catalyst and DBIx::Class demonstrates similar ideas, but I just can't understand how to apply them to this situation (not that I haven't tried).
How would I go about building "has_flag($flag_id)"-type accessors, and then be able to use them from within jqGrid's API? Where in my Catalyst app would this belong?
One of the ways I'd like to filter is by the lack of a particular flag also.
I've got to be honest with you, I'm not entirely sure I understand your question. It seems to be what you're asking has more to do with DBIx::Class than Catalyst--the latter I know very little about, the former I am learning more about every day. With that in mind, here's my best attempt at answering your question. I am using Mojolicious as the MVC, since that's what I know best.
First, I start by creating a many-to-many database:
CREATE TABLE project(
id INTEGER PRIMARY KEY,
name text
);
CREATE TABLE flag(
id INTEGER PRIMARY KEY,
name text
);
CREATE TABLE project_flag(
project_id integer not null,
flag_id integer not null,
FOREIGN KEY(project_id) REFERENCES project(id),
FOREIGN KEY(flag_id) REFERENCES flag(id)
);
INSERT INTO project (id,name) VALUES (1,'project1');
INSERT INTO project (id,name) VALUES (2,'project2');
INSERT INTO project (id,name) VALUES (3,'project3');
INSERT INTO flag (id,name) VALUES (1,'flag1');
INSERT INTO flag (id,name) VALUES (2,'flag2');
INSERT INTO flag (id,name) VALUES (3,'flag3');
INSERT INTO flag (id,name) VALUES (4,'flag4');
INSERT INTO project_flag (project_id,flag_id) VALUES (1,1);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,2);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,3);
INSERT INTO project_flag (project_id,flag_id) VALUES (1,4);
INSERT INTO project_flag (project_id,flag_id) VALUES (2,1);
INSERT INTO project_flag (project_id,flag_id) VALUES (2,4);
And here is my Perl (Mojolicious) code:
#!/usr/bin/env perl
use Mojolicious::Lite;
use Schema;
helper db => sub {
return Schema->connect('dbi:SQLite:test.db');
};
get '/' => sub {
my $self = shift;
my $rs = $self->db->resultset('Project')->search(
{ 'me.name' => 'project3' },
{
join => { 'project_flags' => 'flag' },
select => ['me.name', 'flag.name'],
as => ['project', 'flag']
}
);
$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
$self->render( json => [ $rs->all ] );
};
app->start;
And here's the JSON output (pretty print) from project1 (has flags related to it):
[
{
"project":"project1",
"flag":"flag1"
},
{
"flag":"flag2",
"project":"project1"
},
{
"project":"project1",
"flag":"flag3"
},
{
"flag":"flag4",
"project":"project1"
}
]
And here is JSON for project3, with no relationship to any flags:
[
{
"project":"project3",
"flag":null
}
]
I put the files on Github, so you can check them out if you'd like.
In your given situation, say they've typed the word 'c++' into the filter, and you want to return everything that has been tagged 'c++', then:
my $rs = $self->db->resultset('Tag')->search(
{ 'me.name' => 'c++' },
{
join => { 'project_flags' => 'project' },
select => ['me.name', 'project.name'],
as => ['tag', 'project']
}
);
$rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
$self->render( json => [ $rs->all ] );
If you wanted to return all columns, use prefetch rather than join. Also, if you'd like to support the Autosearch functionality, change search to search_like, like so:
my $rs = $self->db->resultset('Tag')->search_like(
{ 'me.name' => $search_filter.'%' },
I hope that if I did not answer your question what I have given is at least a push in the right direction.

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"
]);