Group By and multiplication

Posts   
 
    
sybjeb
User
Posts: 81
Joined: 20-Sep-2011
# Posted on: 03-Apr-2012 17:57:00   

Hi,

I have 2 tables OrderDetail and OrderDetailDiscount corresponding to the lines of order and discounts to be applied by order line.

the final price (after discounts) must be computed like this :

order line price = 100 discount 1 = 5% discount 2 = 3% => final discount = 100 * (1 - 5%) * ( 1 - 3%) = 100 * 0.95 * 0.995

In SQL, I have been able to write it like this :


SELECT order_detail.Id, order_detail.price * 
  (
    SELECT EXP(SUM(LOG(1 - Discount))) AS Discount
    FROM order_detail_discount
    WHERE order_detail_discount.FK_order_detail_Id = order_detail.Id
    GROUP BY order_detail_discount.FK_order_detail_Id
  ) FinalDiscount
FROM order_detail
WHERE order_detail.Id = XXX

Note : The **Exp(Sum(Log(X))) ** is used to be able to multiply all discount values found by order detail between each other. See http://blogs.x2line.com/al/articles/151.aspx

But I'm not able to write as a Linq query


            var query = from orderDetail in metaData.OrderDetail
                        where orderDetail.Id == XXX
                        select new OrderDetailProjection
                        {
                            Id = orderDetail.Id,
                            DiscountedPrice =
                                (from orderDetailDiscount in metaData.OrderDetailDiscount
                                 where orderDetailDiscount.FkOrderDetailId == [b]orderDetail.Id[/b]
                                 group orderDetailDiscount by orderDetailDiscount.FkOrderDetailId into orderDetailDiscountGroup
                                 select Math.Exp(orderDetailDiscountGroup.Sum(o => (1 - Math.Log(o.Discount))))).Single(),
                        };

=> LLBLGEN throws an exception because of the order_detail.Id that seems to not be put at the right position.

How can I write this query?

Thanks in advance

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 04-Apr-2012 00:28:18   

Hi there. In order to help you, please post the following:

David Elizondo | LLBLGen Support Team
sybjeb
User
Posts: 81
Joined: 20-Sep-2011
# Posted on: 04-Apr-2012 08:49:45   

**Version ** 3.5 Beta

Executed query

Query: SELECT `LPLA_1`.`Id`, (SELECT EXP(`LPA_L2`.`LPAV_`) AS `LPFA_2` FROM (SELECT `LPA_L3`.`FkOrderDetailId`, SUM(`LPA_L3`.`LPAV_`) AS `LPAV_` FROM (SELECT `LPLA_2`.`FK_order_detail_Id` AS `FkOrderDetailId`, @p2 - (LOG(`LPLA_2`.`Discount`)) AS `LPAV_` FROM `order_detail_discount`  `LPLA_2`) `LPA_L3` WHERE ( ( ( `LPA_L3`.`FkOrderDetailId` = `LPLA_1`.`Id`))) GROUP BY `LPA_L3`.`FkOrderDetailId`) `LPA_L2`) AS `DiscountedPrice` FROM `order_detail`  `LPLA_1` WHERE ( ( ( ( ( `LPLA_1`.`Id` = @p3)))))
    Parameter: @p2 : Double. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 1.
    Parameter: @p3 : Int64. Length: 0. Precision: 0. Scale: 0. Direction: Input. Value: 3.

Exception thrown

An exception was caught during the execution of a retrieval query: Unknown column 'LPLA_1.Id' in 'where clause'. Check InnerException, QueryExecuted and Parameters of this exception to examine the cause of this exception.

Stack trace

  à SD.LLBLGen.Pro.ORMSupportClasses.RetrievalQuery.Execute(CommandBehavior behavior)
   à SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchDataReader(IRetrievalQuery queryToExecute, CommandBehavior readerBehavior)
   à SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection(List`1 valueProjectors, IGeneralDataProjector projector, IRetrievalQuery queryToExecute, Dictionary`2 typeConvertersToRun)
   à SD.LLBLGen.Pro.ORMSupportClasses.DataAccessAdapterBase.FetchProjection(List`1 valueProjectors, IGeneralDataProjector projector, IEntityFields2 fields, IRelationPredicateBucket filter, Int32 maxNumberOfItemsToReturn, ISortExpression sortClauses, IGroupByCollection groupByClause, Boolean allowDuplicates, Int32 pageNumber, Int32 pageSize)
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProvider2.ExecuteValueListProjection(QueryExpression toExecute)
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.ExecuteExpression(Expression handledExpression)
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.Execute(Expression expression)
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProProviderBase.System.Linq.IQueryProvider.Execute(Expression expression)
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.Execute()
   à SD.LLBLGen.Pro.LinqSupportClasses.LLBLGenProQuery`1.System.Collections.Generic.IEnumerable<T>.GetEnumerator()
   à System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   à System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   à Bejot.Erp.Common.Server.Dao.OrderDao.GetOrderDetailsQuery(Int64 orderId, Expression`1 predicate) dans e:\01 Code\04 .Net\01 Erp\Bejot.Erp.Common\trunk\Bejot.Erp.Common.Server\Dao\Order\OrderDao.cs:ligne 195
Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 04-Apr-2012 10:32:04   

Version 3.5 Beta

Let's get Beta out of the picture, would you please try with the latest release of v.3.5.

sybjeb
User
Posts: 81
Joined: 20-Sep-2011
# Posted on: 13-Apr-2012 09:44:48   

Same with last version

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 13-Apr-2012 11:31:47   

Could you please provide a repro solution on Northwind? Or if you like you can open a helpdesk thread and attach your database.

sybjeb
User
Posts: 81
Joined: 20-Sep-2011
# Posted on: 13-Apr-2012 14:41:47   

You will find as an attachment a project containing the desired SQL query and the Linq query which throws an exception.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 14-Apr-2012 09:34:31   

I think the error is because Math.Exp is not a built-in supported linq function mapping, so you have to create a custom one. Let me see if I can do it for you...

David Elizondo | LLBLGen Support Team
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2012 11:34:00   

Yes, it was that. In you original code, LINQ2LLBL can't understand Math.Exp/Log as a DB function so it's used as an in-memory one. The problem is that such functions involves IQueryable elements, so it fails on that. You have to make custom function mappings for the functions that are not built-in mapped. You can find more info about this in the documentation.

I created the mappings for you. So put this class into your WinForms project:

using SD.LLBLGen.Pro.ORMSupportClasses;
namespace GroupByMultiplicationTest
{   
    public class MyFunctions
    {
        // EXP(x)
        public static double Exp(double x)
        {
            // empty by default, as it's just here to make the query compile. The call is converted to a SQL function.
            return double.MinValue;
        }

        // LOG(x)
        public static double Log(double x)
        {
            // empty by default, as it's just here to make the query compile. The call is converted to a SQL function.
            return double.MinValue;
        }  
    }

    /// Class which defines the custom mapping between MyFunctions.Lile and MySql LIKE function
    public class MyFunctionMappings : FunctionMappingStore
    {
        public MyFunctionMappings() : base()
        {
            // EXP(x)
            this.Add(new FunctionMapping(typeof(MyFunctions), "Exp", 1, "EXP({0})"));

            // LOG(x)
            this.Add(new FunctionMapping(typeof(MyFunctions), "Log", 1, "LOG({0})"));           
        }
    }
}

... and into your MainForm.cs:

var adapter = new DataAccessAdapter();
LinqMetaData metaData = new LinqMetaData(adapter);
metaData.CustomFunctionMappings = new MyFunctionMappings();

var q1 =
    from odd in metaData.OrderDetailDiscount
    group odd by odd.FkOrderDetailId into oddGroup
    select new
    {
        Id = oddGroup.Key,
        Discount = MyFunctions.Exp(oddGroup.Sum(o => 1 - MyFunctions.Log(o.Discount)))
    };
    
var q2 =
    from od in metaData.OrderDetail
    where od.Id == 1
    select new
    {
        Id = od.Id,
        Price = od.Price,
        FinalDiscount = (from x in q1 where x.Id == od.Id select x.Discount).Single()
    };
                
var results = q2.ToList();

Hope helpful wink

David Elizondo | LLBLGen Support Team
daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 15-Apr-2012 12:19:26   

Looking again into your code and your data, I have some additional comments:

  • About the data you have on DB, you should divide the discount by 100 to make it a percentage.
  • I think you don't need the GROUP BY because you already are aggregating on the correlated row.
  • I think it's better to join at the outer query. My guess is that the SQL is equivalent. I leave this test to you.
  • To make things easier, you also could use a single function mapping to handle all the expression.

New example:

New FunctionMappings

using SD.LLBLGen.Pro.ORMSupportClasses;
namespace GroupByMultiplicationTest
{   
    public class MyFunctions
    {
        public static double CalculateAggregateDiscount(double x)
        {
            return double.MinValue;
        }
    }

    public class MyFunctionMappings : FunctionMappingStore
    {
        public MyFunctionMappings() : base()
        {       
            this.Add(new FunctionMapping(typeof(MyFunctions), "CalculateAggregateDiscount", 1, "EXP(SUM(LOG(1 - {0}/100)))"));          
        }
    }
}

Usage

using (var adapter = new DataAccessAdapter())
{
    var metaData = new LinqMetaData(adapter);
    metaData.CustomFunctionMappings = new MyFunctionMappings();

    var results =
        (from od in metaData.OrderDetail
         join odd in metaData.OrderDetailDiscount on od.Id equals odd.FkOrderDetailId
         where od.Id == 1
         select new OrderDetailProjection
         {
             Id = od.Id,
             Price = od.Price,
             TotalDiscount = MyFunctions.CalculateAggregateDiscount(odd.Discount)
         })
        .ToList();
} 

Generated SQL

SELECT 
    `LPA_L1`.`Id`, 
    `LPA_L1`.`Price`, 
    EXP(SUM(LOG(1 - `LPA_L2`.`Discount`/100))) AS `TotalDiscount` 

FROM ( `order_detail` `LPA_L1`  
    INNER JOIN `order_detail_discount` `LPA_L2`  ON  `LPA_L1`.`Id` = `LPA_L2`.`FK_order_detail_Id`) 

WHERE ( ( ( ( ( `LPA_L1`.`Id` = 1))))) 

simple_smile

David Elizondo | LLBLGen Support Team
sybjeb
User
Posts: 81
Joined: 20-Sep-2011
# Posted on: 16-Apr-2012 13:09:38   

First of all, thanks for your answer.

Concerning your remark :

1) About the data you have on DB, you should divide the discount by 100 to make it a percentage => the discount stored in the database is already divided so I dont need to divide it by 100 in the query. You could not know it because I didn't tell it.

2) I think you don't need the GROUP BY because you already are aggregating on the correlated row => I thought about this but didn't achieve to write the query without it (see 4)

3) I think it's better to join at the outer query. My guess is that the SQL is equivalent. I leave this test to you. => OK

4) To make things easier, you also could use a single function mapping to handle all the expression. => OK but I'm interesting to know how I can write the same in Linq using mapping of Exp and Log and not CalculateAggregateDiscount ? In fact, to be able to use Sum method, I didn't see another way than using a GROUP.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 17-Apr-2012 05:13:50   

sybjeb wrote:

1) About the data you have on DB, you should divide the discount by 100 to make it a percentage => the discount stored in the database is already divided so I dont need to divide it by 100 in the query. You could not know it because I didn't tell it.

Ok, I supposed it because of the data you send in the script, but it's ok, It was only an observation.

sybjeb wrote:

2) I think you don't need the GROUP BY because you already are aggregating on the correlated row => I thought about this but didn't achieve to write the query without it (see 4)

Ok.

sybjeb wrote:

4) To make things easier, you also could use a single function mapping to handle all the expression. => OK but I'm interesting to know how I can write the same in Linq using mapping of Exp and Log and not CalculateAggregateDiscount ? In fact, to be able to use Sum method, I didn't see another way than using a GROUP.

Ok. Then you should look to my previous post (just before the last one). Good luck wink

David Elizondo | LLBLGen Support Team