Fetch EntityCollection with complex predicate expression

Posts   
 
    
Seth avatar
Seth
User
Posts: 204
Joined: 25-Mar-2006
# Posted on: 11-May-2007 07:10:31   

I am stumped with this one: I have three tables Person -> Job -> Billing where a person owns many jobs and jobs own many billings. I want a PersonCollection where the sum of the Billings.Amount <= 0. At the same time, I want a prefetch path that pulls all of the jobs for each person where the sum of the billings.Amount <= 0. What is the best way to do this?

I use LLBLGen 2.0.0.0 Final March 21, 2007.

As a corollary, consider the following additional table Person -> Job -> Payments, where a Job has many payments. How can I query a PersonCollection where the Sum(Billings) - Sum(Payments) != 0 and then have a prefetch path on the PersonCollection for each job that then pulls each job that has Sum(Billings) - Sum(Payments) != 0. Thanks for the help!

-Seth

Walaa avatar
Walaa
Support Team
Posts: 14995
Joined: 21-Aug-2005
# Posted on: 11-May-2007 10:22:14   

I'm I correctly assuming the SQL query you need to execute looks like the following?

SELECT * from Person
WHERE PersonID IN 
(
Select PersonID From Job
INNER JOIN Billing ON ...
GROUP BY PersonID
HAVING SUM(Billing.Amount) <=0
)

If true, then you need to use a FieldCompareSetPredicate to implement the IN Clause. Using the following overload:

public FieldCompareSetPredicate( 
   IEntityField field,
   IEntityField setField,
   SetOperator operatorToUse,
   IPredicate filter,
   IRelationCollection relations,
   string objectAlias,
   long maxNumberOfItemsToReturn,
   ISortExpression sorter,
   bool negate,
   IGroupByCollection groupByClause
)

You will have to set the relations paramtere (from Job to billing) And you will have to set the appropriate GroupByClause and its HavingClause.

Seth avatar
Seth
User
Posts: 204
Joined: 25-Mar-2006
# Posted on: 12-May-2007 01:56:40   

Thanks for the reply. I guess I am not doing it right. The exact query I would like to get is


SELECT * from Person
WHERE PersonId IN
(
Select EstimatorId From Job
INNER JOIN Billing ON Job.JobId = Billing.JobId
GROUP BY EstimatorId 
HAVING SUM(Billing.Amount) <=0
)

Here EstimatorId and JobId are the PK-FK. I don't think I did that part right. The second thing is I want the prefetch path on the PersonCollection that fills in each job only if the SUM(Billing.Amount) <=0. This has really stumped me!


IPrefetchPath2 prefetchJobs = new PrefetchPath2((int)EntityType.PersonEntity);
            //add sorting for additional items here
            IPredicateExpression jobSelect = getFilter();
            prefetchJobs.Add(PersonEntity.PrefetchPathJob, -1, jobSelect);

            IRelationPredicateBucket selectFilter = new RelationPredicateBucket();
            IRelationCollection queryRelations = new RelationCollection();
            queryRelations.Add(PersonEntity.Relations.JobEntityUsingEstimatorId);
            queryRelations.Add(JobEntity.Relations.BillingEntityUsingJobId);

            IGroupByCollection groupBy = new GroupByCollection();
            groupBy.Add(JobFields.EstimatorId);
            groupBy.HavingClause = new PredicateExpression(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) <= 0);

            IPredicate personPredicate = new FieldCompareSetPredicate(
                PersonFields.PersonId,
                null,
                JobFields.EstimatorId,
                null,
                SetOperator.In,
                null,
                queryRelations,
                null,
                -1,
                null,
                false,
                groupBy);

            selectFilter.PredicateExpression.Add(personPredicate);

            _adapter.OpenConnection();
            _adapter.FetchEntityCollection(estimatorCollection, selectFilter, prefetchJobs);
            _adapter.CloseConnection();


The second thing (which is similar but more complicated) is to do the same thing but have aggregates on two different tables (Billing, and Payment). These are both related to the Job by a many-to-one relationship (many Billings and Payments per Job). I want to do the same thing as above with the exception that I want to return a Job and corresponding Person collections when the SUM(Billing.Amount) - SUM(Payment.Amount) != 0. This is like the first query but with an additional aggregate. For some reason I cannot wrap my head around this one!

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 12-May-2007 04:59:39   

Fist of all, Did the first thing work? Did you received the expected results? I think this relation is unnecessary:

queryRelations.Add(PersonEntity.Relations.JobEntityUsingEstimatorId);

as your subquery don't require it:

...
INNER JOIN Billing ON Job.JobId = Billing.JobId
...

With the other case, I think you need to include the Payment relation at Subquery, at groupBy and Having, and it should work.

David Elizondo | LLBLGen Support Team
Seth avatar
Seth
User
Posts: 204
Joined: 25-Mar-2006
# Posted on: 12-May-2007 05:28:50   

I did do some revisions:

            //Job->Billing
            IRelationCollection jobBillingRelation = new RelationCollection();
            jobBillingRelation.Add(JobEntity.Relations.BillingEntityUsingJobId);

            IGroupByCollection grouping = new GroupByCollection();
            grouping.Add(JobFields.EstimatorId);
            grouping.HavingClause = new PredicateExpression(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) <= 0);

            IRelationPredicateBucket selectEstimators = new RelationPredicateBucket(
                new FieldCompareSetPredicate(
                    PersonFields.PersonId,
                    null,
                    JobFields.EstimatorId,
                    null,
                    SetOperator.In,
                    null,
                    jobBillingRelation,
                    "",
                    -1,
                    null,
                    false,
                    grouping));

            _adapter.OpenConnection();
            _adapter.FetchEntityCollection(estimatorCollection, selectEstimators);
            _adapter.CloseConnection();

But I found that it did not return anything. The predicate expression in the RelationPredicateBucket produced the following under the debug visualization:

(
    PersonEntity.[PersonId] IN 
    (
        SELECT JobEntity.[EstimatorId] FROM 
        (
            JobEntity  INNER JOIN BillingEntity  ON  JobEntity.JobId=BillingEntity.JobId
        )
        GROUP BY JobEntity.[EstimatorId] HAVING 
        (
            SUM
            (
                BillingEntity.[Amount]
            )
            <= @Amount1
        )
    )
)

@Amount1=0. That seems to be right to me... It did not retun any values as it should have. I am not sure what exactly the problem could be.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 12-May-2007 08:02:02   

Please post the SQL generated (verbose level.... LLBLGenPro Help - Using generated code - and debugging).

David Elizondo | LLBLGen Support Team
Seth avatar
Seth
User
Posts: 204
Joined: 25-Mar-2006
# Posted on: 12-May-2007 08:33:02   

Thanks for the help! I finally got it. The problem was the Join Hint was not picking up cases where the sum was equal to null (No Billings). Here is the full code in all of its glory:


            //relations
            //Job->Billing
            IRelationCollection jobBillingRelation = new RelationCollection();
            jobBillingRelation.Add(JobEntity.Relations.BillingEntityUsingJobId);
            jobBillingRelation.ObeyWeakRelations = true;

            IGroupByCollection grouping = new GroupByCollection();
            grouping.Add(JobFields.EstimatorId);
            grouping.Add(JobFields.JobId);
            grouping.HavingClause = new PredicateExpression(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) <= 0);
            grouping.HavingClause.AddWithOr(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) == DBNull.Value);

            IRelationPredicateBucket selectEstimators = new RelationPredicateBucket(
                new FieldCompareSetPredicate(
                    PersonFields.PersonId,
                    null,
                    JobFields.EstimatorId,
                    null,
                    SetOperator.In,
                    null,
                    jobBillingRelation,
                    "",
                    -1,
                    null,
                    false,
                    grouping));

            //Load Job Prefetch Path
            IPrefetchPath2 prefetchJobs = new PrefetchPath2((int)EntityType.PersonEntity);
            IGroupByCollection jobGrouping = new GroupByCollection();
            jobGrouping.Add(JobFields.JobId);
            jobGrouping.HavingClause = new PredicateExpression(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) <= 0);
            jobGrouping.HavingClause.AddWithOr(BillingFields.Amount.SetAggregateFunction(AggregateFunction.Sum) == DBNull.Value);

            IPredicateExpression jobSelection = new PredicateExpression(
                new FieldCompareSetPredicate(
                    JobFields.JobId,
                    null,
                    JobFields.JobId,
                    null,
                    SetOperator.In,
                    null,
                    jobBillingRelation,
                    "",
                    -1,
                    null,
                    false,
                    jobGrouping));

            ISortExpression sort = new SortExpression(JobFields.StartDate | SortOperator.Ascending);
            prefetchJobs.Add(PersonEntity.PrefetchPathJob, -1, jobSelection, null, sort);

            _adapter.OpenConnection();
            _adapter.FetchEntityCollection(estimatorCollection, selectEstimators, -1, null, prefetchJobs);
            _adapter.CloseConnection();

This little exercise really helped with understanding the DQE. Thanks for the help! I still need to figure out the other problem. I will post that code as well as soon as I am done.