commandos
commandos

Reputation: 171

combining multiple sql queries together

I have multiple table for a project (sessions , charges and payments)

To get the sessions i'm doing the following :

SELECT
sess.file_id,  SUM(sess.rate * sess.length) AS total
FROM
sess
WHERE sess.sessionDone = 1 
GROUP BY sess.file_id

This will return the amount that a specific student should pay

I also have another table "charges"

SELECT
file_charges.file_id,  SUM(file_charges.price) AS total_charges
FROM
file_charges
GROUP BY file_charges.file_id

And finally the payment query :

SELECT
file_payments.file_id,  SUM(file_payments.paymentAmount) AS total_payment
FROM
file_payments
GROUP BY file_payments.file_id

Can i combine those 3 in a way to have :

 Total = Payments - (Session + Charges)

Note that it could be negative so i could have file_id that exists in session , charges but not in payments and i could have a payment without sessions or charges ...

Edit : http://sqlfiddle.com/#!2/a90d9

Upvotes: 0

Views: 2873

Answers (2)

spencer7593
spencer7593

Reputation: 108400

One issue that needs to be addressed is whether one of these queries can be the "driver", in cases where we don't have rows for a given file_id returned by one or more of the queries. (e.g. there might be rows from sess, but none from file_payments. If we want to be sure to include every possible file_id that appears in any of the queries, we can get a list of all possible file_id with a query like this:

SELECT ss.file_id FROM sess ss
 UNION 
SELECT fc.file_id FROM file_charges fc
 UNION 
SELECT fp.file_id FROM file_payments fp

(NOTE: The UNION operator will remove any duplicates)

To get the specified resultset, we can use that query, along with "left joins" of the other three original queries. The outline of the query will be:

SELECT a.file_id, p.total_payment - ( s.total + c.total_charges)
  FROM a
  LEFT JOIN s ON s.file_id = a.file_id
  LEFT JOIN c ON c.file_id = a.file_id
  LEFT JOIN p ON p.file_id = a.file_id
 ORDER BY a.file_id

In that statement a is a standin for the query that gets the set of all file_id values (as shown above). The s, c and p are standins for your three original queries, on sess, file_charges and file_payments, respectively.

If any of the file_id values is "missing" from any of the queries, we are going to need to substitute a zero for the missing value. We can use the IFNULL function to handle that for us.

This query should return the specified resultset:

SELECT a.file_id
     , IFNULL(p.total_payment,0) - ( IFNULL(s.total,0) + IFNULL(c.total_charges,0)) AS t
  FROM ( -- all possible values of file_id
         SELECT ss.file_id FROM sess ss
          UNION 
         SELECT fc.file_id FROM file_charges fc
          UNION 
         SELECT fp.file_id FROM file_payments fp
       ) a
  LEFT
  JOIN ( -- the amount that a specific student should pay
         SELECT sess.file_id,  SUM(sess.rate * sess.length) AS total
           FROM sess
          WHERE sess.sessionDone = 1 
          GROUP BY sess.file_id
       ) s 
    ON s.file_id = a.file_id
  LEFT
  JOIN ( -- charges 
         SELECT file_charges.file_id,  SUM(file_charges.price) AS total_charges
           FROM file_charges
          GROUP BY file_charges.file_id
       ) c
    ON c.file_id = a.file_id
  LEFT
  JOIN ( -- payments 
         SELECT file_payments.file_id,  SUM(file_payments.paymentAmount) AS total_payment
           FROM file_payments
          GROUP BY file_payments.file_id
       ) p
    ON p.file_id = a.file_id
 ORDER BY a.file_id

(The EXPLAIN for this query is not going to be pretty, with four derived tables. On really large sets, performance may be horrendous. But the resultset returned should meet the specification.)

Beware of queries that JOIN all three tables together... that will likely give incorrect results when there are (for example) two (or more) rows for the same file_id in the file_payment table.

There are other approaches to getting an equivalent result set, but the query above answers the question: "how can i get the results of these queries joined together into a total".


Using correlated subqueries

Here's another approach, using correlated subqueries in the SELECT list...

SELECT a.file_id
     , IFNULL( ( SELECT SUM(file_payments.paymentAmount) FROM file_payments
                    WHERE file_payments.file_id = a.file_id )
           ,0)
     - ( IFNULL( ( SELECT SUM(sess.rate * sess.length) FROM sess 
                    WHERE sess.file_id = a.file_id )
             ,0)
       + IFNULL( ( SELECT SUM(file_charges.price) FROM file_charges
                    WHERE file_charges.file_id = a.file_id )
             ,0)
       ) AS tot
  FROM ( -- all file_id values 
         SELECT ss.file_id FROM sess ss
          UNION 
         SELECT fc.file_id FROM file_charges fc
          UNION 
         SELECT fp.file_id FROM file_payments fp
       ) a
 ORDER BY a.file_id

Upvotes: 3

echo_Me
echo_Me

Reputation: 37233

try this

 SELECT  sess.file_id,  SUM(file_payments.paymentAmount) - (SUM(sess.rate * sess.length)+SUM(file_charges.price)) as total_payment FROM sess , file_charges , file_payments 
   WHERE sess.sessionDone = 1 
  GROUP BY total_payment 

EDIT.

SELECT a.file_id
 , IFNULL(p.total_payment,0) - ( IFNULL(s.total,0) + IFNULL(c.total_charges,0)) AS tot
FROM ( 
     SELECT ss.file_id FROM sess ss
      UNION 
     SELECT fc.file_id FROM file_charges fc
      UNION 
     SELECT fp.file_id FROM file_payments fp
   ) a
 LEFT JOIN ( 
     SELECT sess.file_id,  SUM(sess.rate * sess.length) AS total
       FROM sess
      WHERE sess.sessionDone = 1 
      GROUP BY sess.file_id
   ) s 
ON s.file_id = a.file_id
 LEFT JOIN ( 
     SELECT file_charges.file_id,  SUM(file_charges.price) AS total_charges
       FROM file_charges
      GROUP BY file_charges.file_id
   ) c
ON c.file_id = a.file_id
LEFT JOIN ( 
     SELECT file_payments.file_id,  SUM(file_payments.paymentAmount) AS total_payment
       FROM file_payments
      GROUP BY file_payments.file_id
   ) p
 ON p.file_id = a.file_id
ORDER BY a.file_id

DEMO HERE

Upvotes: 1

Related Questions