Get table alias from Zend_Db_Table_Select - zend-framework

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

Related

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...

Select unique fields from array using MongoDB

I have a MongoDB structure which currently looks like this:
[campaigns] => Array (
[0] => Array (
[campaign_id] => 4e8cba7a0b7aabea08000006
[short_code] => IHEQnP
[users] => Array (
)
)
[1] => Array (
[campaign_id] => 4e8ccf7c0b7aabe508000007
[short_code] => QLU_IY
[users] => Array (
)
)
)
What I would like to be able to do, is search for the short code, and just have the relevant array returned. I initially tried:
db.users.find({'campaigns.short_code':'IHEQnP'}, {'campaigns.campaign_id':1})
However that returns all the arrays, as opposed to just the one (or field) that I want.
Is there a way in Mongo to get the correct array (or even field associated with the array)? Or is that something I would have to do on the server? Am using the lithium framework to retrieve the results (in case it helps).
Thanks in advance :)
Dan
When you use a criteria like campaigns.short_code you are still searching the collection, the campaigns is just a property of a document, your find returns documents.
So given this structure you can not achieve what you want directly by a query.
Arrays in MongoDb can be sliced, but not sorted:
db.users.find({}, {campaigns: { $slice : 1}})
This would give you the first campaign, but as you cant sort it so IHEQnP is at top its of no help in this situation.
Read more here.
You could however filter this quite simple in Lithium after retrieving the full document:
$id = 'id to match against';
$result = $user->campaigns->find(function($model) use ($id) {
return $model->campaign_id === $id
});
See docs for Entity::find here
My solution would be to keep it in User if the amount of campaigns is low (fast to sort and filter in PHP as long as document size isnt too big).
If this is expected to grow then look at moving it to its own model/collection or re-think how you modelled your data.

Zend_Db_Adapter_Mysqli::fetchAssoc() I don't want primary keys as array indexes!

According to ZF documentation when using fetchAssoc() the first column in the result set must contain unique values, or else rows with duplicate values in the first column will overwrite previous data.
I don't want this, I want my array to be indexed 0,1,2,3... I don't need rows to be unique because I won't modify them and won't save them back to the DB.
According to ZF documentation fetchAll() (when using the default fetch mode, which is in fact FETCH_ASSOC) is equivalent to fetchAssoc(). BUT IT'S NOT.
I've used print_r()function to reveal the truth.
print_r($db->fetchAll('select col1, col2 from table'));
prints
Array
(
[0] => Array
(
[col1] => 1
[col2] => 2
)
)
So:
fetchAll() is what I wanted.
There's a bug in ZF documentation
From http://framework.zend.com/manual/1.11/en/zend.db.adapter.html
The fetchAssoc() method returns data in an array of associative arrays, regardless of what value you have set for the fetch mode, **using the first column as the array index**.
So if you put
$result = $db->fetchAssoc(
'SELECT some_column, other_column FROM table'
);
you'll have as result an array like this
$result['some_column']['other_column']

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

Best way to do an Inner Join using the Zend Framework?

It seems like there's a few different ways to join two tables using the Zend Framework, but I've never done it before so I don't know which is the best way to do it.
This is what I'm trying to do...
I have 3 tables in my database:
users
( id , name )
groups
( id , name )
group_members
( id , group_id , user_id )
I'm trying to look up the groups that a user belongs to and display that to the user. This SQL statement pretty much does the job (though there may be a better way to write it). It only returns the columns I'm concerned with which are the group's id and title.
SELECT groups.id, groups.title
FROM group_members
INNER JOIN groups
ON groups.id = group_members.group_id
WHERE user_id = $userId
How can I do this with the Zend Framework?
Finally figured out how to do it. If you've got a better way, please let me know.
$db = Zend_Db_Table::getDefaultAdapter(); //set in my config file
$select = new Zend_Db_Select($db);
$select->from('groups', array('id', 'title')) //the array specifies which columns I want returned in my result set
->joinInner(
'group_members',
'groups.id = group_members.group_id',
array()) //by specifying an empty array, I am saying that I don't care about the columns from this table
->where('user_id = ?', $userId);
$resultSet = $db->fetchAll($select);
This will return a table with only the id and title columns. The empty array() was the key to removing the columns I didn't care about. I could then do something with the result set.
foreach ($resultSet as $row) {
//do something with $row->id or $row->title
}
No need to using Join,we can use Zend_Db_Table instead for the reason about the MVC pattern. I got this idea form here,#10 by Filip.(maybe they call this "Table Data Gateway"?)