Aliases in combined Dynamic Queries

Posts   
 
    
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 08-Jul-2013 08:30:17   

Is there a way to 'seal' usages of aliases within a Dynamic Query? I looked at TargetAlias but it seems not to be applicable here.

For example, below are two methods creating DynamicQuerys - one uses the other as a dynamic table. This works but only because I used "y" in CreateAccountsTransactionAllocationStateQuery(). If I used "x" then it fails, presumably because it is merging the two queries rather than treating them as distinct units.

The full SQL query I hand-wrote can just use "x" in both cases.

What I would like to do is create a small library of DynamicQuery-creating methods like CreateAllocationSummaryQuery() and then reuse them in various other queries some which will be multi-level. Having to know what aliases have already been used is a code-smell so that is the reason for asking whether there is a way to 'hide' the implementation detail somehow.

        static DynamicQuery CreateAllocationSummaryQuery()
        {
            var qf = new QueryFactory();

            return qf.Allocation
                .From(QueryTarget
                    .InnerJoin(CreateAllocationItemSummaryQuery().As("x"))
                    .On(AllocationFields.ID == qf.Field("x", "AllocationID")))
                .Select(() => new
                              {
                                  FeeOrCommissionStatementID = AllocationFields.AccountsTransactionID.As("FeeOrCommissionStatementID").ToValue<int>(),
                                  TotalAllocatedItems = qf.Field("x", "TotalAllocatedItems").Sum().As("TotalAllocatedItems").ToValue<int>(),
                                  TotalAllocatedAmount = qf.Field("x", "TotalAllocatedAmount").Sum().As("TotalAllocatedAmount").ToValue<decimal>(),
                                  LastAllocationDate = AllocationFields.TaxpointDate.Max().As("LastAllocationDate").ToValue<DateTime>()
                              }
                )
                .GroupBy(AllocationFields.AccountsTransactionID);
        }

        static DynamicQuery CreateAccountsTransactionAllocationStateQuery()
        {
            var qf = new QueryFactory();

            return qf.AccountsTransaction
                .From(QueryTarget
                    .LeftJoin(CreateAllocationSummaryQuery().As("y"))
                    .On(AccountsTransactionFields.ID == qf.Field("y", "FeeOrCommissionStatementID")))
                .Select(() => new
                              {
                                  ID = AccountsTransactionFields.ID.ToValue<int>(),
                                  LegalBodyID = AccountsTransactionFields.LegalBodyID.ToValue<int?>(),
                                  //GrossTransactionAmount = AccountsTransactionFields.Amount.Sum().ToValue<decimal>() + AccountsTransactionFields.VATAmount.Sum().ToValue<decimal>(),
                                  AllocatedAmount = qf.Field("y", "TotalAllocatedAmount").Sum().ToValue<decimal>(),
                                  TotalAllocatedItems = qf.Field("y", "TotalAllocatedItems").Count().ToValue<int>(),
                                  LastAllocationDate = qf.Field("y", "LastAllocationDate").Max().ToValue<DateTime>()
                              }
                )
                .GroupBy(AccountsTransactionFields.ID, AccountsTransactionFields.LegalBodyID, AccountsTransactionFields.TaxpointDate);
        }

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 09-Jul-2013 07:52:38   
  • What is the generated SQL for that code?
  • What is your hand-wrote SQL?
  • What is your LLBLGen version and runtime library version?
  • What is wrong if you use "X" as alias in your second method (bad SQL, exception, unexpected results, etc)?
David Elizondo | LLBLGen Support Team
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 09-Jul-2013 09:49:17   

Here is the hand-written SQL using 'x' for both virtual tables

SELECT
    at.ID,
    at.LegalBodyID,
    SUM(COALESCE(at.Amount, 0) + COALESCE(at.VATAmount, 0)) AS GrossTransactionAmount,
    SUM(x.TotalAllocatedAmount) AS AllocatedAmount,
    COUNT(x.TotalAllocatedItems) AS TotalAllocatedItems,
    MAX(x.LastAllocationDate) AS LastAllocationDate
FROM AccountsTransaction at
LEFT JOIN
(
    SELECT
        a.AccountsTransactionID AS FeeOrCommissionStatementID,
        SUM(x.TotalAllocatedItems) AS TotalAllocatedItems,
        SUM(x.TotalAllocatedAmount) AS TotalAllocatedAmount,
        MAX(at.TaxpointDate) AS LastAllocationDate
    FROM Allocation a JOIN AccountsTransaction at ON at.ID = a.ID
    JOIN
    (
        SELECT
            ai.AllocationID,
            COUNT(ai.ID) AS TotalAllocatedItems,
            SUM(ani.Amount) AS TotalAllocatedAmount
        FROM AllocationItem ai JOIN AnalysisItem ani ON ani.ID = ai.ID
        GROUP BY ai.AllocationID
    ) x on x.AllocationID = a.ID
    GROUP BY a.AccountsTransactionID
) x ON x.FeeOrCommissionStatementID = at.ID
GROUP BY at.ID, at.LegalBodyID

Here is the SQL generated when using 'y' as one of the virtual tables. It produces exactly the same output as the hand-written SQL.

DECLARE @p2 int; SET @p2='0'
DECLARE @p4 int; SET @p4='0'

SELECT
  [LPA_L2].[ID],
  [LPA_L2].[LegalBodyID],
  SUM((COALESCE([LPA_L2].[Amount],
  @p2) + COALESCE([LPA_L2].[VATAmount],
  @p4))) AS [GrossTransactionAmount],
  SUM([LPA_y1].[TotalAllocatedAmount]) AS [TotalAllocatedAmount],
  COUNT([LPA_y1].[TotalAllocatedItems]) AS [TotalAllocatedItems],
  MAX([LPA_y1].[LastAllocationDate]) AS [LastAllocationDate] 
FROM
  ( (
SELECT
  [Allocation].[AccountsTransactionID] AS [FeeOrCommissionStatementID],
  SUM([LPA_x3].[TotalAllocatedItems]) AS [TotalAllocatedItems],
  SUM([LPA_x3].[TotalAllocatedAmount]) AS [TotalAllocatedAmount],
  MAX([AccountsTransaction].[TaxpointDate]) AS [LastAllocationDate] 
FROM
  (( (
SELECT
  [AllocationItem].[AllocationID],
  COUNT([AllocationItem].[ID]) AS [TotalAllocatedItems],
  SUM([AnalysisItem].[Amount]) AS [TotalAllocatedAmount] 
FROM
  ( [AnalysisItem]  
LEFT JOIN
  [AllocationItem] ON [AnalysisItem].[ID] = [AllocationItem].[ID]) 
GROUP BY
  [AllocationItem].[AllocationID]) [LPA_x3]  
INNER JOIN
  [Allocation] ON [Allocation].[ID] = [LPA_x3].[AllocationID]) 
INNER JOIN
  [AccountsTransaction] ON [AccountsTransaction].[ID] = [Allocation].[ID]) 
GROUP BY
  [Allocation].[AccountsTransactionID]) [LPA_y1]  
RIGHT JOIN
  [AccountsTransaction] [LPA_L2] ON [LPA_L2].[ID] = [LPA_y1].[FeeOrCommissionStatementID]) 
GROUP BY
  [LPA_L2].[ID],
  [LPA_L2].[LegalBodyID]

Here is the SQL generated when using 'x' for both virtual tables Exception thrown is "An exception was caught during the execution of a retrieval query: The multi-part identifier "TIPS.dbo.AnalysisItem.Amount" could not be bound."

DECLARE @p2 int; SET @p2='0'
DECLARE @p4 int; SET @p4='0'

SELECT
  [LPA_L2].[ID],
  [LPA_L2].[LegalBodyID],
  SUM((COALESCE([LPA_L2].[Amount],
  @p2) + COALESCE([LPA_L2].[VATAmount],
  @p4))) AS [GrossTransactionAmount],
  SUM([LPA_x1].[TotalAllocatedAmount]) AS [TotalAllocatedAmount],
  COUNT([LPA_x1].[TotalAllocatedItems]) AS [TotalAllocatedItems],
  MAX([LPA_x1].[LastAllocationDate]) AS [LastAllocationDate] 
FROM
  ( (
SELECT
  [Allocation].[AccountsTransactionID] AS [FeeOrCommissionStatementID],
  SUM([LPA_x3].[TotalAllocatedItems]) AS [TotalAllocatedItems],
  SUM([LPA_x3].[TotalAllocatedAmount]) AS [TotalAllocatedAmount],
  MAX([AccountsTransaction].[TaxpointDate]) AS [LastAllocationDate] 
FROM
  (( (
SELECT
  [AllocationItem].[AllocationID],
  COUNT([AllocationItem].[ID]) AS [TotalAllocatedItems],
  SUM([AnalysisItem].[Amount]) AS [TotalAllocatedAmount] 
FROM
  [AllocationItem]   
GROUP BY
  [AllocationItem].[AllocationID]) [LPA_x3]  
INNER JOIN
  [Allocation] ON [Allocation].[ID] = [LPA_x3].[AllocationID]) 
INNER JOIN
  [AccountsTransaction] ON [AccountsTransaction].[ID] = [Allocation].[ID]) 
GROUP BY
  [Allocation].[AccountsTransactionID]) [LPA_x1]  
RIGHT JOIN
  [AccountsTransaction] [LPA_L2] ON [LPA_L2].[ID] = [LPA_x1].[FeeOrCommissionStatementID]) 
GROUP BY
  [LPA_L2].[ID],
  [LPA_L2].[LegalBodyID]

LLBLGen Version is 4.0 final (June 26th, 2013); runtime version is 4.0.13.627

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39912
Joined: 17-Aug-2003
# Posted on: 09-Jul-2013 12:17:28   

The problem is that the engine runs into some fields which target a derived table. The only way to know which derived table they're referring to is by alias. So it does a visit on the set of derived tables, and determines which are known and by which alias. If you re-use the alias in nested derived tables (which works in SQL Server, it doesn't in some other DB's btw, so it's not really ok) these will give false positives and things fail.

The code I refer to above is mainly used for linq, however with the re-use of aliases in your queryspec query you run into a limitation of it which in practice isn't common (as aliases are assigned by hand in queryspec).

To work with aliases inside the methods you wrote, you can do the following (as they're used inside the methods only anyway)

var alias = "A" + Guid.NewGuid().ToString().GetHashCode();

GetHashCode returns an int, so it's not going to overflow the alias length. The guid assures you're not re-using aliases in 2 calls of the same method.

Frans Bouma | Lead developer LLBLGen Pro
simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 10-Jul-2013 10:11:20   

That's a reasonable workaround. Thanks.