Get total of referenced tables' column - mysql

Consider a database of accounts and deposits:
CREATE TABLE accounts (
id int not null primary key,
name varchar(63)
);
CREATE TABLE deposits (
id int not null primary key,
account int references accounts(id),
dollars decimal(15, 2),
status enum('pending','complete')
);
insert into accounts values
(0, 'us'),
(1, 'europe'),
(2, 'asia');
insert into deposits values
(0, 0, 10, 'pending'),
(1, 0, 20, 'complete'),
(2, 1, 100, 'complete'),
(3, 1, 200, 'pending'),
(4, 1, 300, 'complete'),
(5, 2, 1000, 'pending');
I would like to get a total of all the complete deposits per bank, this is the expected result:
+--------+-----+
| us | 20 |
| europe | 400 |
| asia | 0 |
+--------+-----+
This is the SQL that I tried, but it does not work as expected:
SELECT
a.name, SUM(d.dollars)
FROM
accounts a
INNER JOIN
deposits d ON (a.id = d.account AND d.status='complete');
This is the result that it gave:
+--------+-----+
| us | 420 |
+--------+-----+
Here is an SQLfiddle of the current code.
What have I done wrong, and how can I get the expected sum?

try this
SELECT
a.name, coalesce(SUM(d.dollars),0) as sums
FROM
accounts a
left JOIN
deposits d ON (a.id = d.account AND d.status='complete')
group by a.name
order by sums desc
you should use LEFT JOIN , and you should use GROUP BY also.
LOOK DEMO

You should use grouping by a.name (or maybe even a.id) and LEFT OUTER JOIN (if you want to get non-present values).
EDIT:
SELECT
a.name, SUM(d.dollars)
FROM
accounts a
LEFT OUTER JOIN
deposits d ON (a.id = d.account AND d.status='complete')
GROUP BY a.name;

Try this query:
SELECT
a.name, IF(SUM(d.dollars) IS NULL, 0, SUM(d.dollars) )
FROM
accounts a
LEFT JOIN
deposits d ON (a.id = d.account AND d.status='complete')
GROUP BY a.name ORDER BY a.id;

By joining accounts with deposits you only make sure that you sum dollars for deposits that are linked to an account. If you would also Group by the account name, or even account Id then you will get a Sum/bank.

Related

Return the first occurrence of a user_id from a result set

I have two tables (simplified to):
+----------------+
| attendances |
+-----+----------+
| int | user_id |
+-----+----------+
| int | event_id |
+-----+----------+
+-------------------------+
| events |
+------+------------------+
| int | id |
+------+------------------+
| date | performance_date |
+------+------------------+
And a simple query:
SELECT count(DISTINCT user_id), events.performance_date
FROM attendances
INNER JOIN events
ON event_id = events.id
GROUP BY performance_date
I only wish to count each user_id once, but the above query only removes the duplicates from each performance_date (allowing them to be duplicated across multiple dates).
Is there a query that can remove duplicate user_ids from the entire result set, and only include the first occurence (date wise)? I'm suspecting it might not be possible.
Input/output examples:
If a user attended an event on 2010-10-10 and again on 2010-10-11, then the results would be:
1, 2010-10-10
Not:
1, 2010-10-10
1, 2010-10-11
Or:
2, 2010-10-10
If another user was added to the above, and they attended on 2010-10-10 and on 2010-10-12, then the results would be:
2, 2010-10-10
1, 2020-10-12
As I say, this may not be possible. The actual output isn't strictly important -- just so long as the unique number of people who attended a particular performance can be derived somehow.
The data will be used to construct a cumulative graph of the growth in the number of unique users by event.
If you want the earliest date per user, you can use aggregation:
select u.id user_id, min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
Actually, you might be looking for histogram, that is the number of users per their earliest event date. You can do this by adding another level of aggregation:
select first_event_date, count(*) no_users
from (
select min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
) t
group by first_event_date
If you want to count all new users per event, you could use the following query:
SELECT Count(u.user_id),
e.performance_date
FROM attendances u
INNER JOIN `events` e
ON u.event_id = e.id
WHERE NOT EXISTS(SELECT u1.user_id
FROM attendances u1
INNER JOIN `events` e1
ON u1.event_id = e1.id
WHERE u1.user_id = u.user_id
AND e1.performance_date < e.performance_date)
GROUP BY performance_date
ORDER BY performance_date
I tested it with the following set:
CREATE TABLE attendances
(
user_id INT,
event_id INT
);
CREATE TABLE `events`
(
id INT,
performance_date DATE
);
INSERT INTO attendances
(user_id,
event_id)
VALUES ( 1, 1),
( 1, 2),
( 2, 1),
( 2, 2),
( 3, 1),
( 4, 2);
INSERT INTO `events`
(id,
performance_date)
VALUES ( 1, '2020-07-24'),
( 2, '2020-07-25');
And then the result is
3 2020-07-24
1 2020-07-25

SQL - How to get total sum based on two tables?

So I have two tables, one is companies orders (order id, customer name, sum) and other is debts (order id) which includes orders that haven't been paid. I need to get customers with biggest total debt. Some customers have made more than 1 order. What is the best solution to do this? Thank you very much!
Orders table:
CREATE TABLE orders( order_id INT PRIMARY KEY, name VARCHAR(30), sum INT );
INSERT INTO orders VALUES
(1, 'Jack Smith', 123),
(2, 'Mary Jane', 61),
(3, 'John McCane', 90),
(4, 'Jack Smith', 512),
(5, 'Mary Jane', 33);
Debts table:
CREATE TABLE debts( order_id INT PRIMARY KEY );
INSERT INTO debts VALUES
(1),(4),(5);
Right now I have something like this:
SELECT name,SUM(sum) FROM orders INNER JOIN debts ON orders.order_id = debts.order_id GROUP BY name;
+------------+----------+
| name | SUM(sum) |
+------------+----------+
| Jack Smith | 635 |
| Mary Jane | 33 |
+------------+----------+
Desired result would look like this:
name sum
Jack Smith 635
Mary Jane 94
John McCane 90
select name,sum from orders o inner join debts d on o.order_id = d.order_id order by o.sum desc
Will give you all customers with debt ordered in descending order. Add limit 1 if you want the customer with highest debt
select name,sum from orders o inner join debts d on o.order_id = d.order_id order by o.sum desc limit 1
SELECT SUM(sum) FROM orders WHERE id IN (select order_id FROM debt)
This might help you
This is what I was looking for:
SELECT name,SUM(sum) FROM orders INNER JOIN debts ON orders.order_id = debts.order_id GROUP BY name;
Thank you everyone for answers.

MySQL - Join if no duplicate

I want to Join to table. the condition is I want to only join those rows which have only one row to match. eg.
books:
id | name | price
1 | book1 | 19
2 | book2 | 19
3 | book3 | 30
price_offer:
id | offer | price
1 | offer1 | 19
2 | offer2 | 30
so now if I do select query on these table:
SELECT * FROM price_offer
JOIN books ON price_offer.price = books.price
I only want to join book with id 3 as it have only one match with price_offer table.
You could use a self join for books table to pick a book with only single match
select po.*, b1.*
from price_offer po
join books b1 on po.price = b1.price
join (
select price,max(id) id
from books
group by price
having count(*) = 1
) b2 on b1.id = b2.id
Demo
Try following query:
Sample data:
create table books(id int, name varchar(10), price int);
insert into books values
(1, 'book1', 19),
(2, 'book2', 19),
(3, 'book3', 30);
create table price_offer(id int, offer varchar(10), price int);
insert into price_offer values
(1, 'offer1', 19),
(2, 'offer2', 30);
Query:
select max(b.id)
from price_offer p
left join books b on b.price = p.price
where p.id is not null
group by b.price
having count(*) = 1;
If you want to avoid nesting queries where you have to use self-joins, you can use window-functions of MySQL 8.0.11, which are exactly for cases like this

Joining table to union of two tables?

I have two tables: orders and oldorders. Both are structured the same way. I want to union these two tables and then join them to another table: users. Previously I only had orders and users, I am trying to shoehorn oldorders into my current code.
SELECT u.username, COUNT(user) AS cnt
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user
This finds the number of nonzero total orders all users have made in table orders, but I want to this in the union of orders and oldorders. How can I accomplish this?
create table orders (
user int,
shipped int,
total decimal(4,2)
);
insert into orders values
(5, 1, 28.21),
(5, 1, 24.12),
(5, 1, 19.99),
(5, 1, 59.22);
create table users (
username varchar(100),
userident int
);
insert into users values
("Bob", 5);
Output for this is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 4 |
+----------+-----+
After creating the oldorders table:
create table oldorders (
user int,
shipped int,
total decimal(4,2)
);
insert into oldorders values
(5, 1, 62.94),
(5, 1, 53.21);
The expected output when run on the union of the two tables is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 6 |
+----------+-----+
Just not sure where or how to shoehorn a union into there. Instead of running the query on orders, it needs to be on orders union oldorders. It can be assumed there is no intersect between the two tables.
You just need to union this way:
SELECT u.username, COUNT(user) AS cnt
FROM
(
SELECT * FROM orders
UNION
SELECT * FROM oldorders
) o
LEFT JOIN users u ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user;
First get the combined orders using UNION between orders and oldorders table.
The rest of the work is exactly same what you did.
SEE DEMO
Note:
Left join doesn't make sense in this case. Orders for which the users don't exist then you will get NULL 0 as output. This doesn't hold any value.
If you want <user,total orders> for all users including users who might not have ordered yet then you need to change the order of the LEFT JOIN

MySQL sum with group by gives wrong results

Here's the SQLFiddle with schema and data.
I'm trying to sum 2 columns, one at parent level and the other at child level.
The current query I'm using gives me the right sum amount on child level, but doubles up the amount on parent level, due to another 1-many relationship involved on the child level.
Ugh... that's a terrible explanation - here's the English version:
Joe the salesman is involved in 2 sales.
For the 1st sale, he get's 2 sets of commissions, based on 2 different commission types. I'm trying to show Joe's total sale value, alongside the total value of his applicable splits. The split value total is fine, but sale value get's doubled up because I'm obviously, grouping/joining incorrectly (see the last example below).
This is fine:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl
where s.product_id = pr.id
and s.id = spl.sale_id
and sp.id = spl.sales_person_id
group by sp.id;
person_name | description | SplitValue
----------- ----------- | ----------
Joe | Widget 1 | 50
Sam | Widget 1 | 10
This is also yields the correct split and sale values, but now 3 rows are displayed for Joe (i.e 2nd row is a duplicate of the 1st one) - I only want to display Joe's "Widget 1" sale once, so not correct:
select sp.person_name, pr.description,
sum(s.sale_value) as SaleValue, sum(spl.split) as SplitValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id, spl.id;
person_name | description | SplitValue | SaleValue
----------- ----------- ---------- ---------
Joe | Widget 1 | 10 | 20
Joe | Widget 1 | 10 | 20
Joe | Widget 2 | 30 | 30
Sam | Widget 1 | 10 | 20
Now the duplicated row is gone, but Joe's SaleValue is incorrect - it should be 50, not 70:
select sp.person_name, pr.description,
sum(spl.split) as SplitValue, sum(s.sale_value) as SaleValue
from sale s, product pr, sales_person sp, sales_split spl, sales_split_agreement ssa
where s.id = spl.sale_id
and s.product_id = pr.id
and sp.id = spl.sales_person_id
and sp.id = ssa.sales_person_id
and spl.sales_person_id = ssa.sales_person_id
and ssa.id = spl.sales_split_agreement_id
group by sp.id;
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 70
Sam | Widget 1 | 10 | 20
I.e. I'm after the query that will yield this result (i.e. Joe's correct SaleValue of 50):
person_name | description | SplitValue | SaleValue
----------- ----------- --------- ----------
Joe | Widget 1 | 50 | 50
Sam | Widget 1 | 10 | 20
Any help will be greatly appreciated!
UPDATE 1:
For clarity - here's the schema and test data from the fiddle:
CREATE TABLE product
(`id` int, `description` varchar(12))
;
INSERT INTO product
(`id`, `description`)
VALUES
(1, 'Widget 1'),
(2, 'Widget 2')
;
CREATE TABLE sales_person
(`id` int, `person_name` varchar(7))
;
INSERT INTO sales_person
(`id`, `person_name`)
VALUES
(1, 'Joe'),
(2, 'Sam')
;
CREATE TABLE sale
(`id` int, `product_id` int, `sale_value` int)
;
INSERT INTO sale
(`id`, `product_id`, `sale_value`)
VALUES
(1, 1, 20.00),
(2, 2, 30.00)
;
CREATE TABLE split_type
(`id` int, `description` varchar(6))
;
INSERT INTO split_type
(`id`, `description`)
VALUES
(1, 'Type 1'),
(2, 'Type 2')
;
CREATE TABLE sales_split_agreement
(`id` int, `sales_person_id` int, `split_type_id` int, `percentage` int)
;
INSERT INTO sales_split_agreement
(`id`, `sales_person_id`, `split_type_id`, `percentage`)
VALUES
(1, 1, 1, 50),
(2, 1, 2, 50),
(3, 2, 1, 50),
(4, 1, 1, 100)
;
CREATE TABLE sales_split
(`id` int, `sale_id` int, `sales_split_agreement_id` int, `sales_person_id` int, `split` int )
;
INSERT INTO sales_split
(`id`, `sale_id`, `sales_split_agreement_id`, `sales_person_id`, `split`)
VALUES
(1, 1, 1, 1, 10),
(2, 1, 2, 1, 10),
(3, 1, 3, 2, 10),
(4, 2, 4, 1, 30)
;
I think you were on to the right track, but I decided to restart and approach from the beginning. Getting the SplitValue for each person does not require all those tables. In fact, all you need are sales_split and sales_person, like this:
SELECT sp.person_name, SUM(ss.split) AS SplitValue
FROM sales_person sp
JOIN sales_split ss ON sp.id = ss.sales_person_id
GROUP BY sp.id;
Similarly, you can get the total sale value for each person with a join between sale, sales_split, and sales_person:
SELECT sp.person_name, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
JOIN sales_person sp ON sp.id = ss.sales_person_id
GROUP BY sp.id;
At this point, I realize you have an error in your expected results (for this data set). Joe does in fact have a sale value of 70, because sale id 1 (value 20), 2 (value 20), and 4 (value 30) add up to 70. However, I still think this query will help you out more than the one you have.
At this point, you can get the values for each sales_person_id by joining those two subqueries to the sales_person table. I took out the join to sales_person in the subqueries, as it became irrelevant now. It even makes the subqueries a little cleaner:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT ss.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM sale s
JOIN sales_split ss ON ss.sale_id = s.id
GROUP BY ss.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is an SQL Fiddle example.
EDIT: I understand now why Joe's actual sale price is 50, because he split twice on sale id 1. To work around this, I first got a list of distinct sales for each sales_person like this:
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split;
This way, there is only one row for sales_person_id = 1 and sale_id = 1. Then, it was easy enough to join that to the sale table and get the proper sales value for each sales_person:
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id;
The rest of my answer above still fits. I wrote one query to get SplitValue, and one query to get SaleValue, and I joined them together. So, all I have to do now is replace the subquery I just gave you, with the incorrect subquery from further up:
SELECT sp.person_name, COALESCE(t1.SplitValue, 0) AS SplitValue, COALESCE(t2.SaleValue, 0) AS SaleValue
FROM sales_person sp
LEFT JOIN(
SELECT ss.sales_person_id, SUM(ss.split) AS SplitValue
FROM sales_split ss
GROUP BY ss.sales_person_id) t1 ON t1.sales_person_id = sp.id
LEFT JOIN(
SELECT t.sales_person_id, SUM(s.sale_value) AS SaleValue
FROM(
SELECT DISTINCT sale_id, sales_person_id
FROM sales_split) t
JOIN sale s ON s.id = t.sale_id
GROUP BY t.sales_person_id) t2 ON t2.sales_person_id = sp.id;
Here is the updated SQL Fiddle.
You mentioned in the comments that you shortened your data for brevity, which is fine. I am leaving my joins as they are, and I trust that it gives you enough direction that you can adjust them accordingly to match your proper structure.