Kinyanjui Kamau
Kinyanjui Kamau

Reputation: 1936

SQL Getting different results after combining two queries

I have two questions. First: I am trying to combine two queries into one to put in a stored procedure for report making. They are

Query 1

SELECT T0.Name AS Period, SUM(ISNULL(T2.LineTotal, 0)) AS CurrentDebtors, MAX(T1.DocRate) AS ExchangeRate,
SUM(CASE WHEN DATEDIFF(day, T1.DocDate, T1.DocDueDate) > 30
THEN T2.LineTotal END) AS NonCurrentDebtors 
FROM OFPR T0 LEFT OUTER JOIN OINV T1 ON T0.AbsEntry = T1.FinncPriod 
INNER JOIN INV1 T2 ON T1.DocEntry = T2.DocEntry WHERE YEAR(T1.DocDate) = @Year
GROUP BY T0.Name 
ORDER BY T0.Name 

Query 2

SELECT T0.Name AS Period, SUM(T1.DocTotal) AS TurnoverMonth
FROM dbo.OFPR T0 LEFT OUTER JOIN dbo.ORCT T1 ON T0.AbsEntry = T1.FinncPriod
WHERE YEAR(T1.DocDate) =  @Year
GROUP BY T0.Name
ORDER BY T0.Name

I have combined them into this:

SELECT T0.Name AS Period, SUM(ISNULL(T2.LineTotal, 0)) AS CurrentDebtors, MAX(T1.DocRate) AS ExchangeRate,
SUM(CASE WHEN DATEDIFF(day, T1.DocDate, T1.DocDueDate) > 30
THEN T2.LineTotal END) AS NonCurrentDebtors , SUM(ISNULL(T3.DocTotal, 0)) AS TurnoverMonth
FROM OFPR T0 LEFT OUTER JOIN OINV T1 ON T0.AbsEntry = T1.FinncPriod 
INNER JOIN INV1 T2 ON T1.DocEntry = T2.DocEntry 
JOIN ORCT T3 ON T0.AbsEntry = T3.FinncPriod
WHERE YEAR(T1.DocDate) = @Year
GROUP BY T0.Name 
ORDER BY T0.Name 

The problem is that while the results of the separate 2 queries above are correct, the combined query above returns incorrect values which are very large in amount. How do I combine the two properly? Second: Query 1 also takes some time to execute if there is very large data, any way to improve its efficiency? Using Microsoft SQL Server 2008

Upvotes: 0

Views: 513

Answers (1)

Nikola Markovinović
Nikola Markovinović

Reputation: 19356

Due to the nature of joins you are bound to get duplicated aggregations if you try to aggregate two or more tables simultaneously. There is probably n:m relation between T2 and T3 when connected through TO. To solve this you might use cte or derived tables - this rewrite uses derived table for T3 to retrieve one row exactly per TO.AbsEntry, thus removing duplication.

SELECT T0.Name AS Period, 
       SUM(T2.LineTotal) AS CurrentDebtors, 
       MAX(T1.DocRate) AS ExchangeRate,
       SUM(CASE WHEN DATEDIFF(day, T1.DocDate, T1.DocDueDate) > 30
                THEN T2.LineTotal 
           END) AS NonCurrentDebtors, 
       T3.TurnoverMonth
  FROM OFPR T0 
 INNER JOIN OINV T1
    ON T0.AbsEntry = T1.FinncPriod 
 INNER JOIN INV1 T2 
    ON T1.DocEntry = T2.DocEntry
 INNER JOIN 
 (
    SELECT ORCT.FinncPriod, 
           SUM(ORCT.DocTotal) AS TurnoverMonth
      FROM ORCT
     WHERE YEAR(ORCT.DocDate) = @Year
     GROUP BY ORCT.FinncPriod
 )  T3 
    ON T0.AbsEntry = T3.FinncPriod
 WHERE YEAR(T1.DocDate) = @Year
 GROUP BY T0.Name, T3.TurnoverMonth
 ORDER BY T0.Name

Another possibility is that you need to filter ORCT table by year, which is omitted in combined query. For performance reasons you might consider expanding that filter to date-range test, to allow Sql Server to use index on DocPeriod:

where DocDate >= convert(datetime, convert(varchar(20), @year) + '0101')
  and DocDate < dateadd (year, 1, 
                         convert(datetime, convert(varchar(20), @year) + '0101'))

Note I've added TurnoverMonth to group by. This does not change anything because there will be exaclty one row per T3.

I've also removed isnull() test from sum, because null values are removed from sum() anyway. If you want to replace final null results with zeros, envelope sum() in isnull().

EDIT: forgot to mention that I've changed left join to inner join because the condition on T1 alters the meaning of the join to inner join (missing rows cannot be matched by anything except is null/is not null). If you actually need outer join, move condition to ON clause.

Upvotes: 2

Related Questions