The Employee table holds the salary information in a year.
Write a SQL to get the cumulative sum of an employee's salary over a period of 3 months but exclude the most recent month.
The result should be displayed by 'Id' ascending, and then by 'Month' descending.
Example
Input
| Id | Month | Salary | |----|-------|--------| | 1 | 1 | 20 | | 2 | 1 | 20 | | 1 | 2 | 30 | | 2 | 2 | 30 | | 3 | 2 | 40 | | 1 | 3 | 40 | | 3 | 3 | 60 | | 1 | 4 | 60 | | 3 | 4 | 70 |Output
| Id | Month | Salary | |----|-------|--------| | 1 | 3 | 90 | | 1 | 2 | 50 | | 1 | 1 | 20 | | 2 | 1 | 20 | | 3 | 3 | 100 | | 3 | 2 | 40 |Explanation
Employee '1' has 3 salary records for the following 3 months except the most recent month '4': salary 40 for month '3', 30 for month '2' and 20 for month '1' So the cumulative sum of salary of this employee over 3 months is 90(40+30+20), 50(30+20) and 20 respectively.
| Id | Month | Salary | |----|-------|--------| | 1 | 3 | 90 | | 1 | 2 | 50 | | 1 | 1 | 20 |Employee '2' only has one salary record (month '1') except its most recent month '2'.
| Id | Month | Salary | |----|-------|--------| | 2 | 1 | 20 |Employ '3' has two salary records except its most recent pay month '4': month '3' with 60 and month '2' with 40. So the cumulative salary is as following.
| Id | Month | Salary | |----|-------|--------| | 3 | 3 | 100 | | 3 | 2 | 40 |
OUTER JOIN
and temporary tables [Accepted]Intuition
\nSolve this issue by two steps. The first one is to get the cumulative sum of an employee\'s salary over a period of 3 months, and then exclude the most recent month from the result.
\nAlgorithm
\nIf you feel hard to work out how to get the cumulative sum of an employee\'s salary over a period of 3 months, think about 2 months as a start. By joining this Employee table with itself, you can get salary information for one more month.
\nSELECT *\nFROM\n Employee E1\n LEFT JOIN\n Employee E2 ON (E2.id = E1.id\n AND E2.month = E1.month - 1)\nORDER BY E1.id ASC , E1. month DESC\n
Id | \nMonth | \nSalary | \nId | \nMonth | \nSalary | \n
---|---|---|---|---|---|
1 | \n4 | \n60 | \n1 | \n3 | \n40 | \n
1 | \n3 | \n40 | \n1 | \n2 | \n30 | \n
1 | \n2 | \n30 | \n1 | \n1 | \n20 | \n
1 | \n1 | \n20 | \n\n | \n | \n |
2 | \n2 | \n30 | \n2 | \n1 | \n20 | \n
2 | \n1 | \n20 | \n\n | \n | \n |
3 | \n4 | \n70 | \n3 | \n3 | \n60 | \n
3 | \n3 | \n60 | \n3 | \n2 | \n40 | \n
3 | \n2 | \n40 | \n\n | \n | \n |
>Note: | \n\n | \n | \n | \n | \n |
> - The blank value in the output is actually NULL in the database. | \n\n | \n | \n | \n | \n |
> - The first three columns are from E1, and the rest ones are from E2. | \n\n | \n | \n | \n | \n |
Then we can add the salary to get the cumulative sum for 2 months.
\nSELECT\n E1.id,\n E1.month,\n (IFNULL(E1.salary, 0) + IFNULL(E2.salary, 0)) AS Salary\nFROM\n Employee E1\n LEFT JOIN\n Employee E2 ON (E2.id = E1.id\n AND E2.month = E1.month - 1)\nORDER BY E1.id ASC , E1.month DESC\n
| id | month | Salary |\n|----|-------|--------|\n| 1 | 4 | 100 |\n| 1 | 3 | 70 |\n| 1 | 2 | 50 |\n| 1 | 1 | 20 |\n| 2 | 2 | 50 |\n| 2 | 1 | 20 |\n| 3 | 4 | 130 |\n| 3 | 3 | 100 |\n| 3 | 2 | 40 |\n
Similarly, you can join this table one more time to get the cumulative sum for 3 months.
\nSELECT\n E1.id,\n E1.month,\n (IFNULL(E1.salary, 0) + IFNULL(E2.salary, 0) + IFNULL(E3.salary, 0)) AS Salary\nFROM\n Employee E1\n LEFT JOIN\n Employee E2 ON (E2.id = E1.id\n AND E2.month = E1.month - 1)\n LEFT JOIN\n Employee E3 ON (E3.id = E1.id\n AND E3.month = E1.month - 2)\nORDER BY E1.id ASC , E1.month DESC\n;\n
| id | month | Salary |\n|----|-------|--------|\n| 1 | 4 | 130 |\n| 1 | 3 | 90 |\n| 1 | 2 | 50 |\n| 1 | 1 | 20 |\n| 2 | 2 | 50 |\n| 2 | 1 | 20 |\n| 3 | 4 | 170 |\n| 3 | 3 | 100 |\n| 3 | 2 | 40 |\n
In addition, we have to exclude the most recent month as required. If we have a temp table including every id and most recent month like below, then we can easily opt out these months by join it with the above table.
\n| id | month |\n|----|-------|\n| 1 | 4 |\n| 2 | 2 |\n| 3 | 4 |\n
Here is the code to generate this table.
\nSELECT\n id, MAX(month) AS month\nFROM\n Employee\nGROUP BY id\nHAVING COUNT(*) > 1\n;\n
At last, we can join them together and get the desired cumulative sum of an employee\'s salary over a period of 3 months excluding the most recent one.
\nMySQL
\nSELECT\n E1.id,\n E1.month,\n (IFNULL(E1.salary, 0) + IFNULL(E2.salary, 0) + IFNULL(E3.salary, 0)) AS Salary\nFROM\n (SELECT\n id, MAX(month) AS month\n FROM\n Employee\n GROUP BY id\n HAVING COUNT(*) > 1) AS maxmonth\n LEFT JOIN\n Employee E1 ON (maxmonth.id = E1.id\n AND maxmonth.month > E1.month)\n LEFT JOIN\n Employee E2 ON (E2.id = E1.id\n AND E2.month = E1.month - 1)\n LEFT JOIN\n Employee E3 ON (E3.id = E1.id\n AND E3.month = E1.month - 2)\nORDER BY id ASC , month DESC\n;\n
id | \nmonth | \nSalary | \n
---|---|---|
1 | \n3 | \n90 | \n
1 | \n2 | \n50 | \n
1 | \n1 | \n20 | \n
2 | \n1 | \n20 | \n
3 | \n3 | \n100 | \n
3 | \n2 | \n40 | \n
Note: Thank @xiaxin for providing this elegant solution.
\n