简体   繁体   中英

How do I subtract two declared variables in MYSQL

The question I am working on is as follows:

What is the difference in the amount received for each month of 2004 compared to 2003?

This is what I have so far,

    SELECT @2003 = (SELECT sum(amount) FROM Payments, Orders
                WHERE YEAR(orderDate) = 2003
                AND Payments.customerNumber = Orders.customerNumber
                    GROUP BY MONTH(orderDate));

    SELECT @2004 = (SELECT sum(amount) FROM Payments, Orders
                WHERE YEAR(orderDate) = 2004
                AND Payments.customerNumber = Orders.customerNumber
                    GROUP BY MONTH(orderDate));

    SELECT MONTH(orderDate), (@2004 - @2003) AS Diff 
           FROM Payments, Orders
              WHERE Orders.customerNumber = Payments.customerNumber
                 Group By MONTH(orderDate);

In the output I am getting the months but for Diff I am getting NULL please help. Thanks

I cannot test this because I don't have your tables, but try something like this:

SELECT a.orderMonth, (a.orderTotal - b.orderTotal ) AS Diff 
FROM 
    (SELECT MONTH(orderDate) as orderMonth,sum(amount) as orderTotal 
    FROM Payments, Orders
    WHERE YEAR(orderDate) = 2004
    AND Payments.customerNumber = Orders.customerNumber
        GROUP BY MONTH(orderDate)) as a,
   (SELECT MONTH(orderDate) as orderMonth,sum(amount) as orderTotal FROM Payments, Orders
    WHERE YEAR(orderDate) = 2003
    AND Payments.customerNumber = Orders.customerNumber
        GROUP BY MONTH(orderDate)) as b
WHERE a.orderMonth=b.orderMonth

I think this is the problem:
In @2003 and @2004, you select only the sum. And even if you group by the month you still select one column ie each row does not say what month it is select for. So when you try to subtract SQL asks which row in @2003 should be subtracted from @2004.
So I think the solution is to select the month with the sum and do the subtract later based on the month.

Q: How do I subtract two declared variables in MySQL.

A: You'd first have to DECLARE them. In the context of a MySQL stored program. But those variable names wouldn't begin with an at sign character. Variable names that start with an at sign @ character are user-defined variables. And there is no DECLARE statement for them, we can't declare them to be a particular type.

To subtract them within a SQL statement

SELECT @foo - @bar AS diff

Note that MySQL user-defined variables are scalar values.

Assignment of a value to a user-defined variable in a SELECT statement is done with the Pascal style assignment operator := . In an expression in a SELECT statement, the equals sign is an equality comparison operator.

As a simple example of how to assign a value in a SQL SELECT statement

SELECT @foo := '123.45' ; 

In the OP queries, there's no assignment being done. The equals sign is a comparison, of the scalar value to the return from a subquery. Are those first statements actually running without throwing an error?

User-defined variables are probably not necessary to solve this problem.

You want to return how many rows? Sounds like you want one for each month. We'll assume that by "year" we're referring to a calendar year, as in January through December. (We might want to check that assumption. Just so we don't find out way too late, that what was meant was the "fiscal year", running from July through June, or something.)

How can we get a list of months? Looks like you've got a start. We can use a GROUP BY or a DISTINCT.

The question was... "What is the difference in the amount received ... "

So, we want amount received . Would that be the amount of payments we received? Or the amount of orders that we received? (Are we taking orders and receiving payments? Or are we placing orders and making payments?)

When I think of "amount received", I'm thinking in terms of income.

Given the only two tables that we see, I'm thinking we're filling orders and receiving payments. (I probably want to check that, so when I'm done, I'm not told... "oh, we meant the number of orders we received" and/or "the payments table is the payments we made, the 'amount we received' is in some other table"

We're going to assume that there's a column that identifies the "date" that a payment was received, and that the datatype of that column is DATE (or DATETIME or TIMESTAMP), some type that we can reliably determine what "month" a payment was received in.

To get a list of months that we received payments in, in 2003...

 SELECT MONTH(p.payment_received_date)
   FROM payment_received p
  WHERE p.payment_received_date >= '2003-01-01'
    AND p.payment_received_date <  '2004-01-01'
  GROUP BY MONTH(p.payment_received_date)
  ORDER BY MONTH(p.payment_received_date)

That should get us twelve rows. Unless we didn't receive any payments in a given month. Then we might only get 11 rows. Or 10. Or, if we didn't receive any payments in all of 2003, we won't get any rows back.

For performance, we want to have our predicates (conditions in the WHERE clause0 reference bare columns. With an appropriate index available, MySQL will make effective use of an index range scan operation. If we wrap the columns in a function, eg

  WHERE YEAR(p.payment_received_date) = 2003 

With that, we will be forcing MySQL to evaluate that function on every flipping row in the table, and then compare the return from the function to the literal. We prefer not do do that, and reference bare columns in predicates (conditions in the WHERE clause).

We could repeat the same query to get the payments received in 2004. All we need to do is change the date literals.

Or, we could get all the rows in 2003 and 2004 all together, and collapse that into a list of distinct months.

We can use conditional aggregation. Since we're using calendar years, I'll use the YEAR() shortcut (rather than a range check). Here, we're not as concerned with using a bare column inside the expression.

SELECT MONTH(p.payment_received_date) AS `mm`
     , MAX(MONTHNAME(p.payment_received_date)) AS `month`
     , SUM(IF(YEAR(p.payment_received_date)=2004,p.payment_amount,0)) AS `2004_month_total`
     , SUM(IF(YEAR(p.payment_received_date)=2003,p.payment_amount,0)) AS `2003_month_total`
     , SUM(IF(YEAR(p.payment_received_date)=2004,p.payment_amount,0))
     - SUM(IF(YEAR(p.payment_received_date)=2003,p.payment_amount,0)) AS `2004_2003_diff`
  FROM payment_received p
 WHERE p.payment_received_date >= '2003-01-01'
   AND p.payment_received_date <  '2005-01-01'
 GROUP
    BY MONTH(p.payment_received_date)
 ORDER
    BY MONTH(p.payment_received_date)

If this is a homework problem, I strongly recommend you work on this problem yourself. There are other query patterns that will return an equivalent result.

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