search by date on DBIx::Class - perl

I have a table in a SQLite database where a column stores file mtimes in epoch seconds.
I would now like to search in that table files that were modified in a certain month?
In raw SQL I would do:
select * from my_table where strftime('%Y-%m', mtime, "unixepoch") = "2009-08"
Is there a way to do this efficiently via DBIx::Class? Is it possible to do
$m->search({ \'strftime('%Y-%m', mtime, "unixepoch")' => "2009-08" })
I tried understanding if there's a way with DBIx::Class::InflateColumn::DateTime but I didn't find it.
Thanks
Simone

The syntax you're looking for is:
$m->search(
\[ q{strftime('%Y-%m', mtime, "unixepoch") = ?}, "2009-08" ]
);
Yes, that's an array-ref-ref. The first element is literal SQL which is permitted to use ? placeholders, and the remaining elements are values to bind to those placeholders, and DBIC will make sure to reorder everything internally so that those values get put into the right place in the bind list when the query runs.
If you need to combine one of these with other criteria, since an arrayrefref isn't a valid hash key, you need to use the -and syntax:
$m->search({
-and => [
foo => 'bar',
\[ q{ SQL }, $bind ],
],
});
Here is the section of the SQL::Abstract docs for this construct.

I would suggest you using search_literal instead:
# assuming $m isa DBIx::Class::ResultSet
$m->search_literal('strftime("%Y%m", mtime, "unixepoch") = ?', '200908');
EDIT: I stand corrected. Please refer to #hobbs' comment and answer.

Related

ZF2 - How to correctly concatenate a select statement containing a CONCAT function

I'm new to Zend and to Postgres. My Users table contains a FirstName column and a LastName column.
I want to query the table using a single 'full name' string; e.g. $search = 'John Sm'.
I'm trying to use the CONCAT_WS function to concatenate the two names from the table, and then compare this with the search string.
My statement
$select->where(array('CONCAT_WS(" ", "u"."FirstName", "u"."LastName") LIKE ?' => array("%$search%")));
I've tried different combinations but can't seem to get the concatenation right.
An example of the statement I want is SELECT * FROM Users WHERE 'firstname lastname' LIKE '%john s%'
This doesn't answer your question, but have you considered making a view for this?
Did you check the latest version of the documentation?
https://framework.zend.com/manual/1.12/en/zend.db.statement.html
If you use array as parameter in your $select->where() it is interpreted as pairs ['column1' => 'value', 'column2' => 'value']. In this solution you can't use functions and combined parts of query.
You can use Zend\Db\Sql\Predicate\Literal(), eg.:
$select->where
->literal('CONCAT_WS(" ", "u"."FirstName", "u"."LastName") LIKE \'%word%\'')
or use Zend\Db\Sql\Predicate\Expression(), eg.:
$select->where
->expression('CONCAT_WS(" ", "u"."FirstName", "u"."LastName") LIKE \'%?%\'', $word)
(second parameter can bee array if variables is more)
In this solution you can build sql WHERE using the same method
$select->where
->equalsTo()
->or
->greatherThan()
->and
->like()
->nest()
->lessThan()
->or
->literal()
->unnest()
...
https://framework.zend.com/manual/2.2/en/modules/zend.db.sql.html#id7
Also you can build as Zend\Db\Sql\Where / Zend\Db\Sql\Predicate as eg.:
$where = new Where();
$where->equalsTo();// and more, more, more, with sub-where inclusive
$select->where->predicate($where);

DBIx::Class - add a read-only synthesized column (defined by SQL) to a result class?

Is it possible to add a 'synthesized' column to a DBIx::Class result class? The value of the synthesized column would be defined by a SQL expression of the current row. For example, if a row had the columns first and last, I could synthesize a new read-only column whose definition is \"me.first || ' ' || me.last" (this is Oracle SQL syntax).
Close to what I want is listed under "COMPUTED COLUMNS" in the DBIx::Class documentation. However, in that case it seems that the column is already defined on the database side. My synthesized column is not in the table schema.
Failing that, is it possible to add my SQL expression to the generated SELECT statement when I search the resultset?
(The above example SQL is misleading - I need to execute a SQL expression that involves database functions so I can't just implement it in perl.)
Perhaps I'm missing something, but I don't see why you can't just add a method to your result class like this:
sub name {
my $self = shift;
return $self->first . ' ' . $self->last;
}
If the calculation must be done on the database side (after your earlier comment), then use the temporary column idea that i suggested, together with some direct SQL. Assuming that you don't wish to search on the field, then something like the following should work:
my $rs = My::Schema->resultset('people');
my #columns_as = $rs->result_source->columns;
my #columns = map { "me.$_" } #columns_as;
my #people = $rs->search(
{ ... where conditions here ... },
{
select => [ #columns, \"me.first || ' ' || me.last" ],  # scalar ref for direct SQL
as => [ #columns_as, 'full_name' ],
order_by => 'full_name',
... etc ...
}
);
# then
print $_->full_name."\n" foreach #people; # now set on the object...
It should theoretically be possible to just specify your additional select and as columns using +select and +as, but I was unable to get these to work properly (this was a year or so ago). Can't recall exactly why now...

Zend Framework: Re-use WHERE clause returned from Zend_Db_Select::getPart()

I have a SELECT object which contains a WHERE.
I can return the WHERE using getPart(Zend_Db_Select::WHERE), this returns something like this:
array
0 => string "(clienttype = 'agent')"
1 => string "AND (nextpayment < (NOW() - INTERVAL 1 DAY))"
This array seems pretty useless as I cannot do this with it
$client->update(array("paymentstatus" => "lapsed"), $where);
Or even put it into another SELECT object. Is there a way of getting a more useful representation of the WHERE statement from the SELECT object?
Thanks
EDIT
The best I have come up with so far is
$where = new Zend_Db_Expr(implode(" ", $select->getPart(Zend_Db_Select::WHERE)));
Your first choice, $client->update(...) would work, if getParts omitted the 'AND' from the second part of the where clause.
I'm pretty sure your only choice is to:
use your second option (probably safest depending on how complex the where clauses are) -or-
iterate through the $where returned and remove the leading 'AND', 'OR' that may exist.

How do I do this search and order_by on a DBIx::Class::ResultSet

Problem definition.
I have multiple clients with multiple users. Each client needs to be able to associate custom data with a user, search, and order by.
Database Solution:
A table Customfields which defines the customfields table. It has an id and name.
It has a has_many relationship with a Userfields table (aka "attributes").
The Userfields table has a userid, customfieldid, content and id.
It belongs_to a Useraccounts table (aka "useraccount") and Customfields (aka "customfield")
Proposed select statement that I want:
This is a select statement that achieves and produces what I need.
SELECT ua.*, (
SELECT content FROM Userfields uf
INNER JOIN Customfields cf
ON cf.id = uf.customfieldid
WHERE cf.name = 'Mothers birthdate'
AND uf.uid=ua.uid
) AS 'Mothers birthdate',
(
SELECT content FROM Userfields uf
INNER JOIN Customfields cf
ON cf.id = uf.customfieldid
WHERE cf.name = 'Join Date' AND
uf.uid=ua.uid
) AS 'Join Date'
FROM UserAccounts ua
ORDER BY 'Mothers birthdate';
In this case their could be anything from 0 ... x sub select statements in the select statement and any one of them or none of them could be wanting to be ordered by.
Question
How do I achieve this with a ->search on my dbix class resultset or how do I achieve the same result with a search on my dbix class resultset?
Here is how I usually select from my Useraccounts table, although I am unsure how to do the complex statement that I want to from here.
my #users = $db->resultset('Useraccounts')->search(
undef,
{
page => $page,
join => 'attributes',
...
});
Thanks for your time.
-pdh
This is really pretty hairy, and any solution isn't going to be pretty, but it does look to be possible if you bend the rules a little bit. Forgive any mistakes I make, as I didn't go and create a schema to test this on, but it's based on the best info I have (and much help from ribasushi).
First, (assuming that your userfields table has a belongs_to relation with the customfields table, called customfield)
my $mbd = $userfields_rs->search(
{
'customfield.name' => 'Mothers birthdate',
'uf.uid' => \'me.uid' # reference to outer query
},
{
join => 'customfield',
alias => 'uf', # don't shadow the 'me' alias here.
}
)->get_column('content')->as_query;
# Subqueries and -as don't currently mix, so hack the SQL ourselves
$mbd->[0] .= q{ AS 'Mothers Birthdate'};
The literal me.uid that uf.uid is being matched against is an unbound variable -- it's the uid field from the query that we're eventually going to put this query into as a subselect. By default DBIC aliases the table that the query is addressing to me; if you gave it a different alias then you would use something diferent here.
Anyway, You could repeat this as_query business with as many different fields as you like, just varying the field-name (if you're smart, you'll write a method to generate them), and put them in an array, so now let's suppose that #field_queries is an array, containing $mbd above as well as another one based on Join Date, and anything you like.
Once you have that, it's as "simple" as...
my $users = $useraccounts_rs->search(
{ }, # any search criteria can go in here,
{
'+select' => [ #field_queries ],
'+as' => [qw/mothers_birthdate join_date/], # this is not SQL AS
order_by => {-asc => 'Mothers birthdate'},
}
);
which will include each of the subqueries into the select.
Now for the sad part: as of right now, this whole thing actually won't work, because subqueries with placeholders don't work properly. So for now you need an additional workaround: instead of 'customfield.name' => 'Mothers birthdate' in the subselect search, do 'customfield.name' => \q{'Mothers birthdate'} -- this is using literal SQL for the field name (BE CAREFUL of SQL injection here!), which will sidestep the placeholder bug. But in the not-too-distant future, that bug will be resolved and the code above will work okay, and we'll update the answer to let you know that's the case.
See DBIx::Class::ResultSource order_by documentation

Get table alias from Zend_Db_Table_Select

I'm working on an Active Record pattern (similar to RoR/Cake) for my Zend Framework library. My question is this: How do I figure out whether a select object is using an alias for a table or not?
$select->from(array("c" => "categories"));
vs.
$select->from("categories");
and I pass this to a "fetch" function which adds additional joins and whatnot to get the row relationships automatically...I want to add some custom sql; either "c.id" or "categories.id" based on how the user used the "from" method.
I know I can use
$parts = $select->getPart(Zend_Db_Select::FROM);
to get the from data as an array, and the table name or alias seems to be in "slot" 0 of said array. Will the table name or alias always be in slot zero? i.e. can I reliably use:
$tableNameOrAlias = $parts[0];
Sorry if this is convolute but hope you can help! :)
Logically, I would think that's how it should work. To be on the safe side, build a few dummy queries using a Select() and dump the part array using print_r or such.
I just performed this test, the alias is the array key, it is not a zero-based numeric array:
$select = $this->db->select()->from(array("c" => "categories","d" => "dummies"));
$parts = $select->getPart(Zend_Db_Select::FROM);
echo '<pre>';
print_r($parts);
echo '</pre>';
Output:
Array
(
[c] => Array
(
[joinType] => inner join
[schema] =>
[tableName] => categories
[joinCondition] =>
)
)
So you would need to reference it as $part["c"]