SQL SELECT Query - mysql

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.

Related

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
;

Combining MySQL querys

This SQL tells me how much when the maximum occurred in the last hour, and is easily modified to show the same for the minimum.
SELECT
mt.mB as Hr_mB_Max,
mt.UTC as Hr_mB_Max_when
FROM
thundersense mt
WHERE
mt.mB =(
SELECT
MAX(mB)
FROM
thundersense mt2
WHERE
mt2.UTC >(UNIX_TIMESTAMP() -3600))
ORDER BY
utc
DESC
LIMIT 1
How do I modify it so it returns both maximum & minimum and their respective times?
Yours Simon M.
Based on my understanding of your question, you are looking to create a 4 column and 1 row answer where it looks like:
+-------+-----------------+----------+-----------------+
| event | time_it_occured | event | time_it_occured |
+-------+-----------------+----------+-----------------+
| fun | 90000 | homework | 12000 |
+-------+-----------------+----------+-----------------+
Below is a similar situation/queries you can adapt for your situation.
So, given a table called 'people' that looks like:
+----+------+--------+
| ID | name | salary |
+----+------+--------+
| 1 | bob | 40000 |
| 2 | cat | 12000 |
| 3 | dude | 50000 |
+----+------+--------+
You can use this query:
SELECT * FROM
(SELECT name, salary FROM people WHERE salary = (SELECT MAX(salary) FROM people)) t JOIN
(SELECT name, salary FROM people WHERE salary = (SELECT MIN(salary) FROM people)) a;
to generate:
+------+--------+------+--------+
| name | salary | name | salary |
+------+--------+------+--------+
| bob | 40000 | cat | 12000 |
+------+--------+------+--------+
Some things to note:
you can change the WHERE clauses to be the ones you have mentioned in question (for MAX and MIN).
Please be careful with the above query, here I am using a cartesian join (cross join in MYSQL) in order to get the 4 columns. To be honest, it doesn't make sense for me to get back data in one row but you said that's what you're looking for.
Here is what I would work with instead, getting two tuples/rows back:
+----------+--------+
| name | salary |
+----------+--------+
| dude | 95000 |
| Cat | 12000 |
+----------+--------+
And to generate this, you would use:
(SELECT name, salary FROM instructor WHERE salary = (SELECT MAX(salary) FROM instructor))
UNION
(SELECT name, salary FROM instructor WHERE salary = (SELECT MIN(salary) FROM instructor));
Also: A JOIN without a ON clause is just a CROSS JOIN.
How to use mysql JOIN without ON condition?
One method uses a join:
SELECT mt.mB as Hr_mB_Max, mt.UTC as Hr_mB_Max_when
FROM thundersense mt JOIN
(SELECT MAX(mB) as max_mb, MIN(mb) as min_mb
FROM thundersense mt2
WHERE mt2.UTC >(UNIX_TIMESTAMP() - 3600)
) mm
ON mt.mB IN (mm.max_mb, mm.min_mb)
ORDER BY utc DESC;
My only concern is your limit 1. Presumably, the mBs should be unique. If not, there is a bit of a challenge. One possibility would be to use an auto-incremented id rather than mB.

Looping query over multiple rows

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;

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;

Join 2 tables, print data from 2nd table when joined rows occur

The case:
I have 2 tables, 'contracts' and 'members' tied with contract.id = members.cid.
Each contract has one main member and several secondary members (usually the children and spouse of the main member). The main member's details (name, address etc) are stored in table contracts whereas, extra members details are kept in table members. (bad logic, i know but this was a mess to begin with and i need time to redesign the db and website)
The desired output:
When I run a batch print of all contracts (lets say, every Friday) I need to also print a copy of the contract for each member, too but with the member's details on the contract instead of the main member.
The question:
How does this translate into a mysql query? Ok, its a left join, but how do I say "print data from table members instead of contracts for the joined rows"?
Main fields that occur in the 2 tables are name + surname, those should be enough for a draft query example.
Example tables and data:
contracts
-------------------------
id | name | surname |
-------------------------
1 | Tom | Jones |
2 | Jamie | Oliver |
members
--------------------------------
id | cid | name | surname |
--------------------------------
1 | 1 | Jack | Jones |
2 | 1 | Anne | Jones |
3 | 2 | Cathy | Wilson |
So the results I want shoudld be:
cid | name | surname |
--------------------------
1 | Tom | Jones |
1 | Jack | Jones |
1 | Anne | Jones |
2 | Jamie | Oliver |
2 | Cathy | Wilson |
If i write
SELECT c.name as name, c.surname as surname, m.name as name, m.surname as surname
FROM contracts c
join members m on c.id = m.cid
I simply end up with
name and name_1, surname and surname_1 but I want ALL names to fall under name and likewise for all other matching columns.
Hope this works :::
select c1.id, c1.name, c1.surname
from contracts c1
union
(Select m.id, m.name, m.surname
from
members m left join contracts c on (c.id = m.cid))
This is what I finally did and it worked (field names are different but the syntax is what matters):
SELECT u.id, u.onoma_u, u.name_u,
coalesce(u.programa, aa.programa) as programa,
coalesce(u.barcode, aa.barcode) as barcode,
coalesce(u.plan, aa.plan) as plan,
coalesce(u.im_exp, aa.im_exp) as im_exp,
coalesce(u.symb, aa.symb) as symb
FROM (SELECT a1.id, a1.onoma_u, a1.name_u, a1.programa, a1.barcode, a1.plan, a1.im_exp, a1.symb
FROM aitisi a1
UNION
SELECT a2.id, m.name, m.surname, NULL, NULL, NULL, NULL, NULL
FROM members m
JOIN aitisi a2 ON a2.id = m.symbid) u
JOIN aitisi aa ON aa.id = u.id;
I used aliases and NULLS as dummy fields to fill in the blanks.