I'm trying to make a listing for Ageing Transaction where areas the list will have 0-30 days column and 31-60 days column.
If transaction date is within 30 days, then its in 0-30 days column, if it's within the range of 31-60 then it's inside 31-60 days column.
So to make it more clear, here's the table
|trans_code |customer |trans_date | credit| debit |
|ABC1000 |John ptd |2014-05-20 | 0.00 | 200.00 |
|ABC1000 |John ptd |2014-07-06 |200.00 | 0.00 |
|ABC1001 |Petron |2014-04-25 | 0.00 | 600.00 |
|ABC1001 |Petron |2014-06-10 |600.00 | 0.00 |
John ptd has a debt of $200 on 2014-05-20 and he paid his debt on 2014-07-06. So the date range is within 31-60 days. The $200 he paid will goes into 31-60 days column in Ageing Transaction listing(like below).
|Customer | 0-30 days | 31-60 days|
|John ptd | 0.00 | 200.00|
|Petron | 0.00 | 600.00|
Now my problem is how to compare trans_date since it's in the same column using SQL. It should be BASED on trans_code.
Updated: It's actually starts with Debit before Credit
Here is a better approach:
SELECT
d.customer,
CASE WHEN DATEDIFF(c.trans_date, d.trans_date) <= 30 THEN d.debit ELSE 0.0 END AS `0-30`,
CASE WHEN DATEDIFF(c.trans_date, d.trans_date) > 30 AND DATEDIFF(c.trans_date, d.trans_date) <= 60
THEN d.debit ELSE 0.0 END AS `31-60`
FROM table AS d
INNER JOIN table AS c ON c.trans_code = d.trans_code and c.customer = d.customer
WHERE d.credit = 0.0 and c.debit = 0.0
SELECT
t.customer,
SUM(c1.credit) AS `0-30`,
SUM(c2.credit) AS `31-60`,
FROM table AS t
LEFT JOIN table AS c1 ON c1.debit = t.credit AND DATEDIFF(t.trans_date, c1.trans_date) <= 30
LEFT JOIN table AS c2 ON c2.debit = c2.credit AND DATEDIFF(t.trans_date, c2.trans_date) > 30 DATEDIFF(t.trans_date, c2.trans_date) <= 60
Take a look at the DATEDIFF function. You JOIN to the transactions paid within 30 days and to the ones paid within 31-60 days.
You can use GROUP_CONCAT.It will look like
SELECT trans_code ,customer, GROUP_CONCAT(trans_date), credit, debit, 0-30 days
, 31-60 days
FROM table1 INNER JOIN table2 ON table1.customer = table2.customer
WHERE customer = "John ptd" GROUP BY trans_code
Not TESTED.
Related
I need to show income and expense per day
Income and expenses are different table
I need to show in following format, for ex:
17/08/2019 date have two incomes in the table
I need to calculate sum of the income in the date, showing on the result with the same day expense.
I have tried with some queries, but it's not working.
Date | Income | Expense | Profit
Select SUM(d.amount)
, SUM(e.amount)
, d.date
, e.date
FROM due d
JOIN expenses e
ON d.date = e.date
Expense table -table-name : expenses
id | date | details | amount
1 13-08-2019 daily 50
2 17-08-2019 cleaning 50
3 17-08-2019 cleaning 50
4 18-08-2019 Tea 150
5 18-08-2019 other 50
Income table -table-name : due
id | date | amount
4 12-08-2019 150
5 13-08-2019 100
6 18-08-2019 450
7 18-08-2019 50
result will be:
id | date | Income | Expense | Profit
1 12-08-2019 150 NULL 150
2 13-08-2019 100 50 50
3 17-08-2019 NULL 100 -100
4 18-08-2019 500 200 300
In the future, I'd suggest posting some table details by using SHOW CREATE TABLE table_name which will allow us to better assist you.
You should be able to use a union and some grouping to get what you are after:
SELECT
Date,
SUM(Income) as Income,
SUM(Expense) as Expense,
SUM(Income) - SUM(Expense) as Profit
FROM (
SELECT
due.date as Date,
due.amount as Income,
0 as Expense
FROM due
UNION ALL
SELECT
expenses.date as Date
0 as Income,
expenses.amount as Expense
FROM expenses
)
GROUP BY Date
I have this query to extract total_hours, start_date and end_date:
select proj.start_date, proj.end_date, sum(ifnull(work.hours_estimate,0)) as total_hours
from project_table proj
left outer join project_task work on
work.project_id = proj.id
where proj.id = 3
This query gives me a single row of result:
start_date | end_date | total_hour
----------------------------------------
2017-04-24 | 2017-05-15 | 119
What I want is to generate a daily interval of rows, constantly decreasing the total_hours by a certain amount, say 19 hours, and the day increasing by 1 day.
Expected results:
day | hours_left
------------------------
2017-04-24 | 119
2017-04-25 | 100
2017-04-26 | 81
2017-04-27 | 62
2017-04-28 | 43
2017-04-29 | 24
... and so on and so forth until it reaches 2017-05-15 (of course, no negative for hours_left, just zero if negative)
can't seem to figure out how to do this.
QUESTIONS:
1.) Is this possible in MySQL?
2.) If this is possible in MySQL, is it efficient/convinient?
If not, I could just do it in application, as state in the comments
For my website, I have a loyalty program where a customer gets some goodies if they've spent $100 within the last 30 days. A query like below:
SELECT u.username, SUM(total-shipcost) as tot
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND user = :user
AND date >= DATE(NOW() - INTERVAL 30 DAY)
:user being their user ID. Column 2 of this result gives how much a customer has spent in the last 30 days, if it's over 100, then they get the bonus.
I want to display to the user which day they'll leave the loyalty program. Something like "x days until bonus expires", but how do I do this?
Take today's date, March 16th, and a user's order history:
id | tot | date
-----------------------
84 38 2016-03-05
76 21 2016-02-29
74 49 2016-02-20
61 42 2015-12-28
This user is part of the loyalty program now but leaves it on March 20th. What SQL could I do which returns how many days (4) a user has left on the loyalty program?
If the user then placed another order:
id | tot | date
-----------------------
87 12 2016-03-09
They're still in the loyalty program until the 20th, so the days remaining doesn't change in this instance, but if the total were 50 instead, then they instead leave the program on the 29th (so instead of 4 days it's 13 days remaining). For what it's worth, I care only about 30 days prior to the current date. No consideration for months with 28, 29, 31 days is needed.
Some create table code:
create table users (
userident int,
username varchar(100)
);
insert into users values
(1, 'Bob');
create table orders (
id int,
user int,
shipped int,
date date,
total decimal(6,2),
shipcost decimal(3,2)
);
insert into orders values
(84, 1, 1, '2016-03-05', 40.50, 2.50),
(76, 1, 1, '2016-02-29', 22.00, 1.00),
(74, 1, 1, '2016-02-20', 56.31, 7.31),
(61, 1, 1, '2015-12-28', 43.10, 1.10);
An example output of what I'm looking for is:
userident | username | days_left
--------------------------------
1 Bob 4
This is using March 16th as today for use with DATE(NOW()) to remain consistent with the previous bits of the question.
The following is basically how to do what you want. Note that references to "30 days" are rough estimates and what you may be looking for is "29 days" or "31 days" as works to get the exact date that you want.
Retrieve the list of dates and amounts that are still active, i.e., within the last 30 days (as you did in your example), as a table (I'll call it Active) like the one you showed.
Join that new table (Active) with the original table where a row from Active is joined to all of the rows of the original table using the date fields. Compute a total of the amounts from the original table. The new table would have a Date field from Active and a Totol field that is the sum of all the amounts in the joined records from the original table.
Select from the resulting table all records where the Amount is greater than 100.00 and create a new table with Date and the minimum Amount of those records.
Compute 30 days ahead from those dates to find the ending date of their loyalty program.
You would need to take the following steps (per user):
join the orders table with itself to calculate sums for different (bonus) starting dates, for any of the starting dates that are in the last 30 days
select from those records only those starting dates which yield a sum of 100 or more
select from those records only the one with the most recent starting date: this is the start of the bonus period for the selected user.
Here is a query to do that:
SELECT u.userident,
u.username,
MAX(base.date) AS bonus_start,
DATE(MAX(base.date) + INTERVAL 30 DAY) AS bonus_expiry,
30-DATEDIFF(NOW(), MAX(base.date)) AS bonus_days_left
FROM users u
LEFT JOIN (
SELECT o.user,
first.date AS date,
SUM(o.total-o.shipcost) as tot
FROM orders first
INNER JOIN orders o
ON o.user = first.user
AND o.shipped = 1
AND o.date >= first.date
WHERE first.shipped = 1
AND first.date >= DATE(NOW() - INTERVAL 30 DAY)
GROUP BY o.user,
first.date
HAVING SUM(o.total-o.shipcost) >= 100
) AS base
ON base.user = u.userident
GROUP BY u.username,
u.userident
Here is a fiddle.
With this input as orders:
+----+------+---------+------------+-------+----------+
| id | user | shipped | date | total | shipcost |
+----+------+---------+------------+-------+----------+
| 61 | 1 | 1 | 2015-12-28 | 42 | 0 |
| 74 | 1 | 1 | 2016-02-20 | 49 | 0 |
| 76 | 1 | 1 | 2016-02-29 | 21 | 0 |
| 84 | 1 | 1 | 2016-03-05 | 38 | 0 |
| 87 | 1 | 1 | 2016-03-09 | 50 | 0 |
+----+------+---------+------------+-------+----------+
The above query will return this output (when executed on 2016-03-20):
+-----------+----------+-------------+--------------+-----------------+
| userident | username | bonus_start | bonus_expiry | bonus_days_left |
+-----------+----------+-------------+--------------+-----------------+
| 1 | John | 2016-02-29 | 2016-03-30 | 10 |
+-----------+----------+-------------+--------------+-----------------+
Simple solution
Seeing how you do your first query, I guessed that when you are at the point where you look for the "expiration date", you already know that the user meets the 100 points over last 30 days. Then you can do this :
SELECT DATE_ADD(MIN(date),INTERVAL 30 DAY)
FROM orders o
WHERE shipped = 1
AND user = :user
AND date >= (DATE(NOW() - INTERVAL 30 DAY))
It takes the minimum order date of a user over the last 30 days, and add 30 days to the result.
But that really is a poor design to achieve what you want.
You would better to think further and implement what's next.
Advanced solution
In order to reproduce all the following solution, I have used the Fiddle that Trincot kindly built, and expanded it to test on more data : 4 users having 4 orders.
SQL FIddle http://sqlfiddle.com/#!9/668939/1
Step 1 : Design
The following query will return all the users meeting the loyalty program criteria, along with their earlier order date within 30 days and the loyalty program expiration date calculated from the earlier date, and the number of days before it expires.
SELECT O.user, u.username, SUM(total-shipcost) as tot, MIN(date) AS mindate,
DATE_ADD(MIN(date),INTERVAL 30 DAY) AS expirationdate,
DATEDIFF(DATE_ADD(MIN(date),INTERVAL 30 DAY), DATE(NOW())) AS daysleft
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND date >= DATE(NOW() - INTERVAL 30 DAY)
GROUP BY user
HAVING tot >= 100;
Now, create a VIEW with the above query
CREATE VIEW loyalty_program AS
SELECT O.user, u.username, SUM(total-shipcost) as tot, MIN(date) AS mindate,
DATE_ADD(MIN(date),INTERVAL 30 DAY) AS expirationdate,
DATEDIFF(DATE_ADD(MIN(date),INTERVAL 30 DAY), DATE(NOW())) AS daysleft
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND date >= DATE(NOW() - INTERVAL 30 DAY)
GROUP BY user
HAVING tot >= 100;
It is important to understand that this is only a one-shot action on your database.
Step 2 : Use your new VIEW
Once you have the view, you can get easily, for all users, the "state" of the loyalty program:
SELECT * FROM loyalty_program
user username tot mindate expirationdate daysleft
1 John 153 February, 28 2016 March, 29 2016 9
2 Joe 112 February, 24 2016 March, 25 2016 5
3 Jack 474 February, 23 2016 March, 24 2016 4
4 Averel 115 February, 22 2016 March, 23 2016 3
For a specific user, you can get the date you are looking for like this:
SELECT expirationdate FROM loyalty_program WHERE username='Joe'
You can also request all the users for which the expiration date is today
SELECT user FROM loyalty_program WHERE expirationdate=DATE(NOW))
But there are other easy possibilities that you'll discover after having played with your VIEW.
Conclusion
Make your life easier: learn to use VIEWS !
I am assuming your table looks like this:
user | id | total | date
-------------------------------
12 84 38 2016-03-05
12 76 21 2016-02-29
23 74 49 2016-02-20
23 61 42 2015-12-28
then try this:
SELECT x.user, x.date, x.id, x.cum_sum, d,date, DATEDIFF(NOW(), x.date) from (SELECT a.user, a.id, a.date, a.total,
(SELECT SUM(b.total) FROM order_table b WHERE b.date <= a.date and a.user=b.user ORDER BY b.user, b.id DESC) AS cum_sum FROM order_table a where a.date>=DATE(NOW() - INTERVAL 30 DAY) ORDER BY a.user, a.id DESC) as x
left join
(SELECT c.user, c.date as start_date, c.id from (SELECT a.user, a.id, a.date, a.total,
(SELECT SUM(b.total) FROM order_table b WHERE b.date <= a.date and a.user=b.user ORDER BY b.user, b.id DESC) AS cum_sum FROM order_table a where a.date>=DATE(NOW() - INTERVAL 30 DAY) ORDER BY a.user, a.id DESC) as c WHERE FLOOR(c.cum_sum/100)=MIN(FLOOR(c.cum_sum/100)) and MOD(c.cum_sum,100)=MAX(MOD(c.cum_sum,100)) group by concat(c.user, "_", c.id)) as d on concat(x.user, "_", x.id)=concat(d.user, "_", d.id) where x.date=d.date;
You will get a table something like this:
user | Date | cum_sum | start_date | Time_left
----------------------------------------------------
12 2016-03-05 423 2016-03-05 24
13 2016-02-29 525 2016-02-29 12
23 2016-02-20 944 2016-02-20 3
29 2015-12-28 154 2015-12-28 4
i have not tested this. But what i am trying to do is to create a table in descending order of id and user, and get a cumulative total column along with it. I have created another table by using this table with cumulative total, with relevant date (i.e. date from which date difference is to be calculated) for each user. I have left joined these two tables, and put in the condition x.date=d.date. I have put start_date and date in the table to check if the query is working.
Also, this is not the most optimum way of writing this code, but i have tried to stay as safe as possible by using sub queries, since i did not have the data to test this. Let me know if you face any error.
There is a query I am trying to implement in which I am not having much success with in trying to find the MAX and MIN for each week.
I have 2 Tables:
SYMBOL_DATA (contains open,high,low,close, and volume)
WEEKLY_LOOKUP (contains a list of weeks(no weekends) with a WEEK_START and WEEK_END)
**SYMBOL_DATA Example:**
OPEN, HIGH, LOW, CLOSE, VOLUME
23.22 26.99 21.45 22.49 34324995
WEEKLY_LOOKUP (contains a list of weeks(no weekends) with a WEEK_START and WEEK_END)
**WEEKLY_LOOKUP Example:**
WEEK_START WEEK_END
2016-01-25 2016-01-29
2016-01-18 2016-01-22
2016-01-11 2016-01-15
2016-01-04 2016-01-08
I am trying to find for each WEEK_START and WEEK_END the high and low for that particular week.
For instance, if the WEEK is WEEK_START=2016-01-11 and WEEK_END=2016-01-15, I would have
5 entries for that particular symbol listed:
DATE HIGH LOW
2016-01-15 96.38 93.54
2016-01-14 98.87 92.45
2016-01-13 100.50 95.21
2016-01-12 99.96 97.55
2016-01-11 98.60 95.39
2016-01-08 100.50 97.03
2016-01-07 101.43 97.30
2016-01-06 103.77 100.90
2016-01-05 103.71 101.67
2016-01-04 102.24 99.76
For each week_ending (2016-01-15) the HIGH is 100.50 on 2016-01-13 and the LOW is 92.45 on 2016-01-14
I attempted to write a query that gives me a list of highs and lows, but when I tried adding a MAX(HIGH), I had only 1 row returned back.
I tried a few more things in which I couldn't get the query to work (some sort of infinite run type). For now, I just have this that gives me a list of highs and lows for every day instead of the roll-up for each week which I am not sure how to do.
select date, t1.high, t1.low
from SYMBOL_DATA t1, WEEKLY_LOOKUP t2
where symbol='ABCDE' and (t1.date>=t2.START_DATE and t1.date<=t2.END_DATE)
and t1.date<=CURDATE()
LIMIT 30;
How can I get for each week (Start and End) the High_Date, MAX(High), and Low_Date, MIN(LOW) found each week? I probably don't need a
full history for a symbol, so a LIMIT of like 30 or (30 week periods) would be sufficient so I can see trending.
If I wanted to know for example each week MAX(High) and MIN(LOW) start week ending 2016-01-15 the result would show
**Result:**
WEEK_ENDING 2016-01-15 100.50 2016-01-13 92.45 2016-01-14
WEEK_ENDING 2016-01-08 103.77 2016-01-06 97.03 2016-01-08
etc
etc
Thanks to all of you with the expertise and knowledge. I greatly appreciate your help very much.
Edit
Once the Week Ending list is returned containing the MAX(HIGH) and MIN(LOW) for each week, is it possible then on how to find the MAX(HIGH) and MIN(LOW) from that result set so it return then only 1 entry from the 30 week periods?
Thank you!
To Piotr
select part1.end_date,part1.min_l,part1.max_h, s1.date, part1.min_l,s2.date from
(
select t2.start_date, t2.end_date, max(t1.high) max_h, min(t1.low) min_l
from SYMBOL_DATA t1, WEEKLY_LOOKUP t2
where symbol='FB'
and t1.date<='2016-01-22'
and (t1.date>=t2.START_DATE and t1.date<=t2.END_DATE)
group by t2.start_date, t2.end_date order by t1.date DESC LIMIT 1;
) part1, symbol_data s1, symbol_data s2
where part1.max_h = s1.high and part1.min_l = s2.low;
You will notice that the MAX and MIN for each week is staying roughly the same and not changing as it should be different for week to week for both the High and Low.
SQL Fiddle
I have abbreviated some of your names in my example.
Getting the high and low for each week is pretty simple; you just have to use GROUP BY:
SELECT s1.symbol, w.week_end, MAX(s1.high) AS weekly_high, MIN(s1.LOW) as weekly_low
FROM weeks AS w
INNER JOIN symdata AS s1 ON s1.zdate BETWEEN w.week_start AND w.week_end
GROUP BY s1.symbol, w.week_end
Results:
| symbol | week_end | weekly_high | weekly_low |
|--------|---------------------------|-------------|------------|
| ABCD | January, 08 2016 00:00:00 | 103.77 | 97.03 |
| ABCD | January, 15 2016 00:00:00 | 100.5 | 92.45 |
Unfortunately, getting the dates of the high and low requires that you re-join to the symbol_data table, based on the symbol, week and values. And even that doesn't do the job; you have to account for the possibility that there might be two days where the same high (or low) was achieved, and decide which one to choose. I arbitrarily chose the first occurrence in the week of the high and low. So to get that second level of choice, you need another GROUP BY. The whole thing winds up looking like this:
SELECT wl.symbol, wl.week_end, wl.weekly_high, MIN(hd.zdate) as high_date, wl.weekly_low, MIN(ld.zdate) as low_date
FROM (
SELECT s1.symbol, w.week_start, w.week_end, MAX(s1.high) AS weekly_high, MIN(s1.low) as weekly_low
FROM weeks AS w
INNER JOIN symdata AS s1 ON s1.zdate BETWEEN w.week_start AND w.week_end
GROUP BY s1.symbol, w.week_end) AS wl
INNER JOIN symdata AS hd
ON hd.zdate BETWEEN wl.week_start AND wl.week_end
AND hd.symbol = wl.symbol
AND hd.high = wl.weekly_high
INNER JOIN symdata AS ld
ON ld.zdate BETWEEN wl.week_start AND wl.week_end
AND ld.symbol = wl.symbol
AND ld.low = wl.weekly_low
GROUP BY wl.symbol, wl.week_start, wl.week_end, wl.weekly_high, wl.weekly_low
Results:
| symbol | week_end | weekly_high | high_date | weekly_low | low_date |
|--------|---------------------------|-------------|---------------------------|------------|---------------------------|
| ABCD | January, 08 2016 00:00:00 | 103.77 | January, 06 2016 00:00:00 | 97.03 | January, 08 2016 00:00:00 |
| ABCD | January, 15 2016 00:00:00 | 100.5 | January, 13 2016 00:00:00 | 92.45 | January, 14 2016 00:00:00 |
To get the global highs and lows, just remove the weekly table from the original query:
SELECT wl.symbol, wl.high, MIN(hd.zdate) as high_date, wl.low, MIN(ld.zdate) as low_date
FROM (
SELECT s1.symbol, MAX(s1.high) AS high, MIN(s1.low) as low
FROM symdata AS s1
GROUP BY s1.symbol) AS wl
INNER JOIN symdata AS hd
ON hd.symbol = wl.symbol
AND hd.high = wl.high
INNER JOIN symdata AS ld
ON ld.symbol = wl.symbol
AND ld.low = wl.low
GROUP BY wl.symbol, wl.high, wl.low
Results:
| symbol | high | high_date | low | low_date |
|--------|--------|---------------------------|-------|---------------------------|
| ABCD | 103.77 | January, 06 2016 00:00:00 | 92.45 | January, 14 2016 00:00:00 |
The week table seems entirely redundant...
SELECT symbol
, WEEK(zdate)
, MIN(low) min
, MAX(high) max_high
FROM symdata
GROUP
BY symbol, WEEK(zdate);
This is a simplified example. In reality, you might use DATE_FORMAT or something like that instead.
http://sqlfiddle.com/#!9/c247f/3
Check if following query produces desired result:
select part1.end_date,part1.min_l,part1.max_h, s1.date, part1.min_l,s2.date from
(
select t2.start_date, t2.end_date, max(t1.high) max_h, min(t1.low) min_l
from SYMBOL_DATA t1, WEEKLY_LOOKUP t2
where symbol='ABCDE'
and (t1.date>=t2.START_DATE and t1.date<=t2.END_DATE)
group by t2.start_date, t2.end_date
) part1, symbol_data s1, symbol_data s2
where part1.max_h = s1.high and part1.min_l = s2.low
and (s1.date >= part1.start_date and part1.end_date)
and (s2.date >= part1.start_date and part1.end_date)
I want to return a report that looks like the following:
unit | days | stays | income | charges | mgmt fee | bal
1001 20 6 775.00 1500.00 310.00 0.00
1002 40 14 5000.00 200.00 2100.00 2700.00
1003 50 20 6000.00 10.00 2500.00 3490.00
So the bal is (income - (charges+mgmt fee).
I have tables that look like this:
Unit
id | name | mgmt_fee
1001 blossom 30
1002 charlie 25
1003 deniro 30
1004 archie 20
1005 lilly 25
The mgmt fee is used as a percentage (%)
Reservations
id | unit | arrival | depart | total_price
10111 1001 2014-02-09 2014-02-13 400.00
10012 1001 2014-03-10 2014-03-15 300.00
10145 1002 2014-04-01 2014-04-05 600.00
10043 1003 2014-05-30 2014-06-03 350.00
NOTE: these are not actual data. It is a representation of my fields and what their data may look like, though.
Charges
id | unit | amount | charged_dtm
1 1001 40.00 2014-03-24 19:04:31
2 1001 30.00 2014-03-25 20:51:08
3 1002 100.00 2014-04-05 12:52:25
**There are cases where there may not be charges for a unit in a given month.
I have tried the following query:
SELECT u.name AS unit,
(r.departure-r.arrival) AS days,
COUNT(r.id) AS stays,
SUM(r.total_price) AS income,
SUM(c.amount) AS charges,
(SUM(r.total_price)*(.01*u.mgmt_fee)) AS management,
((SUM(r.total_price)-(SUM(r.total_price)*
(.01*u.mgmt_fee)))-SUM(c.amount)) AS bal
FROM reservations r
JOIN units u ON r.unit = u.id
JOIN charges c ON u.id = c.unit
GROUP BY u.id
It will return one unit and all the other data is totaled together. It is really odd.
Now, I tried removing everything and adding on one a time to find the culprit.
I found that up to this query I am good.
SELECT CONCAT(u.unit_name,', "',u.unit_nickname,'"') AS unit,
SUM(r.departure-r.arrival) AS days,
COUNT(r.id) AS stays,
SUM(r.total_price) AS income
FROM reservations r
JOIN units u ON r.unit = u.id
GROUP BY u.id
Once I add in the charges is when the query goes astray.
Any ideas on how to accomplish my desired result?
I think you can't do it all in one go as you are trying to because you want to group/sum items from different tables at the same time. Try this:
select unit, name, stays, days, total_price, charges, mgmt_fee,
(total_price*.01*mgmt_fee) AS management,
(total_price-(total_price*.01*mgmt_fee)-charges) AS bal
from (
select
x.unit,
name,
count(*) as stays,
sum(days) as days,
sum(total_price) as total_price,
charges,
mgmt_fee
from
(select
unit ,
datediff(depart,arrival) as days,
total_price
from
Reservations
) as x
join (
select unit, sum(amount) as charges
from Charges
group by unit
) as y
on x.unit = y.unit
join Unit as u
on u.id = x.unit
group by unit
) as inner_result
Not very tidy or elegant, but I think it works.
Note that you will have to be careful if there can be > 1 row in charges per unit.
Here is a fiddle to show it running: http://sqlfiddle.com/#!2/f05ba/18