Nested Select with ZF2 - zend-framework2

Trying to get a nested select using Zend\Db\Sql\Select and can't see anything at all in the documentation or on google.
Wanting to do something like this:
SELECT
table1.*,
(SELECT x,y,z FROM table2 WHERE table2.a = table1.a) as b
FROM table1
Without the nested select, it would look something like this:
$select = new Zend\Db\Sql\Select;
$select
->columns(array(
'*'
))
->from('table1')
ZF1 looked about creating a subSelect item and then adding it as an Expression inside the list of columns but in ZF2 it complains about an Expression needing to be a string.
Edit: The nested-select needs to be as a column as I end up with multiplied rows when using GROUP BY on same column name. This is the correct query I'm trying to get into Zend\Db\Sql\Select:
SELECT
users.id,
(SELECT count(explorations.id) FROM explorations WHERE user_id = users.id) as total_explorations,
count(villages.id)
FROM
users
INNER JOIN
villages
on (villages.user_id = users.id)
GROUP BY
users.id

Ralph Schindler has a repository of different DB patterns that he has specifically implemented in Zend\Db. Here's one for subselects: https://github.com/ralphschindler/Zend_Db-Examples/blob/master/example-20.php
The content is this:
<?php
/** #var $adapter Zend\Db\Adapter\Adapter */
$adapter = include ((file_exists('bootstrap.php')) ? 'bootstrap.php' : 'bootstrap.dist.php');
refresh_data($adapter);
use Zend\Db\Sql;
use Zend\Db\ResultSet\ResultSet;
$sql = new Sql\Sql($adapter);
$subselect = $sql->select();
$subselect->from('artist')
->columns(array('name'))
->join('album', 'artist.id = album.artist_id', array())
->where->greaterThan('release_date', '2005-01-01');
$select = $sql->select();
$select->from('artist')
->order(array('name' => Sql\Select::ORDER_ASCENDING))
->where
->like('name', 'L%')
->AND->in('name', $subselect);
$statement = $sql->prepareStatementForSqlObject($select);
$result = $statement->execute();
$rows = array_values(iterator_to_array($result));
assert_example_works(
count($rows) == 2
&& $rows[0]['name'] == 'Lady Gaga'
&& $rows[1]['name'] == 'Linkin Park'
);
Basically, you can use one select as the value of the predicate of another select.

I would suggest you restructure you SQL query. I'm not sure which database you are using, but if you are using MySQL, the COUNT function can use the DISTINCT keyword. This way you don't count the duplicated ids. I've adjusted your SQL query to what I would use, this way you eliminate the need for inner select.
SELECT
users.id,
COUNT(DISTINCT explorations.id) AS total_explorations,
COUNT(DISTINCT villages.id) AS total_villages
FROM users
INNER JOIN villages ON villages.user_id = users.id
INNER JOIN explorations ON explorations.user_id = users.id
GROUP BY users.id
I haven't run this query, but I'm sure it should work and give you the result you want. Hopefully I'm not misunderstanding your situation. Below is the equivalent Zend Framework 2 select.
$select = $sql->select('users');
$select->columns(array('id'));
$select->join('villages',
'villages.user_id = users.id',
array(
'total_villages' => new Expression("COUNT(DISTINCT villages.id)")
)
);
$select->join('explorations',
'explorations.user_id = users.id',
array(
'total_explorations' => new Expression("COUNT(DISTINCT explorations.id)")
)
);

What you are describing is defined as a JOIN. There are some different join scenarios and i will not cover the differences of them, but the most commons would be INNER JOIN or LEFT JOIN.
And this is indeed to be found inside the ZF2-Documentation of Zend\Db\Sql#Join
The Query would look like this:
Select
t1.*,
t2.field1,
t2.field2,
t2.field3
FROM
tablename1 t1,
tablename2 t2
WHERE
t1.field = t2.field
Looking at the Documentation of ZF2-Documentation of Zend\Db\Sql#Join, i think the Select would look like this:
$select = new \Zend\Db\Sql\Select();
$select->columns(array(
'id',
'title',
// List ALL Columns from TABLE 1 - * is bad/slow!
), true)->from(array(
't1' => 'tablename1'
))->join(
'tablename2',
'id = t1.id',
array(
'username',
'email',
// List ALL Columns from TABLE 2 u want to select
),
$select::JOIN_INNER
)
Another i think: If you don't use the columns() you'd SELECT * but for your own sake, start writing good queries ;) Have more control over your code!
Can't promise that this code works, since i don't use Zend\Db on my own, but using the Documentation on the right point should get you running nonetheless.

I Hope I am getting your problem correctly...
Still not upgraded myself to ZF2 but this is one of the way you can create a nested Select statement in ZF1 if you are using MVC architecture(try it in ZF2 also).
$table1 = new table1();
$table1->select()->from('table1',array('*',
'b' => '(SELECT x,y,z FROM table2 WHERE table2.a = table1.a)',
));
Update:
Got back to this after your comment and realized that the code I've written would not work as you will not be able select multiple columns from another table into a single column(i.e x,y,z in b).
But yes it would work in case you have to perform some agg. function on the other table which gives out a single column. e.g.
$table1 = new table1();
$table1->select()->from('table1',array('*',
'b' => '(count (*) FROM table2 WHERE table2.a = table1.a)',
));
So this would work.
This way you can get some of the columns with some function performed on them.
And the rest of the columns from the other table(table2) you can get using a join.

Related

Getting Conditional Count in Join with Laravel Query Builder

I am trying to achieve the following with Laravel Query builder.
I have a table called deals . Below is the basic schema
id
deal_id
merchant_id
status
deal_text
timestamps
I also have another table called merchants whose schema is
id
merchant_id
merchant_name
about
timestamps
Currently I am getting deals using the following query
$deals = DB::table('deals')
-> join ('merchants', 'deals.merchant_id', '=', 'merchants.merchant_id')
-> where ('merchant_url_text', $merchant_url_text)
-> get();
Since only 1 merchant is associated with a deal, I am getting deals and related merchant info with the query.
Now I have a 3rd table called tbl_deal_votes. Its schema looks like
id
deal_id
vote (1 if voted up, 0 if voted down)
timestamps
What I want to do is join this 3rd table (on deal_id) to my existing query and be able to also get the upvotes and down votes each deal has received.
To do this in a single query you'll probably need to use SQL subqueries, which doesn't seem to have good fluent query support in Laravel 4/5. Since you're not using Eloquent objects, the raw SQL is probably easiest to read. (Note the below example ignores your deals.deal_id and merchants.merchant_id columns, which can likely be dropped. Instead it just uses your deals.id and merchants.id fields by convention.)
$deals = DB::select(
DB::raw('
SELECT
deals.id AS deal_id,
deals.status,
deals.deal_text,
merchants.id AS merchant_id,
merchants.merchant_name,
merchants.about,
COALESCE(tbl_upvotes.upvotes_count, 0) AS upvotes_count,
COALESCE(tbl_downvotes.downvotes_count, 0) AS downvotes_count
FROM
deals
JOIN merchants ON (merchants.id = deals.merchant_id)
LEFT JOIN (
SELECT deal_id, count(*) AS upvotes_count
FROM tbl_deal_votes
WHERE vote = 1 && deal_id
GROUP BY deal_id
) tbl_upvotes ON (tbl_upvotes.deal_id = deals.id)
LEFT JOIN (
SELECT deal_id, count(*) AS downvotes_count
FROM tbl_deal_votes
WHERE vote = 0
GROUP BY deal_id
) tbl_downvotes ON (tbl_downvotes.deal_id = deals.id)
')
);
If you'd prefer to use fluent, this should work:
$upvotes_subquery = '
SELECT deal_id, count(*) AS upvotes_count
FROM tbl_deal_votes
WHERE vote = 1
GROUP BY deal_id';
$downvotes_subquery = '
SELECT deal_id, count(*) AS downvotes_count
FROM tbl_deal_votes
WHERE vote = 0
GROUP BY deal_id';
$deals = DB::table('deals')
->select([
DB::raw('deals.id AS deal_id'),
'deals.status',
'deals.deal_text',
DB::raw('merchants.id AS merchant_id'),
'merchants.merchant_name',
'merchants.about',
DB::raw('COALESCE(tbl_upvotes.upvotes_count, 0) AS upvotes_count'),
DB::raw('COALESCE(tbl_downvotes.downvotes_count, 0) AS downvotes_count')
])
->join('merchants', 'merchants.id', '=', 'deals.merchant_id')
->leftJoin(DB::raw('(' . $upvotes_subquery . ') tbl_upvotes'), function($join) {
$join->on('tbl_upvotes.deal_id', '=', 'deals.id');
})
->leftJoin(DB::raw('(' . $downvotes_subquery . ') tbl_downvotes'), function($join) {
$join->on('tbl_downvotes.deal_id', '=', 'deals.id');
})
->get();
A few notes about the fluent query:
Used the DB::raw() method to rename a few selected columns.
Otherwise, there would have been a conflict between deals.id
and merchants.id in the results.
Used COALESCE to default null votes to 0.
Split the subqueries into separate PHP strings to improve readability.
Used left joins for the subqueries so deals with no upvotes/downvotes still show up.

How to build inner join in Rails with conditions?

I've a model StockUpdate which keeps track of stocks for every product for a store. Table attributes are: :product_id, :stock, :store_id. I was trying to find out last entry for every product for a given store. According to that I build my query in PGAdmin which is given below and it's working fine. I'm new in Rails and I don't know how to represent it in Model. Please help.
SELECT a.*
FROM stock_updates a
INNER JOIN
(
SELECT product_id, MAX(id) max_id
FROM stock_updates where store_id = 9 and stock > 0
GROUP BY product_id
) b ON a.product_id = b.product_id AND
a.id = b.max_id
I does not clearly understand what you want to do, but I think you can do something like this:
class StockUpdate < ActiveRecord::Base
scope :a_good_name, -> { joins(:product).where('store_id = ? and stock > ?', 9, 0) }
end
You can all call StoclUpdate.a_good_name.explain to check the generated sql
What you need is really simple and can be easily accomplished with 2 queries. Otherwise it becomes very complicated in a single query (it's still doable though):
store_ids = [0, 9]
latest_stock_update_ids = StockUpdate.
where(store_id: store_ids).
group(:product_id).
maximum(:id).
values
StockUpdate.where(id: latest_stock_update_ids)
Two queries, without any joins necessary. The same could be possible with a single query too. But like your original code, it would include subqueries.
Something like this should work:
StockUpdate.
where(store_id: store_ids).
where("stock_updates.id = (
SELECT MAX(su.id) FROM stock_updates AS su WHERE (
su.product_id = stock_updates.product_id
)
)
")
Or perhaps:
StockUpdate.where("id IN (
SELECT MAX(su.id) FROM stock_updates AS su GROUP BY su.product_id
)")
And to answer your original question, you can manually specify a joins like so:
Model1.joins("INNER JOINS #{Model2.table_name} ON #{conditions}")
# That INNER JOINS can also be LEFT OUTER JOIN, etc.

COUNT and GROUP BY using Zend Framework 2 and tableGateway

In Zend Framework 2, using tableGateway, I want to run the following SQL query:
SELECT categories.category_name, COUNT(forums.forum_id)
FROM categories LEFT JOIN forums
ON categories.category_id = forums.category_id
GROUP BY categories.category_name;
Problem is that I simply don't know how to do it. I know how to use $select->join() for example, but I can't figure out how to also do a COUNT and GROUP BY.
What I want with my SQL: I have 2 tables; categories and forums. I want to select all the categories from categories and for each category I want the amount of forums.
Someone on another forum gave me the correct answer, and this works for me. Thought I would share it in case anyone else is having a similar question. Here is how I have it now:
use Zend\Db\Sql\Expression;
$resultSet = $this->tableGateway->select(function (Select $select)
{
// Select columns and count the forums.
$select->columns(array(
'category_name',
'forumsCount' => new Expression('COUNT(forums.forum_id)')
));
// Left-join with the forums table.
$select->join('forums', 'categories.category_id = forums.category_id', array(), 'left');
// Group by the category name.
$select->group('categories.category_name');
});
return $resultSet;
Your query looks right. Does it work as expected when you run it directly on the database.
I think you might just need to execute the raw query using an adapter.
$sql = "SELECT categories.category_name, COUNT(forums.forum_id) FROM categories LEFT JOIN forums ON categories.category_id = forums.category_id GROUP BY categories.category_name";
$statement = $this->adapter->query($sql);
return $statement->execute();

Join Non ContentPart Table to ContentPart Table Using Orchard HQL API

I am trying to perform a simple join between two different tables using the Orchard HQL API. The problem is that one of the tables is not a ContentPartTable. Is this possible??
Here is what it would look like in regular SQL:
Select * From ItemPartRecord
Join ItemRecord
On ItemRecord.ItemId = ItemPartRecord.ItemId
Where ItemRecord.Price Between 1000 and 10000
How exactly could I go about doing this?
If anyone is wondering how to do this:
//Join the non content part table
var defaultHqlQuery = query as DefaultHqlQuery;
var fiJoins = typeof(DefaultHqlQuery).GetField("_joins", BindingFlags.Instance | BindingFlags.NonPublic);
var joins = fiJoins.GetValue(defaultHqlQuery) as List<Tuple<IAlias, Join>>;
joins.Add(new Tuple<IAlias, Join>(new Alias("ExampleNamespace.Data.Models"), new Join("ExampleRecord", "ExampleAlias", ",")));
Action<IHqlExpressionFactory> joinOn = predicate => predicate.EqProperty("valueToJoinOn", "aliasToJoinOn.valueToJoinOn");
query = query.Where(
alias => alias.Named("ExampleAlias"),
joinOn
);

ASP.NET MVC & EF4 Entity Framework - Are there any performance concerns in using the entities vs retrieving only the fields i need?

Lets say we have 3 tables, Users, Products, Purchases.
There is a view that needs to display the purchases made by a user.
I could lookup the data required by doing:
from p in DBSet<Purchases>.Include("User").Include("Product") select p;
However, I am concern that this may have a performance impact because it will retrieve the full objects.
Alternatively, I could select only the fields i need:
from p in DBSet<Purchases>.Include("User").Include("Product") select new SimplePurchaseInfo() { UserName = p.User.name, Userid = p.User.Id, ProductName = p.Product.Name ... etc };
So my question is:
Whats the best practice in doing this?
== EDIT
Thanks for all the replies.
[QUESTION 1]: I want to know whether all views should work with flat ViewModels with very specific data for that view, or should the ViewModels contain the entity objects.
Real example: User reviews Products
var query = from dr in productRepository.FindAllReviews()
where dr.User.UserId = 'userid'
select dr;
string sql = ((ObjectQuery)query).ToTraceString();
SELECT [Extent1].[ProductId] AS [ProductId],
[Extent1].[Comment] AS [Comment],
[Extent1].[CreatedTime] AS [CreatedTime],
[Extent1].[Id] AS [Id],
[Extent1].[Rating] AS [Rating],
[Extent1].[UserId] AS [UserId],
[Extent3].[CreatedTime] AS [CreatedTime1],
[Extent3].[CreatorId] AS [CreatorId],
[Extent3].[Description] AS [Description],
[Extent3].[Id] AS [Id1],
[Extent3].[Name] AS [Name],
[Extent3].[Price] AS [Price],
[Extent3].[Rating] AS [Rating1],
[Extent3].[ShopId] AS [ShopId],
[Extent3].[Thumbnail] AS [Thumbnail],
[Extent3].[Creator_UserId] AS [Creator_UserId],
[Extent4].[Comment] AS [Comment1],
[Extent4].[DateCreated] AS [DateCreated],
[Extent4].[DateLastActivity] AS [DateLastActivity],
[Extent4].[DateLastLogin] AS [DateLastLogin],
[Extent4].[DateLastPasswordChange] AS [DateLastPasswordChange],
[Extent4].[Email] AS [Email],
[Extent4].[Enabled] AS [Enabled],
[Extent4].[PasswordHash] AS [PasswordHash],
[Extent4].[PasswordSalt] AS [PasswordSalt],
[Extent4].[ScreenName] AS [ScreenName],
[Extent4].[Thumbnail] AS [Thumbnail1],
[Extent4].[UserId] AS [UserId1],
[Extent4].[UserName] AS [UserName]
FROM [ProductReviews] AS [Extent1]
INNER JOIN [Users] AS [Extent2] ON [Extent1].[UserId] = [Extent2].[UserId]
LEFT OUTER JOIN [Products] AS [Extent3] ON [Extent1].[ProductId] = [Extent3].[Id]
LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent1].[UserId] = [Extent4].[UserId]
WHERE N'615005822' = [Extent2].[UserId]
or
from d in productRepository.FindAllProducts()
from dr in d.ProductReviews
where dr.User.UserId == 'userid'
orderby dr.CreatedTime
select new ProductReviewInfo()
{
product = new SimpleProductInfo() { Id = d.Id, Name = d.Name, Thumbnail = d.Thumbnail, Rating = d.Rating },
Rating = dr.Rating,
Comment = dr.Comment,
UserId = dr.UserId,
UserScreenName = dr.User.ScreenName,
UserThumbnail = dr.User.Thumbnail,
CreateTime = dr.CreatedTime
};
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name],
[Extent1].[Thumbnail] AS [Thumbnail],
[Extent1].[Rating] AS [Rating],
[Extent2].[Rating] AS [Rating1],
[Extent2].[Comment] AS [Comment],
[Extent2].[UserId] AS [UserId],
[Extent4].[ScreenName] AS [ScreenName],
[Extent4].[Thumbnail] AS [Thumbnail1],
[Extent2].[CreatedTime] AS [CreatedTime]
FROM [Products] AS [Extent1]
INNER JOIN [ProductReviews] AS [Extent2] ON [Extent1].[Id] = [Extent2].[ProductId]
INNER JOIN [Users] AS [Extent3] ON [Extent2].[UserId] = [Extent3].[UserId]
LEFT OUTER JOIN [Users] AS [Extent4] ON [Extent2].[UserId] = [Extent4].[UserId]
WHERE N'userid' = [Extent3].[UserId]
ORDER BY [Extent2].[CreatedTime] ASC
[QUESTION 2]: Whats with the ugly outer joins?
In general, only retrieve what you need, but keep in mind to retrieve enough information so your application is not too chatty, so if you can batch a bunch of things together, do so, otherwise you'll pay network traffic cost everytime you need to go back to the database and retrieve some more stuffs.
In this case, assuming you will only need those info, I would go with the second approach (if that's what you really need).
Eager loading with .Include doesn't really play nice when you want filtering (or ordering for that matter).
That first query is basically this:
select p.*, u.*, p2.*
from products p
left outer join users u on p.userid = u.userid
left outer join purchases p2 on p.productid = p2.productid
where u.userid == #p1
Is that really what you want?
There is a view that needs to display the purchases made by a user.
Well then why are you including "Product"?
Shouldn't it just be:
from p in DBSet<Purchases>.Include("User") select p;
Your second query will error. You must project to an entity on the model, or an anonymous type - not a random class/DTO.
To be honest, the easiest and most well performing option in your current scenario is to query on the FK itself:
var purchasesForUser = DBSet<Purchases>.Where(x => x.UserId == userId);
That should produce:
select p.*
from products p
where p.UserId == #p1
The above query of course requires you to include the foreign keys in the model.
If you don't have the FK's in your model, then you'll need more LINQ-Entities trickery in the form of anonymous type projection.
Overall, don't go out looking to optimize. Create queries which align with the scenario/business requirement, then optimize if necessary - or look for alternatives to LINQ-Entities, such as stored procedures, views or compiled queries.
Remember: premature optimization is the root of all evil.
*EDIT - In response to Question Update *
[QUESTION 1]: I want to know whether all views should work with flat ViewModels with very specific data for that view, or should the ViewModels contain the entity objects.
Yes - ViewModel's should only contain what is required for that View. Otherwise why have the ViewModel? You may as well bind straight to the EF model. So, setup the ViewModel which only the fields it needs for the view.
[QUESTION 2]: What's with the ugly outer joins?
That is default behaviour for .Include. .Include always produces a left outer join.
I think the second query will throw exception because you can't map result to unmapped .NET type in Linq-to-entities. You have to return annonymous type and map it to your object in Linq-to-objects or you have to use some advanced concepts for projections - QueryView (projections in ESQL) or DefiningQuery (custom SQL query mapped to new readonly entity).
Generally it is more about design of your entities. If you select single small entity it is not a big difference to load it all instead of projection. If you are selecting list of entities you should consider projections - expecially if tables contains columns like nvarchar(max) or varbinar(max) which are not needed in your result!
Both create almost the same query: select from one table, with two inner joins. The only thing that changes from a database perspective is the amount of fields returned, but that shouldn't really matter that much.
I think here DRY wins from a performance hit (if it even exists): so my call is go for the first option.

Resources