MySQL - Arithmetic operations with relations - mysql

How do I get the amount of income each employee automatically, based on how the number of employees who participated in the construction project .
Already tried this , but the error . Subquery returned more than 1 row .
SELECT e.name,
( SELECT( p.costs / count(r.employee_id))
FROM relation_employee r GROUP BY r.project_id ) AS revenue
FROM project p
INNER JOIN relation_employee r ON p.id = r.project_id
INNER JOIN employee e ON r.employee_id = e.id
table employee
id INT
name VARCHAR
table project
id INT
name VARCHAR
costs INT
table relation_employee
employee_id INT
project_id INT

Instead of using a correlated subquery in the select part you could get the employee count per project as a derived table to use in the from part, which at least to me looks a bit cleaner. The query could look like this:
-- revenue per employee
SELECT e.name, sum(p.costs * 1.0 / emp_count) AS revenue
FROM project p
INNER JOIN relation_employee r ON p.id = r.project_id
INNER JOIN (SELECT project_id, count(employee_id) emp_count FROM relation_employee GROUP BY project_id) c ON c.project_id = p.id
INNER JOIN employee e ON r.employee_id = e.id
GROUP BY e.name;
-- revenue per employee and project
SELECT
e.name as employee_name,
p.name as project_name,
sum(p.costs / emp_count) AS revenue
FROM project p
INNER JOIN relation_employee r ON p.id = r.project_id
INNER JOIN (SELECT project_id, count(employee_id) emp_count FROM relation_employee GROUP BY project_id) c ON c.project_id = p.id
INNER JOIN employee e ON r.employee_id = e.id
GROUP BY e.name, p.name;
Sample SQL Fiddle

Related

using MYSQL Group By to get the most popular value

I am practicing MYSQL using https://www.w3schools.com/mysql/trymysql.asp?filename=trysql_func_mysql_concat which has a mock database for me to practice with an I am experimenting using the GROUP BY command I am attempting to group all employees up with all of their sales and determine, their name, their amount of sales and the product that they sold the most. I have managed to get their name and sales but not the product name. I know that extracting information with a group by is difficult and I have tried using a sub query. Is there a way to get the information.
My query is below.
SELECT
CONCAT_WS(' ',
Employees.FirstName,
Employees.LastName) AS 'Employee name',
COUNT(*) AS 'Num of sales'
FROM
Orders
INNER JOIN
Employees ON Orders.EmployeeID = Employees.EmployeeID
INNER JOIN
OrderDetails ON OrderDetails.OrderID = Orders.OrderID
INNER JOIN
Products ON Products.ProductID = OrderDetails.ProductID
GROUP BY Orders.EmployeeID
ORDER BY COUNT(*) DESC;
What this says is get orders, join employees based on orders employeeid, join the order details based on order id and join products information based on product id in the order details, then it groups them by the employee id and orders them by the number of sales an employee has made.
SELECT
concat_ws(' ',
Employees.FirstName,
Employees.LastName) as 'Employee name',
count(*) as 'Num of sales',
(
SELECT Products.ProductName
FROM Orders
INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID
INNER JOIN OrderDetails ON OrderDetails.OrderID = Orders.OrderID
INNER JOIN Products ON Products.ProductID = OrderDetails.ProductID
GROUP BY Orders.EmployeeID
ORDER BY count(Products.ProductName) desc
LIMIT 1
) as 'Product Name'
FROM Orders
INNER JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID
INNER JOIN OrderDetails ON OrderDetails.OrderID = Orders.OrderID
INNER JOIN Products ON Products.ProductID = OrderDetails.ProductID
GROUP BY Orders.EmployeeID
ORDER BY count(*) desc;
Above is my attempt at using a sub query for the solution.
It is quite ugly, as the w3school uses still mysql 5.7
On a personal note, you should install your own server grab somewhere a database and test it there, in mysql workbench you can have many query tabs in which you can test queries , till you het the "right" result.
SELECT
CONCAT_WS(' ',
Employees.FirstName,
Employees.LastName) AS 'Employee name',
COUNT(*) AS 'Num of sales',
tn.ProductName
FROM
Orders
INNER JOIN
Employees ON Orders.EmployeeID = Employees.EmployeeID
INNER JOIN
OrderDetails ON OrderDetails.OrderID = Orders.OrderID
INNER JOIN
Products ON Products.ProductID = OrderDetails.ProductID
INNEr JOIN
(SELECT EmployeeID, p.ProductName
FROM (SELECT IF (#Eid = EmployeeID ,#rn := #rn +1, #rn := 1) rn,ProductID, sumamount
, #Eid := EmployeeID as EmployeeID
FROM
(
SELECT
EmployeeID,ProductID, SUM(Quantity) sumamount
FROM Orders o INNER JOIN OrderDetails od ON od.OrderID = o.OrderID,(SELECT #Eid := 0, #rn := 0) t1
GROUP BY EmployeeID,ProductID
ORDER BY EmployeeID,sumamount DESC ) t2 ) t3
INNER JOIN Products p ON t3.ProductID = p.ProductID
WHERE rn= 1) tn
ON Orders.EmployeeID = tn.EmployeeID
GROUP BY Orders.EmployeeID
ORDER BY COUNT(*) DESC;
In your second query you are trying to get an employee's most often sold product. But there are two mistakes in that subquery:
The subquery is invalid. You group by employee, but select a product. Which product? An employee can sell many different products. MySQL should raise a syntax error here, as all other DBMS I know of do. But you are in cheat mode. MySQL allows incorrect aggregation queries and silently applies ANY_VALUE on all columns that cannot be selected otherwise. Thus you are selecting ANY_VALUE(Products.ProductName), i.e. a product arbitrarily chosen by the DBMS. To get out of cheat mode SET sql_mode = 'ONLY_FULL_GROUP_BY';.
Then, you don't relate the subquery to your main query. So when selecting the row for, say, employee #123, your subquery still selects data for all employees in order to pick one of their products. And as this is independent from the employee in the main query, it will probably pick the same product for every other employee you are selecting, too.
Here is what the query should look like instead:
SELECT
concat_ws(' ', e.FirstName, e.LastName) as "Employee name",
count(*) as "Num of sales",
(
SELECT p2.ProductName
FROM Orders o2
INNER JOIN OrderDetails od2 ON od2.OrderID = o2.OrderID
INNER JOIN Products p2 ON p2.ProductID = od2.ProductID
WHERE o2.EmployeeID = o.EmployeeID
GROUP BY p2.ProductID
ORDER BY count(*) DESC
LIMIT 1
) as "Product Name"
FROM Orders o
INNER JOIN Employees e ON o.EmployeeID = e.EmployeeID
INNER JOIN OrderDetails od ON od.OrderID = o.OrderID
GROUP BY o.EmployeeID
ORDER BY count(*) desc;
Demo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=f35e96764d454a4032d7778b550fc6b4
Disclaimer: When an employee sold more than one product most often (e.g. 500 x product A, 500 x product B, 200 x product C), then one of them (A or B in the example) gets picked arbitrarily for the employee.

MySQL - How to limit one result per ID?

I have the following query which creates a view table showing the highest salesperson in a store with few other details:
CREATE OR REPLACE VIEW sales_data AS
SELECT s.storename AS "Store",
e.employee_name AS "Employee",
e1.employee_name AS "Manager",
SUM(p.total_sale_value) AS "Sales Value"
FROM fss_Shop s
JOIN Employee e ON e.storeid = s.storeid
JOIN Payment p ON p.employee_number = e.employee_number
JOIN Employee e1 ON e1.employee_number = e.manager_number
WHERE s.storeid=1
GROUP BY e.employee_name
ORDER BY SUM(p.total_sale_value) DESC LIMIT 1;
The above query will only show the sales data for a single store and the reason being as I have stated WHERE s.storeid=1. I have 20 stores in my table. How can I change the above query so that it gives me sales data for 20 stores (so that's 20 rows).
CREATE OR REPLACE VIEW employee_sales_totals AS
SELECT
e.*,
SUM(p.total_sale_value) AS total_sale_value
FROM
Employee e
INNER JOIN
Payment p
ON p.employee_number = e.employee_number
GROUP BY
e.id -- This should be the Primary Key / Surrogate Key of the employee table
;
CREATE OR REPLACE VIEW shop_top_employee_by_sales_value AS
SELECT
s.storename AS "Store",
e.employee_name AS "Employee",
m.employee_name AS "Manager",
p.total_sale_value AS "Sales Value"
FROM
(
SELECT storeid, MAX(total_sale_value) AS total_sale_value
FROM employee_sales_totals
GROUP BY storeid
)
p
INNER JOIN
employee_sales_totals e
ON e.storeid = p.storeid
AND e.total_sale_value = p.total_sale_value
INNER JOIN
fss_Shop s
ON s.storeid = e.storeid
INNER JOIN
Employee m
ON m.employee_number = e.manager_number
;
As per the answer to your previous question, if multiple employees are tied for the same total sales amount in the same store, all such employees would be returned.
Consider using a ranking variable by SalesValue for each employee per store and then choose the RANK=1 in outer query:
SELECT main.Store, main.Employee, main.Manager, main.SalesValue
FROM
(SELECT agg.*,
#store:=agg.Store AS CURR_STORE,
#rank:=CASE WHEN #val > agg.SalesValue THEN #rank+1 ELSE 1 END AS RANK,
#val:=CASE WHEN #store <> agg.Store THEN #val ELSE agg.SalesValue END AS CURR_VAL
FROM
(SELECT s.storename AS "Store",
e.employee_name AS "Employee",
e1.employee_name AS "Manager",
SUM(p.total_sale_value) AS "SalesValue"
FROM fss_Shop s
INNER JOIN Employee e ON e.storeid = s.storeid
INNER JOIN Payment p ON p.employee_number = e.employee_number
INNER JOIN Employee e1 ON e1.employee_number = e.manager_number
GROUP BY s.storename,
e.employee_name,
e1.employee_name
) As agg
CROSS JOIN (SELECT #rank:= 0) AS r1
CROSS JOIN (SELECT #val:= 0) AS r2
CROSS JOIN (SELECT #store:= 0) AS r3
ORDER BY agg.Store, agg.SalesValue DESC
) As main
WHERE main.RANK = 1;
DEMO
Rextester (using random data with only one Sales table)
Alternatively, if variables cannot be used, consider creating two views where latter references the former: 1) initial aggregate query, 2) correlated subquery to retrieve top employee per store
CREATE OR REPLACE VIEW sales_data AS
SELECT s.storename AS "Store",
e.employee_name AS "Employee",
e1.employee_name AS "Manager",
SUM(p.total_sale_value) AS "SalesValue"
FROM fss_Shop s
INNER JOIN Employee e ON e.storeid = s.storeid
INNER JOIN Payment p ON p.employee_number = e.employee_number
INNER JOIN Employee e1 ON e1.employee_number = e.manager_number
GROUP BY s.storename,
e.employee_name,
e1.employee_name;
CREATE OR REPLACE VIEW top_sales_data AS
SELECT s.*
FROM sales_data s
WHERE (SELECT Count(*) FROM sales_data sub
WHERE sub.SalesValue > s.SalesValue
AND sub.Store = s.Store) = 0;

Comparing two SQL queries when using IN

I have two tables, for example the Employee and Project tables:
Employee (id, dept, joining_date)
Project (emp_id, project)
With Project having foreign key from Employee table.
I have to query on project and dept and return Employee in the order of their joining_date. Which query will work faster on big data set on the queries below?
select * from Employee where id in (select p.emp_id from Project p join Employee e on p.emp_id = e.id where p.project = 'project1' and e.dept = 'dept1') order by joining_date
select * from Employee where id in (select p.emp_id from Project p join Employee e on p.emp_id = e.id where p.project = 'project1' and e.dept = 'dept1') and dept = 'dept1' order by joining_date
Or is there any better and simpler way to do so?
The outer query using the IN() expression serves no purpose is entirely unnecessary. This will produce the output you need:
select e.*
from Project p
inner join Employee e on p.emp_id = e.id
where p.project = 'project1' and e.dept = 'dept1'
order by joining_date

SQL aggregation group by

I have an employee table and a leave_allocation table which has a one-to-many relationship, Each employee has a number of leave allocations over a period of time. I would like to get the LATEST allocation for each employee.
I tried the query but the date and the days values do not correlate to the same row
select e.employee_number, e.nme, MAX(l.date), l.days
from employee e, leave_allocation l
where l.employee_id = e.employee_id
group by e.employee_number, e.nme
How can I get the latest allocation per employee?
SELECT e.employee_number
,e.nme
,l.days
FROM employee e
, leave_allocation l
,(SELECT employee_id
,MAX(DATE) date
FROM leave_allocation
GROUP BY employee_id) m
WHERE l.employee_id = e.employee_id
AND l.employee_id = m.employee_id
AND l.date = m.date
If there can be multiple rows with same employee_number and date, then you need to sum.
SELECT e.employee_number
,e.nme
,sum(l.days)
FROM employee e
, leave_allocation l
,(SELECT employee_id
,MAX(DATE) date
FROM leave_allocation
GROUP BY employee_id) m
WHERE l.employee_id = e.employee_id
AND l.employee_id = m.employee_id
AND l.date = m.date
GROUP BY e.employee_number
,e.nme
Place the MAX() date in a subquery:
SELECT e.employee_number, e.nme, l.leavedate, la.days
FROM employee e
INNER JOIN
(
SELECT Max(date) leavedate, employee_id
FROM leave_allocation
GROUP BY employee_id
) l
ON e.employee_id = l.employee_id
INNER JOIN leave_allocation la
ON l.employee_id = la.employee_id
AND l.leavedate = la.date
I also switched the query to use ANSI join syntax instead of commas between the tables.
Try this one,
SELECT e.employee_number, e.nme, c.maxDate, l.days
FROM employee e
INNER JOIN leave_allocation l
ON l.employee_id = e.employee_id
INNER JOIN
(
select employee_id, MAX(date) maxDate
from leave_allocation
group by employee_id
) c ON c.employee_id = l.employee_ID AND
c.maxDate = l.date

How to create SQL subquery ON JOIN using multiple tables

I have the folowing tables.
ORDER
OrderNumber
CustomerNumber
EmployeeNumber
OrderDate
CUSTOMER
CustomerNumber
Name
Address
EMPLOYEE
EmployeeNumber
Name
Address
ORDERDETAIL
OrderNumber
Qty
Description
Price
Let say ORDERDETAIL table has 10 records
I would like to write a query that will return 10 records from ORDERDETAIL table to include Employee name, employee address, customer name, customer address and and order Date.
I know that I could write a query and use INNER JOIN to get the info from ORDER table, but how do you create the rest of query to get the info from the CUSTOMER and EMPLOYEE tables.
SELECT *
FROM OrderDetail D
INNER JOIN Order O
ON D.OrderNumber = O.OrderNumber;
Just add some more joins...
SELECT *
FROM OrderDetail D
JOIN Order USING (OrderNumber)
JOIN Customer USING (CustomerNumber)
JOIN Employee USING (EmployeeNumber)
You might want to re-order the JOINs in order to have the smallest tables first, as this could provide you with some performance boost (depending on your server's version, the most recent will optimize the join for you and might actually execute the joins in the "probably best" way).
Also, in the MySQL dialect at least, JOIN implicitly expands to INNER JOIN, and writing
A JOIN B USING (COL)
is equivalent to writing
A JOIN B ON (A.COL = B.COL)
SELECT *
FROM OrderDetail D
INNER JOIN Order O ON D.OrderNumber = O.OrderNumber
INNER JOIN Eployee E on O.EployeeNumber = E.EployeeNumber
INNER JOIN Customer C on O.CustomerNumber = C.CustomerNumber
if you have foreign key reference between
ORDER.EmployeeNumber and EMPLOYEE.EmployeeNumber
ORDER.CustomerNumber and CUSTOMER.CustomerNumber
then try this
SELECT
E.name AS employeeName,
E.Address AS employeeAddress,
C.name AS customerName,
C.Address AS customerAddress.
O.OrderDate
FROM OrderDetail D
INNER JOIN Order O ON D.OrderNumber = O.OrderNumber
INNER JOIN EMPLOYEE E ON E.EmployeeNumber = 0.EmployeeNumber
INNER JOIN CUSTOMER C ON C.CustomerNumber= 0.CustomerNumber
LIMIT 0,10