简体   繁体   中英

SQL SELECT Sum values without including duplicates

I have a problem in Oracle SQL that I'm trying to get my head around.

I'll illustrate with an example. I have three tables that I am querying:

Employees
__________________________________________
| EmployeeID | Name                      |
| 1          | John Smith                |
| 2          | Douglas Hoppalot          |
| 3          | Harry Holiday             |
... 



InternalCosts
________________________________
| IntID | Amount | EmployeeID  |
| 1     | 10     |     1       |
| 2     | 20     |     2       |
| 3     | 30     |     1       |
...


ExternalCosts
________________________________
| ExtID | Amount | EmployeeID  |
| 1     | 40     |     1       |
| 2     | 50     |     2       |
| 3     | 60     |     1       |
...

What I want to achieve is a result of one row per employee, with sums of each of their internal and external costs, ie

____________________________________________________________
| Name             | InternalCostTotal | ExternalCostTotal |
| John Smith       | 40                | 100               |
| Douglas Hoppalot | 20                | 50                |
...

The problem I have is that when I query both the InternalCosts and ExternalCosts tables, I get each permutation of costs, not just one per employee. When I group by employee Name and sum the amount fields the values are too high. What I have tried:

SELECT emp.Name, sum(int.Amount), sum(ext.Amount) 
FROM Employees emp,
     InternalCosts int,
     ExternalCosts ext
WHERE emp.EmployeeId = int.EmployeeID
  and emp.EmployeeID = ext.EmployeeID
GROUP BY emp.Name

The examples above would return:

 ____________________________________________________________
 | Name             | InternalCostTotal | ExternalCostTotal |
 | John Smith       | 80                | 200               | <- too high!
 | Douglas Hoppalot | 20                | 50                |
 ...

Grateful for any help/advice/thoughts!

You should use subqueries on int and ext to do the summing, and join to the subqueries.

I also suggest using explicit JOINs rather than table, table, table

eg

SELECT emp.Name, int.Amount AS InternalCostTotal, ext.Amount AS ExternalCostTotal
FROM Employees emp
JOIN ( 
    SELECT EmployeeID, SUM(Amount) AS Amount 
    FROM InternalCosts 
    GROUP BY EmployeeID 
) int ON emp.EmployeeId = int.EmployeeID
JOIN ( 
    SELECT EmployeeID, SUM(Amount) AS Amount 
    FROM ExternalCosts 
    GROUP BY EmployeeID 
) ext ON emp.EmployeeId = ext.EmployeeID

SQL Fiddle

Oracle 11g R2 Schema Setup :

CREATE TABLE Employees ( EmployeeID, Name ) AS
          SELECT 1, 'John Smith' FROM DUAL
UNION ALL SELECT 2, 'Douglas Hoppalot' FROM DUAL
UNION ALL SELECT 3, 'Harry Holiday' FROM DUAL;

CREATE TABLE InternalCosts ( IntID, Amount, EmployeeID ) AS
          SELECT 1, 10, 1 FROM DUAL
UNION ALL SELECT 2, 20, 2 FROM DUAL
UNION ALL SELECT 3, 30, 1 FROM DUAL;

CREATE TABLE ExternalCosts ( ExtID, Amount, EmployeeID ) AS
          SELECT 1, 40, 1 FROM DUAL
UNION ALL SELECT 2, 50, 2 FROM DUAL
UNION ALL SELECT 3, 60, 1 FROM DUAL;

Query 1 :

SELECT e.*,
       ( SELECT SUM( Amount ) FROM InternalCosts i WHERE e.EmployeeID = i.EmployeeID ) AS InternalCostTotal,
       ( SELECT SUM( Amount ) FROM ExternalCosts x WHERE e.EmployeeID = x.EmployeeID ) AS ExternalCostTotal
FROM   Employees e

Results :

| EMPLOYEEID |             NAME | INTERNALCOSTTOTAL | EXTERNALCOSTTOTAL |
|------------|------------------|-------------------|-------------------|
|          1 |       John Smith |                40 |               100 |
|          2 | Douglas Hoppalot |                20 |                50 |
|          3 |    Harry Holiday |            (null) |            (null) |

Or (using joins):

WITH InternalTotals AS (
  SELECT EmployeeID,
         SUM( Amount ) AS InternalCostTotal
  FROM   InternalCosts
  GROUP BY
         EmployeeID
),
ExternalTotals AS (
  SELECT EmployeeID,
         SUM( Amount ) AS ExternalCostTotal
  FROM   ExternalCosts
  GROUP BY
         EmployeeID
)
SELECT e.EmployeeID,
       i.InternalCostTotal,
       x.ExternalCostTotal
FROM   Employees e
       LEFT OUTER JOIN
       InternalTotals i
       ON ( e.EmployeeID = i.EmployeeID )
       LEFT OUTER JOIN
       ExternalTotals x
       ON ( e.EmployeeID = x.EmployeeID );

This should do the trick:

SELECT emp.Name, NVL(IntAmount,0), NVL(ExtAmount,0)
FROM Employees emp
  LEFT JOIN
    (SELECT EmployeeID, sum(Amount) as IntAmount
     FROM InternalCosts GROUP BY EmployeeID) int
    ON emp.EmployeeID = int.EmployeeID
  LEFT JOIN
    (SELECT EmployeeID, sum(Amount) as ExtAmount
     FROM ExternalCosts GROUP BY EmployeeID) ext
    ON emp.EmployeeID = ext.EmployeeID

Please also note that normalizing your table structure could help you in this case and in the future probably (and by this I mean merging External and Internal costs into one table, provided that it is possible of course).

I would suggest you try a group by using Employee ID instead and later do an INNER join to project the names from the Employee table.

Cheers!

Try this

SELECT t1.emp_name,inernalAmount,sum(ext.Amount) as externalAmount from
(
SELECT emp.EmployeeID,emp.Name, sum(int.Amount) as internalAmount 
FROM Employees emp inner join InternalCosts int on emp.EmployeeId = int.EmployeeID
) t1 inner join ExternalCosts ext on t1.EmployeeID = ext.EmployeeID
GROUP BY t1.Name,internalAmount

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM