Fetch an association whether it exists or not - doctrine

I perform a simple query like this, but loose all the objects that do not have an association with episodes:
$query = $this->getEntityManager()
->createQuery('
SELECT p,e
FROM AcmeDemoBundle:Place p
JOIN p.episodes e
WHERE p.id = :id'
)
->setParameter('id',$id);
This is a simple asso:
/**
* #ORM\OneToMany(targetEntity="Episode", mappedBy="place")
*/
protected $episodes;
My query automatically discards the objects that have an empty $episodes Collection. Does anyone know why? I am sure it makes sense but I can't figure this out.
Is there a way to fetch the object anyway whether there is or there is not the asso episodes?
Many thanks.

I think you want a LEFT JOIN instead of just a JOIN.
JOIN gives only records from the left that also have a record on the right.
LEFT JOIN gives records from the left regardless of if they also have a record on the right.

Related

Joining multiple tables on an already joined table using Eloquent

I have the following which brings back all users in a group along with their posts.
$group = Group::where('id', $id)->with('users.posts')->firstOrFail();
However, what I need is an additional join on the users to bring back additional (hasMany) information.
What I want is something like this (although this doesn't work)
$group = Group::where('id', $id)->with('users.posts,houses')->firstOrFail();
The sql would look something like
SELECT * FROM groups
JOIN group_users ON groups.id = group_users.group_id
JOIN users ON users.id = group_users.user_id
JOIN posts ON posts.user_id = users.id
JOIN house_users ON house_users.user_id = users.id
JOIN houses ON houses.id = house_users.house_id
WHERE groups.id = 123
If you pass a single argument to with(), it will look for a relationship with a matching name. Using a single string with a comma won't work as it won't parse and respect it. Since you're trying to use multiple relationships, this needs to be multiple signature, which there are a couple ways to accomplish.
First, array syntax:
->with(["users.posts", "houses"])
Second, multiple arguments:
->with("users.posts", "houses")
Either method will specify that you want multiple relationships loaded to your initial query; preference is given to whichever you find easier to read.

Partial select of left joined table with doctrine query builder

When querying one table using the doctrine query builder a partial select can be written like this:
$queryBuilder = $this->createQueryBuilder('person');
$queryBuilder->addSelect('partial person.{id, name}');
How can one write a partial select be written for a left joined table? I tried something like this, but can't figure out the correct syntax:
$queryBuilder = $this->createQueryBuilder('person');
$queryBuilder->join('person.address');
$queryBuilder->addSelect('partial person.{id, name} person.address.city'); // ???
My goal would be to select only parts of the Person and the Address object when executing the query to be more memory efficient.
Your syntax is off for your join operation. You have to give an alias when using join. From there, you can just use the same syntax to query your partial Address object:
// In a method of PersonRepository
$qb = $this->createQueryBuilder('person')
->select(['partial person.{id, name}', 'partial address.{id, city}'])
->join('person.address', 'address');
Notice that I added id to the fields retrieved for Address. If you don't, Doctrine will give you the following error:
Error: The partial field selection of class Path\To\Entity\Address must contain the identifier
As a side note, you said you wanted to write this select for a left joined table. If you want to perform a LEFT JOIN, you need to use leftJoin instead of join (the signature of both methods is the same).

SQL query to return a row even if not found, with at least in parameters

I would like to write a SQL query (oracle)
to know if an operation (identified by ope.ope_operationid)
has at least an operation of a certain type (opt.opt_id), and if it does not, to show that it doesn't in the results.
For example, I have this operation LAA351BP (I know this one exists in base),
and I would like to know if it has at least an operation type which id is 3781.
If it has, print everything, if it hasn't, print the operationid
and something like 'not found' next to it
Is nvl the function to use ? It seems I can't get it to work properly.
SELECT DISTINCT ope.ope_operationid,
ser.ser_code,
opt.opt_code,
ost.ost_code
FROM od_operation ope,
od_service_type ser,
od_operation_type opt,
od_status_type ost,
od_equipment_type eqt,
WHERE ope.ser_id = ser.ser_id
AND opt.opt_id = ope.opt_id
AND ost.ost_id = ope.ost_id
AND ope.opt_id = 3781
AND ope.ope_operationid = 'LAA351BP'
Thanks
You should start using standard JOIN syntax. Apart from being more readable (at least in my opinion) it also protects you from accidental cartesian joins if you forget the actual join condition in the WHERE clause. Plus it is portable across nearly all DBMS as opposed to the clunky (+) syntax used by Oracle (which also has some limitations that the JOIN syntax does not have)
Here is the query re-written using explicit (instead of implicit) joins:
SELECT DISTINCT ope.ope_operationid,
ser.ser_code,
opt.opt_code,
ost.ost_code
FROM od_operation ope,
LEFT JOIN od_service_type ser ON ope.ser_id = ser.ser_id
LEFT JOIN od_operation_type opt ON opt.opt_id = ope.opt_id
LEFT JOIN od_status_type ost ON ost.ost_id = ope.ost_id
LEFT JOIN od_equipment_type eqt ON ????????
WHERE ope.opt_id = 3781
AND ope.ope_operationid = 'LAA351BP'
Edit
The missing join condition on od_equipment_type is exactly the reason why the JOIN syntax is preferred. If the trailing comma in the original SQL is removed, the statement would create an unwanted cartesian join which might have a severe impact on the server if the involved tables are big.
With the JOIN syntax you will always get a syntax error which prevents you from such typos. Using implicit joins will only give you an error when you leave a comma in the FROM list, but never if you miss a join condition in the WHERE

ObjectQuery lambda expression for select inside select

i want to create query like this using lambda expression in ObjectQuery
SELECT *
FROM tableA
WHERE ID in (SELECT ID in tableB)
I try using
var histories = _entities.ProductViewHistories.Take(5).OrderByDescending(p=>p.DateViewed);
var products = _entities.Products.Where(p => p.ID in histories );
but it's not working.
Can someone point me to the right direction?
Thanks
Sure - "in" isn't part of C# in that way. Try this though:
var products = _entities.Products.Where(p => histories.Contains(p.ID));
Contains is the appropriate method to detect whether a value is within another collection. However, you may find it would be more appropriate to use a join here.
Also, I'm concerned about this query:
That doesn't represent the 5 most recently viewed histories - it represents "some arbitrary 5 view histories" (the first 5 returned in some indeterminate order) then arranged in most-recent-first order.
I suspect you actually want:
var histories = _entities.ProductViewHistories.OrderByDescending(p=>p.DateViewed)
.Take(5);

Can I force the auto-generated Linq-to-SQL classes to use an OUTER JOIN?

Let's say I have an Order table which has a FirstSalesPersonId field and a SecondSalesPersonId field. Both of these are foreign keys that reference the SalesPerson table. For any given order, either one or two salespersons may be credited with the order. In other words, FirstSalesPersonId can never be NULL, but SecondSalesPersonId can be NULL.
When I drop my Order and SalesPerson tables onto the "Linq to SQL Classes" design surface, the class builder spots the two FK relationships from the Order table to the SalesPerson table, and so the generated Order class has a SalesPerson field and a SalesPerson1 field (which I can rename to SalesPerson1 and SalesPerson2 to avoid confusion).
Because I always want to have the salesperson data available whenever I process an order, I am using DataLoadOptions.LoadWith to specify that the two salesperson fields are populated when the order instance is populated, as follows:
dataLoadOptions.LoadWith<Order>(o => o.SalesPerson1);
dataLoadOptions.LoadWith<Order>(o => o.SalesPerson2);
The problem I'm having is that Linq to SQL is using something like the following SQL to load an order:
SELECT ...
FROM Order O
INNER JOIN SalesPerson SP1 ON SP1.salesPersonId = O.firstSalesPersonId
INNER JOIN SalesPerson SP2 ON SP2.salesPersonId = O.secondSalesPersonId
This would make sense if there were always two salesperson records, but because there is sometimes no second salesperson (secondSalesPersonId is NULL), the INNER JOIN causes the query to return no records in that case.
What I effectively want here is to change the second INNER JOIN into a LEFT OUTER JOIN. Is there a way to do that through the UI for the class generator? If not, how else can I achieve this?
(Note that because I'm using the generated classes almost exclusively, I'd rather not have something tacked on the side for this one case if I can avoid it).
Edit: per my comment reply, the SecondSalesPersonId field is nullable (in the DB, and in the generated classes).
The default behaviour actually is a LEFT JOIN, assuming you've set up the model correctly.
Here's a slightly anonymized example that I just tested on one of my own databases:
class Program
{
static void Main(string[] args)
{
using (TestDataContext context = new TestDataContext())
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Place>(p => p.Address);
context.LoadOptions = dlo;
var places = context.Places.Where(p => p.ID >= 100 && p.ID <= 200);
foreach (var place in places)
{
Console.WriteLine(p.ID, p.AddressID);
}
}
}
}
This is just a simple test that prints out a list of places and their address IDs. Here is the query text that appears in the profiler:
SELECT [t0].[ID], [t0].[Name], [t0].[AddressID], ...
FROM [dbo].[Places] AS [t0]
LEFT OUTER JOIN (
SELECT 1 AS [test], [t1].[AddressID],
[t1].[StreetLine1], [t1].[StreetLine2],
[t1].[City], [t1].[Region], [t1].[Country], [t1].[PostalCode]
FROM [dbo].[Addresses] AS [t1]
) AS [t2] ON [t2].[AddressID] = [t0].[AddressID]
WHERE ([t0].[PlaceID] >= #p0) AND ([t0].[PlaceID] <= #p1)
This isn't exactly a very pretty query (your guess is as good as mine as to what that 1 as [test] is all about), but it's definitively a LEFT JOIN and doesn't exhibit the problem you seem to be having. And this is just using the generated classes, I haven't made any changes.
Note that I also tested this on a dual relationship (i.e. a single Place having two Address references, one nullable, one not), and I get the exact same results. The first (non-nullable) gets turned into an INNER JOIN, and the second gets turned into a LEFT JOIN.
It has to be something in your model, like changing the nullability of the second reference. I know you say it's configured as nullable, but maybe you need to double-check? If it's definitely nullable then I suggest you post your full schema and DBML so somebody can try to reproduce the behaviour that you're seeing.
If you make the secondSalesPersonId field in the database table nullable, LINQ-to-SQL should properly construct the Association object so that the resulting SQL statement will do the LEFT OUTER JOIN.
UPDATE:
Since the field is nullable, your problem may be in explicitly declaring dataLoadOptions.LoadWith<>(). I'm running a similar situation in my current project where I have an Order, but the order goes through multiple stages. Each stage corresponds to a separate table with data related to that stage. I simply retrieve the Order, and the appropriate data follows along, if it exists. I don't use the dataLoadOptions at all, and it does what I need it to do. For example, if the Order has a purchase order record, but no invoice record, Order.PurchaseOrder will contain the purchase order data and Order.Invoice will be null. My query looks something like this:
DC.Orders.Where(a => a.Order_ID == id).SingleOrDefault();
I try not to micromanage LINQ-to-SQL...it does 95% of what I need straight out of the box.
UPDATE 2:
I found this post that discusses the use of DefaultIfEmpty() in order to populated child entities with null if they don't exist. I tried it out with LINQPad on my database and converted that example to lambda syntax (since that's what I use):
ParentTable.GroupJoin
(
ChildTable,
p => p.ParentTable_ID,
c => c.ChildTable_ID,
(p, aggregate) => new { p = p, aggregate = aggregate }
)
.SelectMany (a => a.aggregate.DefaultIfEmpty (),
(a, c) => new
{
ParentTableEntity = a.p,
ChildTableEntity = c
}
)
From what I can figure out from this statement, the GroupJoin expression relates the parent and child tables, while the SelectMany expression aggregates the related child records. The key appears to be the use of the DefaultIfEmpty, which forces the inclusion of the parent entity record even if there are no related child records. (Thanks for compelling me to dig into this further...I think I may have found some useful stuff to help with a pretty huge report I've got on my pipeline...)
UPDATE 3:
If the goal is to keep it simple, then it looks like you're going to have to reference those salesperson fields directly in your Select() expression. The reason you're having to use LoadWith<>() in the first place is because the tables are not being referenced anywhere in your query statement, so the LINQ engine won't automatically pull that information in.
As an example, given this structure:
MailingList ListCompany
=========== ===========
List_ID (PK) ListCompany_ID (PK)
ListCompany_ID (FK) FullName (string)
I want to get the name of the company associated with a particular mailing list:
MailingLists.Where(a => a.List_ID == 2).Select(a => a.ListCompany.FullName)
If that association has NOT been made, meaning that the ListCompany_ID field in the MailingList table for that record is equal to null, this is the resulting SQL generated by the LINQ engine:
SELECT [t1].[FullName]
FROM [MailingLists] AS [t0]
LEFT OUTER JOIN [ListCompanies] AS [t1] ON [t1].[ListCompany_ID] = [t0].[ListCompany_ID]
WHERE [t0].[List_ID] = #p0

Resources