Looping query over multiple rows - mysql

I need to scan two tables (all rows where the name matches), and then I need to find the MAXIMUM difference in the sell and buy prices. (highest profit that could be made)
How can I find this result strictly using mysql? I want the query to perform the calculation.
I have two tables:
SELL:
Name | Sell_price | Seller
------------------------
Toyota | 12,000 | Mike
Toyota | 11,000 | Tony
Toyota | 9,000 | James
----------------------------
Mazda | 5,000 | Craig
Mazda | 4,000 | Roger
Mazda | 3,000 | Jones
Buy:
Name | Buying_Price | Buyer
------------------------
Toyota | 13,000 | Steve
Toyota | 12,000 | Andy
Toyota | 10,000 | Charles
----------------------------
Mazda | 5,000 | Thatguy
Mazda | 4,000 | Dog
Mazda | 3,500 | Cat
Result:
Name |Profit | Buyer | Seller
----------------------------------
Toyota | 2,000 | Charles | Mike
---------------------------------
Mazda | 1,500 | Craig | Cat

Join the tables on brand and then filter to only get the maximum sell and minimum buying prices:
select
sell.name,
sell.sell_price - buy.buying_price as profit,
buy.buyer,
sell.seller
from sell
join buy on buy.name = sell.name
where (sell.name, sell.sell_price) in
(
select name, max(sell_price)
from sell
group by name
)
and (buy.name, buy.buying_price) in
(
select name, min(buying_price)
from buy
group by name
);
Or do it vice versa, filter first and then join:
select
s.name,
s.sell_price - b.buying_price as profit,
b.buyer,
s.seller
from
(
select *
from sell
where (name, sell_price) in
(
select name, max(sell_price)
from sell
group by name
)
) s
join
(
select *
from buy
where (name, buying_price) in
(
select name, min(buying_price)
from buy
group by name
)
) b on b.name = s.name;

You need to compare every row against every other row, so OUTER JOIN is your friend. Then you can do a calculation in your SELECT and use AS to assign a temporary column name:
SELECT s.name, (s.sell_price - b.buy_price) AS profit, s.seller, b.buyer
FROM `sell` as s
OUTER JOIN `buy` as b ON s.name=b.name
To then sort it, you simply need to utilize ORDER BY:
SELECT s.name, (s.sell_price - b.buy_price) AS profit, s.seller, b.buyer
FROM `sell` as s
OUTER JOIN `buy` as b ON s.name=b.name
ORDER BY profit
You are probably right not to do this in code, depending on your context.

Something like (not tested):
Select s.sell_price - b.buyer_price from seller as s join buyer as b on s.name like b.name group by s.name, b.name;

A little bit modified query but I suppose that it's giving more precise result.
If You are making calculating of your proffit as a seller, then You have to select only rows where the buying price is greater then the selling price.
SELECT s.name, s.sell_price, b.buy_price, (b.buy_price - s.sell_price) AS proffit, s.seller, b.buyer
FROM `sell` as s
RIGHT JOIN `buy` as b ON s.name=b.name WHERE b.buy_price > s.sell_price ORDER BY proffit DESC
and the result will be:
name | proffit | seller | buyer
--------------------------------------------
Toyota | 4,000 | James | Steve
Mazda | 2,000 | Jones | Thatguy
Toyota | 1,000 | James | Charlie

According to your result table, u need to retrieve only records which have profits without any damages. You can try like this one.
SELECT s.Name, (s.Sell_Price-b.Buying_Price) AS Profit, b.Buyer, s.Seller
FROM Sell s RIGHT JOIN Buy b ON s.Name=b.Name
WHERE b.Buying_Price > s.Sell_Price;

Related

SQL insert query with some condition

I am using MySQL.
There are three tables presented: Patient, Occupies, Room and Privte_Patient. I need to identify the first available room and allocate the room to a newly admitted patient with PIN '314' (the patient is already added to a database). Note that the room should be either single or multiple occupancy depending whether the patient is private or not.
As a result, I want get the SQL query which will allocate the patient with PIN '314' to the room number 1. Because this patient is not a private patient and room number 1 is the first room with empty bed (size is 2 beds).
Any idea of how to identify this room? Can I do it using Conditional INSERT?
Table Patient
+-------+---------+
| PIN | name |
+-------+---------+
|314 | Lana |
|778899 | Michael |
|345566 | Jone |
+-------+---------+
Table Occupies
+--------+--------+
|patient | room |
+--------+--------+
|778899 | 1 |
|345566 | 4 |
+-------+---------+
Table Room
+--------+--------+
|number | size |
+--------+--------+
| 1 | 2 |
| 2 | 12 |
| 3 | 1 |
| 4 | 1 |
+-------+---------+
Private_Patient
+--------+--------+
|patient |consultant|
+--------+--------+
|345566 | 345566 |
+-------+---------+
Consider:
insert into occupies(patient, room)
select 314, r.number
from room r
left join (select room, count(*) size from occupies group by room) o
on o.room = r.number
where coalesce(o.size, 0) < r.size
order by r.number
limit 1
For each room, the query brings the number of occupants and uses that information to filter out rooms that are full already. The first room where at least in bed is available is selected.
It might be simpler to understand with a correlated subquery:
insert into occupies(patient, room)
select 314, r.number
from room r
where r.size > (
select count(*)
from occupies o
where o.room = r.number
)
order by r.number
limit 1
INSERT INTO Occupies(room, patient)
SELECT number, '314'
FROM Room r
INNER JOIN
(SELECT room, COUNT(patient) amountOfPatientsInRoom from Occupies GROUP BY
room) ON
o.room = r.number
WHERE r.size < o.amountOfPatientsInRoom
LIMIT 1

MySQL GROUP_CONCAT with SUM() and multiple JOINs inside subquery

I'm very average with MySQL, but usually I can write all the needed queries after reading documentation and searching for examples. Now, I'm in the situation where I spent 3 days re-searching and re-writing queries, but I can't get it to work the exact way I need. Here's the deal:
1st table (mpt_companies) contains companies:
| company_id | company_title |
------------------------------
| 1 | Company A |
| 2 | Company B |
2nd table (mpt_payment_methods) contains payment methods:
| payment_method_id | payment_method_title |
--------------------------------------------
| 1 | Cash |
| 2 | PayPal |
| 3 | Wire |
3rd table (mpt_payments) contains payments for each company:
| payment_id | company_id | payment_method_id | payment_amount |
----------------------------------------------------------------
| 1 | 1 | 1 | 10.00 |
| 2 | 2 | 3 | 15.00 |
| 3 | 1 | 1 | 20.00 |
| 4 | 1 | 2 | 10.00 |
I need to list each company along with many stats. One of stats is the sum of payments in each payment method. In other words, the result should be:
| company_id | company_title | payment_data |
--------------------------------------------------------
| 1 | Company A | Cash:30.00,PayPal:10.00 |
| 2 | Company B | Wire:15.00 |
Obviously, I need to:
Select all the companies;
Join payments for each company;
Join payment methods for each payment;
Calculate sum of payments in each method;
GROUP_CONCAT payment methods and sums;
Unfortunately, SUM() doesn't work with GROUP_CONCAT. Some solutions I found on this site suggest using CONCAT, but that doesn't produce the list I need. Other solutions suggest using CAST(), but maybe I do something wrong because it doesn't work too. This is the closest query I wrote, which returns each company, and unique list of payment methods used by each company, but doesn't return the sum of payments:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
Then I tried:
SELECT *,
(some other sub-queries I need...),
(SELECT GROUP_CONCAT(DISTINCT(mpt_payment_methods.payment_method_title), ':', CAST(SUM(mpt_payments.payment_amount) AS CHAR))
FROM mpt_payments
JOIN mpt_payment_methods
ON mpt_payments.payment_method_id=mpt_payment_methods.payment_method_id
WHERE mpt_payments.company_id=mpt_companies.company_id
ORDER BY mpt_payment_methods.payment_method_title) AS payment_data
FROM mpt_companies
...and many other variations, but all of them either returned query errors, either didn't return/format data I need.
The closest answer I could find was MySQL one to many relationship: GROUP_CONCAT or JOIN or both? but after spending 2 hours re-writing the provided query to work with my data, I couldn't do it.
Could anyone give me a suggestion, please?
You can do that by aggregating twice. First for the sum of payments per method and company and then to concatenate the sums for each company.
SELECT x.company_id,
x.company_title,
group_concat(payment_amount_and_method) payment_data
FROM (SELECT c.company_id,
c.company_title,
concat(pm.payment_method_title, ':', sum(p.payment_amount)) payment_amount_and_method
FROM mpt_companies c
INNER JOIN mpt_payments p
ON p.company_id = c.company_id
INNER JOIN mpt_payment_methods pm
ON pm.payment_method_id = p.payment_method_id
GROUP BY c.company_id,
c.company_title,
pm.payment_method_id,
pm.payment_method_title) x
GROUP BY x.company_id,
x.company_title;
db<>fiddle
Here you go
SELECT company_id,
company_title,
GROUP_CONCAT(
CONCAT(payment_method_title, ':', payment_amount)
) AS payment_data
FROM (
SELECT c.company_id, c.company_title, pm.payment_method_id, pm.payment_method_title, SUM(p.payment_amount) AS payment_amount
FROM mpt_payments p
JOIN mpt_companies c ON p.company_id = c.company_id
JOIN mpt_payment_methods pm ON pm.payment_method_id = p.payment_method_id
GROUP BY p.company_id, p.payment_method_id
) distinct_company_payments
GROUP BY distinct_company_payments.company_id
;

WHERE/GROUP By Condition - One Name but multiple values

I have the following table:
Name Product
Bob Car
Bob Apples
Bob Pears
Bob Car
John Apples
John Pears
Whoever has bought a Product Car, I want to keep separate from everyone else. So, I create a flag:
Name Product Flag
Bob Car 1
Bob Apples 0
Bob Pears 0
Bob Car 1
John Apples 0
John Pears 0
But the problem with my flag is that even if I do a where condition and say, show me the consumer WHERE flag !=1, it'll pick Bob. Which is incorrect as Bob owns a car.
I would still like to GROUP by Product.
How do I separate the above table into two groups?
Thanks!
Use below query :-
select name from table where flag!=1
and name not in (select name from table where flag = 1)
group by name
"show me the consumer WHERE flag !=1, it'll pick Bob" that is because you are asking for rows where flag != 1. Instead you'll need something a little more complicated, like:
SELECT DISTINCT Name
FROM tableTable
WHERE Name NOT IN (SELECT Name FROM theTable WHERE Product = 'Car')
alternatively, you can do a LEFT JOIN, which may or may not be faster depending on the amount of data you have and how its values are distributed.
SELECT DISTINCT a.Name
FROM theTable a
LEFT JOIN theTable b ON a.Name = b.Name AND b.Product = 'Car'
WHERE a.Product != 'Car' AND b.Product IS NULL
;
This gets all the rows with products other than cars, and then uses the LEFT JOIN in conjunction with the IS NULL condition to find which did not also have a 'Car' row.
I think you want your table's data displayed, just with "People who bought cars" partitioned (not grouped) separately somehow - this could be done with an ORDER BY OwnsACar clause, for example.
Step 1: Identify the people who have bought cars:
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
Step 2: Join on this data to generate a calculated "OwnsACar" column:
SELECT
yourTable.Name,
yourTable.Product,
ISNULL( carowners.Name ) AS OwnsACar
FROM
yourTable
LEFT OUTER JOIN
(
SELECT DISTINCT
Name
FROM
yourTable
WHERE
Product = 'Car'
) AS carowners ON carowners.Name = yourTable.Name
ORDER BY
OwnsACar ASC,
yourTable.Name ASC
You can use these two queries. The additional Flag column is not required.
-- do not have Car
SELECT *
FROM products
WHERE Name not in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
-- have Car
SELECT *
FROM products
WHERE Name in (SELECT DISTINCT Name
FROM products
WHERE Product='Car');
Illustration:
-- table
SELECT * FROM products;
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
| John | Apples |
| John | Pears |
+------+---------+
-- query for people that do not have Car
+------+---------+
| Name | Product |
+------+---------+
| John | Apples |
| John | Pears |
+------+---------+
-- query for people having 'Car'
+------+---------+
| Name | Product |
+------+---------+
| Bob | Car |
| Bob | Apples |
| Bob | Pears |
| Bob | Car |
+------+---------+
Try with :
SELECT `t`.`Name`, `t`.`Product`, SUM(`t`.`Flag`) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;
Although you can go without the flag column by going :
SELECT `t`.`Name`, `t`.`Product`, SUM(IF(`t`.`Product` = 'Car', 1, 0)) as hasCar
FROM your_table t
GROUP BY `t`.`Name`
HAVING `t`.`hasCar` = 0;

SQL SELECT Query

Suppose I have a SQL table "Company" with three columns: "department_id", "employee", "job". Something like this:
DEPARTAMENT_ID | EMPLOYEE | JOB
--------------------------------------
1 | Mark | President
1 | Robert | Marketing Manager
1 | Rose | Administration Assitant
2 | Anna | Programmer
2 | Michael | Programmer
2 | Celia | Sales Manager
3 | Jhon | Sales Manager
3 | Donna | Programmer
3 | David | Marketing Manager
I would like to write a query that returns the departments id where at least 50% of their jobs are the same.
Result i need in my example would be just:
DEPARTAMENT_ID |
--------------------------------------
2 |
How do I write this SQL query? I think i tried all kind of stuff but i dont get it :(.
This is a bit tricky. You need to compare the total number of people on a job in a department to the total number. So, one method uses two aggregations:
select department_id
from (select department_id, count(*) as numemp
from t
group by department_id
) d join
(select department_id, max(numemp) as numemp
from (select department_id, job, count(*) as numemp
from t
group by department_id, job
) d
group by department_id
) dj
on d.numemp <= 2 * dj.numemp;
You might get duplicates if you have one department that is exactly split between two jobs. In that case, use select distinct.

SQL sorting does not follow group by statement, always uses primary key

I have a SQL database with a table called staff, having following columns:
workerID (Prim.key), name, department, salary
I am supposed to find the workers with the highest salary per department and used the following statement:
select staff.workerID, staff.name, staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
I get one worker shown from each department, but they are NOT the workers with the highest salary, BUT the biggest salary value is shown, even though the worker does not get that salary.
The person shown is the worker with the "lowest" workerID per department.
So, there is some sorting going on using the primary key, even though it is not mentioned in the group by statement.
Can someone explain, what is going on and maybe how to sort correctly.
Explanation for what is going on:
You are performing a GROUP BY on staff.department, however your SELECT list contains 2 non-grouping columns staff.workerID, staff.name. In standard sql this is a syntax error, however MySql allows it so the query writers have to make sure that they handle such situations themselves.
Reference: http://dev.mysql.com/doc/refman/5.0/en/group-by-handling.html
In standard SQL, a query that includes a GROUP BY clause cannot refer to nonaggregated columns in the select list that are not named in the GROUP BY clause.
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause.
The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate.
Starting with MySQL 5.1 the non-standard feature can be disabled by setting the ONLY_FULL_GROUP_BY flag in sql_mode: http://dev.mysql.com/doc/refman/5.6/en/sql-mode.html#sqlmode_only_full_group_by
How to fix:
select staff.workerID, staff.name, staff.department, staff.salary
from staff
join (
select staff.department, max(staff.salary) AS biggest
from staff
group by staff.department
) t
on t.department = staff.department and t.biggest = staff.salary
In the inner query, fetch department and its highest salary using GROUP BY. Then in the outer query join those results with the main table which would give you the desired results.
This is the usual case group by with a aggregate function does not guarantee proper row corresponding to the aggregate function. Now there are many ways to do it and the usual practice is a sub-query and join. But if the table is big then performance wise it kills, so the other approach is to use left join
So lets say we have the table
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 1 | abc | computer | 400 |
| 2 | cdf | electronics | 200 |
| 3 | gfd | computer | 400 |
| 4 | wer | physics | 300 |
| 5 | hgt | computer | 700 |
| 6 | juy | electronics | 100 |
| 7 | wer | physics | 400 |
| 8 | qwe | computer | 200 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
| 11 | qsq | computer | 600 |
| 12 | asd | electronics | 300 |
+----------+------+-------------+--------+
SO we can get the data as
select st.* from staff st
left join staff st1 on st1.department = st.department
and st.salary < st1.salary
where
st1.workerid is null
The above will give you as
+----------+------+-------------+--------+
| workerid | name | department | salary |
+----------+------+-------------+--------+
| 5 | hgt | computer | 700 |
| 9 | iop | electronics | 800 |
| 10 | kli | physics | 800 |
+----------+------+-------------+--------+
My favorite solution to this problem uses LEFT JOIN:
SELECT m.workerID, m.name, m.department, m.salary
FROM staff m # 'm' from 'maximum'
LEFT JOIN staff o # 'o' from 'other'
ON m.department = o.department # match rows by department
AND m.salary < o.salary # match each row in `m` with the rows from `o` having bigger salary
WHERE o.salary IS NULL # no bigger salary exists in `o`, i.e. `m`.`salary` is the maximum of its dept.
;
This query selects all the workers that have the biggest salary from their department; i.e. if two or more workers have the same salary and it is the bigger in their department then all these workers are selected.
Try this:
SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
INNER JOIN (SELECT s.department, MAX(s.salary) AS biggest
FROM staff s GROUP BY s.department
) AS B ON s.department = B.department AND s.salary = B.biggest;
OR
SELECT s.workerID, s.name, s.department, s.salary
FROM (SELECT s.workerID, s.name, s.department, s.salary
FROM staff s
ORDER BY s.department, s.salary DESC
) AS s
GROUP BY s.department;