Sum, Group Concat and Join with 3 tables. - mysql

I am working on a tool to administer customer and payment data.
I use MySQL and have the following tables: customers and payments:
customers:
ID | invoiceID | supreme_invoiceID
1 123 a123
2 124 a123
3 103 a103
4 110 a110
payments:
ID | supreme_invoiceID | amount | date
1 a123 10 10.10.2010
2 a103 105 10.11.2017
3 a123 5 11.10.2010
And my result should look like this:
view_complete:
ID | supreme_invoideID | number_invoices | GROUP_CONCAT(invoiceID) | SUM(payments.amount) | GROUP_CONCAT(payments.amount)
1 a123 2 123;124 15 10;15
Unfortunately, I cannot get it directly into one table. Instead I create 2 views and query the payments table separately for aggregate data on payments.
First, I create an auxiliary view:
CREATE VIEW precomplete as
SELECT *, COUNT(supreme_invoiceID) as number_invoices FROM customers
GROUP BY supreme_invoiceID;
Then, a second one:
Then I take a second VIEW
CREATE VIEW complete AS
SELECT precomplete.*, SUM(payments.amount)
LEFT JOIN payments p ON precomplete.supreme_invoiceID = p.supreme_invoiceID
GROUP BY precomplete.supreme_invoiceID;
And the concatenated Values I receive in an additional query. But I would like to receive my data all in one query and hopefully, without such view hierarchy. PhpMyAdmin is already pretty slow in loading my views even with few entries.
Any help is greatly appreciated.
Thanks!

The db design forces an approach which builds the aggregates separately to avoid duplicates before joining on a common field for example
drop table if exists c,p;
create table c(ID int, invoiceID int, supreme_invoiceID varchar(4));
insert into c values
(1 , 123 , 'a123'),
(2 , 124 , 'a123'),
(3, 103 , 'a103'),
(4 , 110 , 'a110');
create table p(ID int, supreme_invoiceID varchar(4), amount int, date varchar(10));
insert into p values
(1 , 'a123' , 10 , '10.10.2010'),
(2 , 'a103' , 105 , '10.11.2017'),
(3 , 'a123' , 5 , '11.10.2010');
select c.*,p.*
from
(select min(c.id) minid,count(*) nofinvoices,group_concat(c.invoiceid) gciid, max(supreme_invoiceid) maxsid
from c
group by supreme_invoiceid
) c
join
(select group_concat(supreme_invoiceid) gcsid, sum(amount),group_concat(amount),max(supreme_invoiceid) maxsid
from p
group by supreme_invoiceid
) p
on p.maxsid = c.maxsid
order by minid
;
+-------+-------------+---------+--------+-----------+-------------+----------------------+--------+
| minid | nofinvoices | gciid | maxsid | gcsid | sum(amount) | group_concat(amount) | maxsid |
+-------+-------------+---------+--------+-----------+-------------+----------------------+--------+
| 1 | 2 | 123,124 | a123 | a123,a123 | 15 | 10,5 | a123 |
| 3 | 1 | 103 | a103 | a103 | 105 | 105 | a103 |
+-------+-------------+---------+--------+-----------+-------------+----------------------+--------+
2 rows in set (0.15 sec)
Much like your view approach. Note there doesn't appear to be a customer in the customer table

Related

Inserting rows into a MySQL DB based on what is missing

I've this table called Runks (a Runk is basically like a challenge in this game that I'm making).
Every game can hold 4 users. Thus per round 4 Runks will be created. 1 round will last 24 hours.
At the end of the round the status of these Runks changes.
However I am running into a problem. If one or more of the users neglected to upload Runk in the meantime I need to create an empty Runk for them in the database.
This query:
SELECT runk_group_id, COUNT(runk_id)
FROM runks
WHERE runk_status = 'ACTIVE'
GROUP BY runk_group_id
Would output this:
This should then result in a next query creating 5 Runks.
1 Runk needs to be created for group_id 32
1 Runk needs to be created for group_id 35
3 Runks need to be created for group_id 44
Also one thing that needs to be taken into is the fact that I need new Runks created with the player ids that have not yet uploaded a Runk.
So if for group 32 player 1, 2 & 3 have already uploaded a Runk... the Runk that will need to be created needs to belong to player 4.
This is what my table looks like:
For the sake of an answer, here is a simplified example (apologies for the terrible naming...):
CREATE TABLE users (
user_id int,
);
INSERT INTO users (1), (2), (3);
CREATE TABLE users_list (
user_id int
);
INSERT INTO users_list values (1), (1), (1), (3);
-- SELECT as shown
SELECT user_id, count(user_id)
FROM users_list
GROUP BY user_id;
+---------+----------------+
| user_id | count(user_id) |
+---------+----------------+
| 1 | 3 |
| 3 | 1 |
+---------+----------------+
-- Incorrect, count includes all an entry even if the left join has nulls
SELECT u.user_id, count(u.user_id)
FROM users u
LEFT JOIN users_list ul ON u.user_id = ul.user_id
GROUP BY u.user_id;
# Gives - WRONG
+---------+------------------+
| user_id | count(u.user_id) |
+---------+------------------+
| 1 | 3 |
| 2 | 1 |
| 3 | 1 |
+---------+------------------+
-- Doesn't include the nulls in the count so we ge the correct answer
SELECT u.user_id, count(ul.user_id)
FROM users u
LEFT JOIN users_list ul ON u.user_id = ul.user_id
GROUP BY u.user_id;
+---------+-------------------+
| user_id | count(ul.user_id) |
+---------+-------------------+
| 2 | 0 |
| 1 | 3 |
| 3 | 1 |
+---------+-------------------+

How to do nested sum in group by in optimized/one query in MYSQL?

I've 3 tables, Manager, Employee & Salary. Following is the structure of all the tables.
Manager
id | Name
---------
111 | AAA
222 | BBB
Employee
id | Name | Manager_id | new_policy_deductions
----------------------------------
1 | A B | 111 | 100
2 | A C | 111 | 200
3 | C D | 222 | 200
Salary
id | employee_id | Month | Emp_Salary | Manager_id
---------------------------------------------------
1 | 1 | Jan | 500 | 111
2 | 1 | Feb | 500 | 111
3 | 1 | Mar | 600 | 111
4 | 2 | Apr | 500 | 111
5 | 1 | Apr | 700 | 111
6 | 3 | Mar | 300 | 222
7 | 3 | Apr | 500 | 222
employee_id is foreign key from Employee table to Salary table & manager_id is foreign key from Manager table to other tables.
Now, I need to construct a query such that I get following result.
Manager_id | Net_Salary
-----------------------
111 | 2500
222 | 600
How did I reached that numbers?
Take sum of salaries of all the employees under one manager (500 + 500 + 600 + 500 + 700 = 2800) & then subtract all new_policy_deductions in that manager (100 + 200 = 300). It implies 111 will have 2500 (2800 - 300).
Similarly, for 222 we have 600.
I was able to achieve this using 2 queries, which are as follows,
x = select manager_id, sum(Emp_Salary) from Salary group by manager_id
y = select manager_id, sum(new_policy_deductions) from Employee group by manager_id
result = x - y
Can this be achieved in single SQL query? If yes, how?
Note:
The actual table names are different then I used here.
I can't modify table structure. It was designed long time ago.
Nested SQL query is not allowed, as that is equivalent to 2 queries, and it will be inefficient.
Edit:
Following are the queries, which will help in creating dummy data.
create table manager (id int, name text);
create table employee (id int, name text, manager_id int, new_policy_deductions int);
create table salary(id int, employee_id int, emp_salary int, manager_id int);
select * from manager;
INSERT INTO manager
(`id`,
`name`)
VALUES
(111,'AAA'), (222,'BBB');
select * from employee;
INSERT INTO employee
(`id`,
`name`,
`manager_id`,
`new_policy_deductions`)
VALUES
(1,'A B',111,100), (2,'A C B',111,200), (3,'C A B',222,200);
select * from salary;
INSERT INTO salary
(`id`,
`employee_id`,
`month`,
`emp_salary`,
`manager_id`)
VALUES
(1,1,'Jan',500,111), (2,1,'Feb',500,111), (3,1,'Mar',600,111), (4,2,'Apr',500,111), (5,1,'Apr',700,111), (6,3,'Mar',300,222), (7,3,'Apr',500,222);
I've ignored foreign key constraints in query, as it is dummy data. Actual tables do have foreign key constraints.
Try this:
SELECT t1.Manager_id, sumOfSalaries - sumOfDeductions
FROM (
SELECT Manager_id, SUM(Emp_Salary) AS sumOfSalaries
FROM Salary
GROUP BY Manager_id) AS t1
INNER JOIN (
SELECT Manager_id, SUM(new_policy_deductions) AS sumOfDeductions
FROM Employee
GROUP BY Manager_id
) AS t2 ON t1.Manager_id = t2.Manager_id
Edit:
SELECT t1.Id, t1.Name,
COALESCE(sumOfSalaries, 0) - COALESCE(sumOfDeductions, 0) AS Net_Salary
FROM Manager AS t1
LEFT JOIN (
SELECT Manager_id, SUM(Emp_Salary) AS sumOfSalaries
FROM Salary
GROUP BY Manager_id
) AS t2 ON t1.Id = t2.Manager_id
INNER JOIN (
SELECT Manager_id, SUM(new_policy_deductions) AS sumOfDeductions
FROM Employee
GROUP BY Manager_id
) AS t3 ON t2.Manager_id = t3.Manager_id
select e.manager_id, (sum(e.Emp_Salary)-sum(s.new_policy_deductions))
FROM Salary as s
LEFT JOIN Employee as e
ON s.manager_id=e.manager_id
group by e.manager_id
would something like this be what you are looking for? might need some editing (typos are possible I dont have you db to check across)
In question to the comment this might be something you are also interested in:
select e.manager_id, (sum(e.Emp_Salary)-sum(s.new_policy_deductions))
FROM Salary as s
LEFT JOIN Employee as e
ON e.id=s.employee_id
group by e.manager_id
This is the best I can think of at current with the tables shown
I have a query that can help you: try this-
SELECT e.`Manager_id` as manager_id, (SELECT sum(s.`Emp_Salary`)
FROM salary as s
WHERE s.`Manager_id` = e.`Manager_id`) - sum(e.new_policy_deductions) as net_salary
FROM employee as e GROUP BY e.`Manager_id`
This is tested locally and output like you want. if some properties like-table name, field name change then please change. i think table names are are small later at my case.

Summing column values from 2 tables and getting their difference

I have 2 tables, ord_tbl and pay_tbl with these data:
ord_tbl
invoice | emp_id | prod_id | amount
123 | 101 | 1 | 1000
123 | 101 | 2 | 500
123 | 101 | 3 | 500
124 | 101 | 2 | 300
125 | 102 | 3 | 200
pay_tbl
invoice | new_invoice | amount
123 | 321 | 300
123 | 322 | 200
124 | 323 | 300
125 | 324 | 100
I would like the selection statement to give me this result
invoice | emp_id | orig_amt | balance | status
123 | 101 | 2000 | 1500 | unsettled
The invoice that has 0 balance will not be included anymore. This is what I tried so far...
;WITH CTE as
(SELECT ot.invoice, MAX(ot.emp_id) as emp_id, SUM(ot.amount) as origAmt FROM ord_tbl ot GROUP BY ot.invoice),
CTE2 as
(SELECT pt.invoice, SUM(pt.amountt) as payAmt FROM pay_tbl GROUP BY pt.invoice)
SELECT CTE.invoice, CTE.emp_id, CTE.origAmt, CTE.origAmt-CTE2.payAmt as bal, 'UNSETTLED' as status
FROM
CTE LEFT JOIN CTE2 ON CTE.invoice=CTE2.invoice
WHERE CTE.emp_id='101' AND CTE.origAmt-CTE2.payAmt>0 OR CTE2.patAmt IS NULL
This has been taught to me here and it works in sql server. What I need now is to have this run in ms access. I tried this code but ms access gives me an error saying "Invalid SQL statement; expected 'DELETE','INSERT', 'SELECT', or 'UPDATE'."
Can you help? Thanks.
MS ACCESS sql is poor and ACCESS doesn't know WITH instruction. I created tables (all fields int type). I rewrote query and this query works:
SELECT CTE.invoiceCTE,
CTE.emp_idCTE,
CTE.origAmtCTE,
CTE.origAmtCTE-CTE2.payAmtCTE2 as bal,
'UNSETTLED' as status
FROM
(SELECT invoice as invoiceCTE,
MAX(emp_id) as emp_idCTE,
SUM(amount) as origAmtCTE
FROM ord_tbl
GROUP BY invoice) as CTE
LEFT JOIN
( SELECT invoice as invoiceCTE2,
SUM(amount) as payAmtCTE2
FROM pay_tbl
GROUP BY invoice) as CTE2
ON CTE.invoiceCTE=CTE2.invoiceCTE2
WHERE CTE.emp_idCTE=101
AND (CTE.origAmtCTE-CTE2.payAmtCTE2>0 OR CTE2.payAmtCTE2 IS NULL)
I don't know about emp_id. If it is some kind of customer id you'd have only one per invoice_id and you'd need this SQL:
SELECT
ord_tbl.invoice,
First(ord_tbl.emp_id) AS ErsterWertvonemp_id,
Sum(ord_tbl.amount) AS origAmt,
Sum([ord_tbl].[amount])-Sum([pay_tbl].[amount]) AS bal,
"unsettled" AS status
FROM
ord_tbl LEFT JOIN pay_tbl
ON ord_tbl.invoice = pay_tbl.invoice
GROUP BY ord_tbl.invoice
HAVING (((Sum([ord_tbl].[amount])-Sum([pay_tbl].[amount]))>0));
If you want to select only the ones with emp_id=101 you'd need this:
SELECT
ord_tbl.invoice,
ord_tbl.emp_id,
Sum(ord_tbl.amount) AS origAmt,
Sum([ord_tbl].[amount])-Sum([pay_tbl].[amount]) AS bal,
"unsettled" AS status
FROM
ord_tbl LEFT JOIN pay_tbl
ON ord_tbl.invoice = pay_tbl.invoice
GROUP BY
ord_tbl.invoice,
ord_tbl.emp_id
HAVING (
((ord_tbl.emp_id)=101)
AND
((Sum([ord_tbl].[amount])-Sum([pay_tbl].[amount]))>0)
);

Tricky: Left join relation with row value

Suppose that I have the following two tables:
PRICE
price_id price room_id nr_of_people
1 80 1 1
2 75 1 2
3 90 2 2
4 120 3 3
ROOM
room_id room_no max_people
1 101 2
2 102 3
3 103 4
And, I need the following result:
QUERY_RESULT
price room_no nr_of_people
80 101 1
75 101 2
0 102 1
90 102 2
0 102 3
0 103 1
0 103 2
120 103 3
0 103 4
The tricky part in here is, I need to retrieve price for each people (for 2 people, for 3 people, for 4 people; that is incrementing upto the max_people defined in the room table), if there is no actual data available in the price table, it should fill in 0 by default. Above illustration should support my explanation.
I am not sure whether the table structure have any logical error.
Any thought/input/help regarding how to resolve the issue is much appreciated.
As abresas' answer and xQbert's comments suggest, you somehow need to create data in order to join it with your tables.
Like abresas' answer, I use an auxiliary table, but in my solution, this table needs to be filled with numbers 1 to N only, where N = biggest value that can ever appear on column max_people.
I created an auxiliary table called aux with a single column num. This query works for me:
SELECT IF(price.price IS NULL, 0, price.price) AS price, room.room_no, aux.num AS nr_of_people
FROM room
JOIN aux ON aux.num <= room.max_people
LEFT JOIN price ON ( price.room_id = room.room_id
AND aux.num = price.nr_of_people )
ORDER BY room.room_id, num
Unfortunately, mysql doesn't provide a native mechanism to generate a sequence of integers (see these questions), so physically creating the auxiliary table seems to be the most practical way to achieve what you need, though workarounds certainly exist if you really can't or don't want to create such table.
Just for the fun of it, the following would work without creating a new table (all inspired in the questions I linked to):
SELECT [...]
FROM room
JOIN (
SELECT 1 num
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
-- ...add as many entries as needed...
) aux ON aux.num <= room.max_people
LEFT JOIN [...]
As well as this:
SELECT [...]
FROM room
JOIN (
SELECT #row := #row +1 AS num
FROM any_table_that_is_big_enough, (SELECT #row :=0) r
) aux ON aux.num <= room.max_people
LEFT JOIN [...]
You should create a table where you have the nr_of_people from 1 up to max_people for each room, and the room_id. Then you can do an OUTER JOIN do get the information as you asked.
You can also create it as a temporary table constructing a query with the data you need in your code.
mysql> CREATE TEMPORARY TABLE nr ( nr_of_people int, room_id int );
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO nr VALUES ( 1, 1 ), ( 2, 1 ), ( 1, 2 ), ( 2, 2 ),
( 3, 2 ), ( 1, 3 ), ( 2, 3 ), ( 3, 3 ), ( 4, 3 );
Query OK, 9 rows affected (0.00 sec)
Records: 9 Duplicates: 0 Warnings: 0
mysql> SELECT price.price, room.room_no, nr.nr_of_people
FROM price
RIGHT OUTER JOIN nr ON price.room_id = nr.room_id AND price.nr_of_people = nr.nr_of_people
INNER JOIN room ON nr.room_id = room.room_id;
+-------+---------+--------------+
| price | room_no | nr_of_people |
+-------+---------+--------------+
| 80 | 101 | 1 |
| 75 | 101 | 2 |
| NULL | 102 | 1 |
| 90 | 102 | 2 |
| NULL | 102 | 3 |
| NULL | 103 | 1 |
| NULL | 103 | 2 |
| 120 | 103 | 3 |
| NULL | 103 | 4 |
+-------+---------+--------------+
9 rows in set (0.00 sec)

Getting unique values in sql server

I have a table say :
id| AccID | Subject | Date
1 | 103 | Open HOuse 1 | 11/24/2011 9:00:00 AM
2 | 103 | Open HOuse 2 | 11/25/2011 10:00:00 AM
3 | 72 | Open House 3 | 11/26/2011 1:10:28 AM
4 | 82 | OPen House 4 | 11/27/2011 5:00:29 PM
5 | 82 | OPen House 5 | 11/22/2011 5:00:29 PM
From the above table, i need all the unique values for the Accid. But say, if there are two or more columns with the same Accid, then i need the one which has the smaller date (among the columns which have the same Accid)
So, from the above table, the o/p should be :
1
3
5
Can any1 please help me in this ? Thanks
SELECT t1.*
FROM [MyTable] t1
INNER JOIN
(
SELECT AccID, MIN(Date) Date
FROM [MyTable]
GROUP BY AccID
) t2 ON t1.AccID = t2.AccID AND t1.Date = t2.Date
More than just the AccID but...
WITH SEL
AS
(
SELECT AccID, MIN(DATE)
FROM table
GROUP BY AccID
)
SELECT table.*
FROM table
JOIN SEL ON SEL.AccID = table.AccID