How to do Subquery for same Table in Yii2 Eloquent - yii

how to create this query using model in yii2
select *,p1.plan_id from product p1
where id in (select max(p2.id) from product p2 where p2.plan_id = p1.plan_id)
form the following table Product
id product_name plan_id
-------------------------------
1 bottle 1
2 book 2
3 book 2
4 bottle 1
5 notbook 3

You could refactor you query using an inner join eg:
$sql = "select *
from product p1
inner join (
select plan_id, max(id) max_id
from product
group by plain_id
) t on t.plan_id = p1.plan_id and p1.id = t.max_id";
and in some case could be useful findBySql, assuming your model is name Product
$models = Product::findBySql($sql)->all();

This should be the exact query you would do, but it won't work for MySQL:
// select *, p1.plan_id from product p1
// where id in (select max(p2.id) from product p2 where p2.plan_id = p1.plan_id)
$subquery = (new \yii\db\Query)->select('[[p2]].[[id]]')
->from(['p2' => 'product'])
->where('[[p2]].[[plan_id]] = [[p1]].[[plan_id]]')
->orderBy('[[p2]].[[id]] DESC')->limit(1); // equiv to max(p2.id)
$query = (new \yii\db\Query)->from(['p1' => 'product'])
->where(['p1.id' => $subquery])
->one();
// PS: Yii will quote fields wrapped in `[[]]` double square-brackets.
Using joins
So, you should use innerJoin (or other variants) to achieve the same result working for any database:
$query = (new \yii\db\Query)->from(['p1' => 'product'])
->innerJoin(['product p2'], '[[p2]].[[plan_id]] = [[p1]].[[plan_id]]')
->orderBy('[[p2]].[[id]] DESC')->limit(1); // equiv to max(p2.id)
->one();

Related

How to get max value from one table, then join another table using Entity Framework 6

I have 2 tables stock and stockprice. I want to get all stocks with their last updated price using Entity Framework 6.
I first select * from stock, then use foreach to get last updated price for each symbol. How can do that with just one query using entity framework 6?
Stock:
Id Symbol
-------------
1 AAPL
2 MSFT
Stockprice:
Id Symbol Price LastUpdatedDate
-------------------------------------
1 AAPL 120 11/7/2016
2 AAPL 121 11/2/2016
3 AAPL 123 11/3/2016
4 MSFT 111 11/2/2016
5 MSFT 101 11/6/2016
I want to get last updated price for each symbol.
AAPL 120 11/7/2016
MSFT 101 11/6/2016
if you have list of object with property
Id,Symbol,Price,LastUpdatedDate
let say
List<ItemPrice> itemPrice
then you can do like this
var lastUpdated= from item in itemPrice
group item by item.Symbol into itemGroup
let maxDate = itemGroup.Max(gt => gt.LastUpdatedDate)
select new RegistrationModel
{
Symbol = itemGroup.Key,
LastUpdatedDate= maxDate,
Price= itemGroup.First(gt2 => gt2.ExpiryDateAD == maxDate).Price
}).ToList()
i hope it may help you
Try this
using(var db= new DbContext())
{
//This will fetch all your stock and include all corresponding StockPrices.
var stockWithPrices = db.Stock
.Select(x=> new
{
Name = x. Name
Price = x.StockPrices.OrderByDescending(y=>y.LastUpdatedDate).FirstOrDefault().Price
Date = x.StockPrices.OrderByDescending(y=>y.LastUpdatedDate).FirstOrDefault().Date
}.ToList();
}
Assuming your model look like this
public partial class Stock
{
public int Id {get;set;}
public string Symbol {get;set;}
public ICollection<StockPrice> StockPrices {get;set;}
}
Note: I realize you prefer a pure EF solution but this is another way to do it and I think it will be more scalable and supportable.
Query :
Select A.Symbol, C.Price, A.MaxDate
(
Select Max(LastUpdatedDate) as MaxDate, Symbol
FROM stockprice A INNER JOIN stock B on A.Symbol = B.Symbol
Group By Symbol
) A
INNER JOIN StockPrice C on A.Symbol = C.Symbol AND A.MaxDate = C.LastUpdatedDate
To make a View :
Create View vStockPrice AS
Select A.Symbol, C.Price, A.MaxDate
(
Select Max(LastUpdatedDate) as MaxDate, Symbol
FROM stockprice A INNER JOIN stock B on A.Symbol = B.Symbol
Group By Symbol
) A
INNER JOIN StockPrice C on A.Symbol = C.Symbol AND A.MaxDate = C.LastUpdatedDate
Go
To Do it using Stored Procedures:
Create Procedure usp_StockPrice AS
Begin
Select A.Symbol, C.Price, A.MaxDate
(
Select Max(LastUpdatedDate) as MaxDate, Symbol
FROM stockprice A INNER JOIN stock B on A.Symbol = B.Symbol
Group By Symbol
) A
INNER JOIN StockPrice C on A.Symbol = C.Symbol AND A.MaxDate = C.LastUpdatedDate
END
Then you simply query the view like you would query any table using EF or use the process to call a stored procedure.
var query = from rows in vStockPrice select ..... ;
The advantage of doing it in View or SP is that you can handle any future changes to your requirements by simply altering the base query. (Say you want to ignore penny stocks so all it will take is a where clause : where price > 1.0 ). You will not have to recompile your C# code (and your DBA will like this approach for other reasons !!)
Let me know if you want me to expand (or you think this is not relevant )

LINQ vs. SQL Joins And Groupings (w/ Specific Case)

In SQL, when you do a bunch of joins, it treats all of the joined objects as one "super-object" to be selected from. This remains the case when you group by a particular column, as long as you include anything you select in the grouping (unless it is produced by the grouping, such as summing a bunch of int columns).
In LINQ, you can similarly do a bunch of joins in a row, and select from them. However, when you perform a grouping, it behaves differently. The syntax in query-style LINQ only allows for grouping a single table (i.e., one of your joins), discarding the others.
For an example case suppose we have a few tables:
Request
-------
int ID (PK)
datetime Created
int StatusID (FK)
Item
----
int ID (PK)
string Name
RequestItem
-----------
int ID (PK)
int ItemID (FK)
int RequestID (FK)
int Quantity
Inventory
---------
int ID (PK)
int ItemID (FK)
int Quantity
LU_Status
---------
int ID (PK)
string Description
In our example, LU_Status has three values in the database:
1 - New
2 - Approved
3 - Completed
This is a simplified version of the actual situation that lead me to this question. Given this schema, the need is to produce a report that shows the number of requested items (status not "Completed"), approved items (status "Approved"), distributed items (status "Completed"), and the number of items in stock (from Inventory), all grouped by the item. If this is a bit vague take a look at the SQL or let me know and I'll try to make it clearer.
In SQL I might do this:
select i.Name,
Requested = sum(ri.Quantity),
Approved = sum(case when r.StatusID = 2 then ri.Quantity else 0 end)
Distributed = sum(case when r.StatusID = 3 then ri.Quantity else 0 end)
Storage = sum(Storage)
from RequestItem as ri
inner join Request as r on r.ID = ri.RequestID
inner join Item as i on i.ID = ri.ItemID
inner join (select ItemID, Storage = sum(Quantity)
from Inventory
group by ItemID)
as inv on inv.ItemID = ri.ItemID
group by i.Name
This produces the desired result.
I began to rewrite this in LINQ, and got so far as:
var result = from ri in RequestItem
join r in Request on ri.RequestID equals r.ID
join i in Item on ri.ItemID equals i.ID
join x in (from inv in Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) })
on ri.ItemID equals x.ItemID
group...????
At this point everything had been going smoothly, but I realized that I couldn't simply group by i.Name like I did in SQL. In fact, there seemed to be no way to group all of the joined things together so that I could select the necessary things from them, so I was forced to stop there.. I understand how to use the group syntax in simpler situations (see the subquery), but if there's a way to do this sort of grouping in LINQ I'm not seeing it, and searching around here and elsewhere has not illuminated me.
Is this a shortcoming of LINQ, or am I missing something?
You can create an anonymous type in a grouping that contains all data you need:
var result = from ri in RequestItem
join r in Request on ri.RequestID equals r.ID
join i in Item on ri.ItemID equals i.ID
join x in (from inv in Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) })
on ri.ItemID equals x.ItemID
group new
{
i.Name,
r.StatusId,
ri.Quantity,
x.Storage,
}
by i.Name into grp
select new
{
grp.Key,
Requested = grp.Where(x => x.StatusID == 2).Sum(x => x.Quantity),
Distributed = grp.Where(x => x.StatusID == 3).Sum(x => x.Quantity),
Storage = grp.Sum(x => x.Storage)
}
(not tested, obviously, but it should be close).
The easiest way is to use group new { ... } by ... construct and include all the items from the joins that you need later inside the { ... }, like this
var query =
from ri in db.RequestItem
join r in db.Request on ri.RequestID equals r.ID
join i in db.Item on ri.ItemID equals i.ID
join x in (from inv in db.Inventory
group inv by inv.ItemID into g
select new { ItemID = g.Key, Storage = g.Sum(x => x.Quantity) }
) on ri.ItemID equals x.ItemID
group new { ri, r, i, x } by i.Name into g
select new
{
Name = g.Key,
Requested = g.Sum(e => e.ri.Quantity),
Approved = g.Sum(e => e.r.StatusID == 2 ? e.ri.Quantity : 0),
Distributed = g.Sum(e => e.r.StatusID == 3 ? e.ri.Quantity : 0),
Storage = g.Sum(e => e.x.Storage)
};

$wpdb->get_results with UNIONs in query

when running each of the following SELECTs on their own (without UNION) I´m getting results as expected. I don´t get any results when using UNION.
Any ideas why this doesn´t work?
$query = "
(SELECT * FROM projects WHERE public='1')
UNION
(SELECT * FROM projects JOIN project_region ON projects.id_project = project_region.id_project
JOIN user ON user.id_region = project_region.id_region WHERE user.user_id = {$current_user->ID})
UNION
(SELECT * FROM projects JOIN project_user ON projects.id_project = project_user.id_project
WHERE project_user.user_id = {$current_user->ID})
";
$projects = $wpdb->get_results($query);
if ($projects) {
foreach ($projects as $project) {
// output results
}
}
In UNION you need to have this same column number, column names for each union query. So in first query you have columns from projects table, but in second query you have columns from projects, project_region, and user tables.
The MYSQL error was *Column 'id_project' in field list is ambiguous*
So the working query looks like this:
$query = "
(SELECT id_project, name FROM projects WHERE public='1')
UNION
(SELECT projects.id_project, projects.name FROM projects JOIN project_region ON projects.id_project = project_region.id_project
JOIN user ON user.id_region = project_region.id_region WHERE user.user_id = {$current_user->ID})
UNION
(SELECT projects.id_project, projects.name FROM projects JOIN project_user ON projects.id_project = project_user.id_project
WHERE project_user.user_id = {$current_user->ID})
";
i found the solution here:
http://sqlzoo.net/howto/source/z.dir/err918/mysql

Problems with Linq-to-SQL join query

var grid = new System.Web.UI.WebControls.GridView();
grid.DataSource = from booking in bookings
join f in getallAttendees on booking.UserID equals f.UserID into fg
from fgi in fg.DefaultIfEmpty() //Where(f => f.EventID == booking.EventID)
where
booking.EventID == id
select new
{
EventID = booking.EventID,
UserID = booking.UserID,
TrackName = booking.Name,
BookingStatus = booking.StatusID,
AttendeeName = booking.FirstName,
// name = account.FirstName,
AmountPaid = booking.Cost,
AttendeeAddress = booking.DeliveryAdd1,
City = booking.DeliveryCity,
Postcode = booking.Postcode,
Date = booking.DateAdded,
product = fgi == null ? null : fgi.productPurchased
};
Booking table design:
ID, EventID, UserID, BookingStatus, AmountPaid, AttendeeAddress, City, Date
Product table design:
ID, EventID, SecondDriver, Apples, Bananas, SecondItem
The association between booking and product table is many to many and the foreign key is UserID
The above Linq-to-SQL query returns the data of only users whose booking.UserID equals att.UserID, what I want is to return all the users of that particular event also with apples and banana fields if they have bought else fill null in that columns.
Any possible solutions or examples will be highly appreciated.
I think you are looking for an equivalent to a Left Join. You can accomplish this by using the DefaultIfEmpty() extension method. Modify your join statement and add another from in like so:
join at in getallAttendees on booking.UserID equals at.UserID into attg
from att in attg
In your select statement you can use the ternary operator ? : like so:
SecondItem = att == null ? null : att.SecondItem,
Edit
Your bookings and getallAttendees IQueryable need to come from the same context if you want to join them together.
Otherwise you will need to use getallAttendees.ToList() and bookings.ToList()

NHibernate + Criterias + DetachedCriteria

I got this a problem, I need translate this SQL Query to Criteria, but I´m getting some troubles, any one can give a hand?
SELECT * FROM Ref
INNER JOIN Product Prod on Ref.id = Prod.id_referencia
AND Prod.ProdDtAlteracao = (SELECT MAX(Prod2.ProdDtAlteracao) FROM Product Prod2 WHERE Prod.ProdCod = Prod2.ProdCod)
Assuming Ref has a bag/list property called "products" which maps to Product...
session.CreateCriteria<Ref>("r")
.CreateAlias("products", "p", InnerJoin)
.Add(Subqueries.PropertyEq("p.ProdDtAlteracao", DetachedCriteria.For<Product>("p2")
.SetProjection(Projections.Max("p2.ProdDtAlteracao"))
.Add(Restrictions.EqProperty("p.ProdCod", "p2.ProdCod"))))
.List<Ref>();
query would look like
SELECT ...
FROM Ref
INNER JOIN Product Prod on Ref.id = Prod.id_referencia
WHERE Prod.ProdDtAlteracao = (
SELECT MAX(Prod2.ProdDtAlteracao)
FROM Product Prod2
WHERE Prod.ProdCod = Prod2.ProdCod
)