MYSQL sum/Count fails inside inner join group by - mysql

I have a table that contains sales records:
Sale ID
EmployeeId(FK)
Employee 2
...
1
101
Null
...
2
102
Null
...
3
300
Bob
...
...
...
...
...
I have another table that contains employee records:
EmployeeId
EmployeeName
...
101
Amanda
...
102
Bob
...
...
...
...
300
cicilia
...
...
...
...
I'm trying to do a select where i get all sales and group them by employees for performance analysis. So far i managed to get right the employees and their sale counts and totals. The problem is the third column in my sales record is called employee2, it can be null as not every sale has another employee assisting. It is not indexed to the employee table unlike the second column.
So for example in my query below, the expected results should be Amanda has 1 salecount, 0 helpCount, meanwhile Boss has 1 salecount, 1 helpCount, and cicillia has 1 salecount, 0 helpcount. But im getting 1 salecount for all which is correct, but 0 helpcounts for bob. This is my query so far:
select employee.employee_id,
employee.employee_Name,
count(sale.sale_id) as saleCount,
sum(sale.grand_total) as totalSalesRevenue,
sum(CASE WHEN sale.employee2 = employee.employee_Name THEN 1
ELSE 0 END) as helperEmpCount
from employee
inner join sale on employee.employee_id = sale.employee_id
group by employee.employee_id;
The result set, where helpCounts should not be 0.
Im running a mysql 8.0 database.
Edit: I have found a workaround, albeit a very unefficient one. If i change my count to a nested select it works, but this decreases performance by quite a bit considering i have a lot of employees.
New query:
select employee.employee_id,
employee.employee_Name,
count(sale.sale_id) as saleCount,
sum(sale.grand_total) as totalSalesRevenue,
(select count(sale.employee2) from sale where sale.employee2= employee_Name) as helperEmpCount
from employee
inner join sale on employee.employee_id = sale.employee_id
group by employee.employee_id;
Any idea how to make it more efficient?

You can join the tables on either of the 2 conditions and use conditional aggregation:
SELECT e.employee_id,
e.employee_Name,
SUM(s.employee_id = e.employee_id) AS saleCount,
SUM(CASE WHEN s.employee_id = e.employee_id THEN s.grand_total ELSE 0 END) AS totalSalesRevenue,
SUM(s.employee2 = e.employee_Name) AS helperEmpCount
FROM employee e LEFT JOIN sale s
ON s.employee_id = e.employee_id OR s.employee2 = e.employee_Name
GROUP BY e.employee_id;

Related

Getting percentage of total in SQL with two joins

So I'm trying to do something that I think should be fairly simple with SQL. But I'm having a hard time figuring it out. Here is the format of my data:
One table with user information, let's call it User:
ID name_user Drive_Type
1 Tim Stick shift
2 Jim Automatic
3 Bob Automatic
4 Lisa Stick shift
Then I have one table used for the join, let's call it Join_bridge:
user_ID car_has_ID
1 12
2 13
3 14
4 14
And one table with car information, let's call it Car:
car_ID name
12 Honda
13 Toyota
14 Ford
Then what I want is something that looks like this with the total number of Ford's that are stick shift and the percentage
name Total percentage
Ford 1 25%
I have tried the following, which gets the total right, but not the percentage:
select Drive_Type,
name,
count(Drive_Type) as Total,
(count(Drive_Type) / (select count(*)
from User
join Join_bridge
on User.ID = user_ID
join Car
on Car.car_ID = Join_bridge.car_has_ID
) * 100.0 as Percent
from User
join Join_bridge
on User.ID = Join_bridge.user_ID
join Car
on Car.car_ID = Join_bridge.car_has_ID
where name = 'Ford' and Drive_Type = "Automatic"
;
What am I missing? Thanks.
See this SQL Fiddle with the query - the trick is to SUM over CASE that returns 1 for rows you look for and 0 for the rest in order to calculate "Total" at the same time you can also count all rows to calculate percentage.
Here's the SQL query:
SELECT
'Ford' name,
SUM(a.ford_with_stack_flag) Total,
100.0 * SUM(a.ford_with_stack_flag) / COUNT(*) percentage
FROM (
SELECT
Car.name,
(CASE WHEN User.Drive_Type = 'Stick Shift' and Car.name = 'Ford' THEN 1 ELSE 0 END) ford_with_stack_flag
FROM User
JOIN Join_bridge on User.ID = Join_bridge.user_ID
JOIN Car ON Car.car_ID = Join_bridge.car_has_ID
) a
Compute percent and join to Car. Window functions are supported in MySql 8.0
select c.car_ID, c.name, p.cnt, p.Percent
from car c
join (
select car_has_ID, u.Drive_Type,
count(*) cnt,
count(*) / count(count(*)) over() Percent
from Join_bridge b
join user u on u.ID = b.user_ID
group by b.car_has_ID, u.Drive_Type
) p on p.car_has_ID = c.car_ID
where c.name = 'Ford' and p.Drive_Type='Stick shift';
db<>fiddle

SQL: how to use two conditions whith dependences?

I have database where is unique customer ID.
I what to know what products they have bought before specified product. I have a list when specified ID has bought bananas I want to search all products and dates before that.
Example I have data:
CustomerID Product Date
123 banana 2015-03-15
111 banana 2014-07-09
321 banana 2013-04-03
How I can write SQL command search all products what Customer have bought before that date?
Example
CustomerID Product Date
123 Apple 2014-05-07
123 Kiwi 2014-05-06
123 Pen 2012-12-12
111 Pen 2014-07-07
111 Milk 2010-01-30
321 Milk 2012-02-12
This should work for you. The table name is contrived, so you will need to replace it.
SELECT CustomerID, Product, Date
FROM ProductTable p1
WHERE Date < (
SELECT MAX(Date)
FROM ProductTable p2
WHERE p2.CustomerID = p1.CustomerID AND p2.Product = 'banana'
)
ORDER BY CustomerID, Date;
#Peter Abolins's answer is correct, but does not handle the case when the customer has never bought a banana yet.
To handle this case, the request would become:
SELECT CustomerID, Product, Date
FROM ProductTable p1
WHERE Date < (
SELECT IFNULL (
(SELECT MAX(Date)
FROM ProductTable p2
WHERE p2.CustomerID = p1.CustomerID AND p2.Product = 'banana'),
'9999-12-31'
)
)
ORDER BY CustomerID, Date;
PS: I know that this should be a comment, but I cannot comment with this account yet.
I'm not sure if this is what you want but give it a try and look at the results:
SELECT * FROM tableA AS a
JOIN tableA AS b
ON a.CustomerID = b.CustomerID AND a.Date > b.Date
ORDER BY a.CustomerID, a.Product, a.Date
You have two tables, the first table with a list of customer ID, and dates for a specified product, and the second table with the history of product sold to that customer.
We will call them respectively LIST and HIST, you can get your desired output with:
SELECT p1.CustomerID, p2.Product, p2.Date
FROM LIST p1
left join HIST p2 on p2.CustomerID = p1.CustomerID AND P1.Date > P2.Date
ORDER BY p1.CustomerID, p2.Date;

Getting max date from a joined table with group by

I've got 3 tables - entryrecord, employee and employee_entryrecord (linking table).
The query I'd like is for it to return the most recent (max time) inout record for each employee.
employee
id employee
1 John
2 Tom
entryrecord
id created_date inout
1 2016-07-22 16:01:38 1
2 2016-07-22 16:03:22 1
3 2016-07-22 16:05:22 2
4 2016-07-22 16:07:22 2
5 2016-07-22 16:09:22 1
I'd like the follow output
created_date employee inout entryrecordid
2016-07-22 16:09:22 John 1 5
2016-07-22 16:05:22 Tom 2 3
However, in the sqlfiddle below you can see it does not return the correct inout and entryrecordid values.
I've created a sqlfiddle to view what I've done.
SQL Fiddle
Any help would be great.
Thanks.
Please give it a try:
SELECT
finalALias.created_date,
E.employee,
finalALias.inout,
finalALias.id AS entryrecordid
FROM employee E
INNER JOIN
(
SELECT
*
FROM entryrecord entryR
INNER JOIN
(
SELECT
EER.employeeid,
MAX(created_date) max_time
FROM entryrecord ER
INNER JOIN employee_entryrecord EER ON ER.id = EER.entryrecordid
GROUP BY EER.employeeid
) t
ON t.max_time=entryR.created_date
) AS finalALias
ON E.id = finalALias.employeeid
ORDER BY finalALias.created_date DESC;
WORKING DEMO
Just a gentle reminder:
E -> employee
ER -> entryrecord
ERR -> employee_entryrecord
The problem is that grouping happens before ordering. You will have to do a sub query. You always want to try and keep your sub queries to a minimum as they put a heavy toll on the SQL server.
I changed your LEFT JOINS to INNER JOINS because it looked like you wanted to only get employees that were in the other tables.
SELECT
entryrecord.created_date,
employee.employee,
entryrecord.inout,
entryrecord.id
FROM
entryrecord
INNER JOIN
employee_entryrecord ON entryrecord.id = employee_entryrecord.entryrecordid
INNER JOIN
employee ON employee_entryrecord.employeeid = employee.id
WHERE
entryrecord.inout in (1,2)
AND entryrecord.id = (
SELECT er2.id
FROM employee_entryrecord eer2, entryrecord er2
WHERE eer2.employeeid = employee.id
AND er2.id = eer2.entryrecordid
ORDER BY er2.created_date DESC LIMIT 1
)

MySQL Retrieve Lowest Value in Multi-table Query

My goal is to retrieve the recorded purchase price for an item on an accepted purchase order.
Purchase_Orders table contains metadata for the order, such as the order number and its status (e.g., 1 for accepted, 0 for declined).
Purchase_Ord_Contents table contains contents records, which are linked via foreign key to the parent purchase order on a shared index order_number)
For example: I have two orders in my database, one has been accepted and the other has been declined. The data is represented as follows:
=========================================
PURCHASE_ORDERS TABLE
=========================================
id | order_number | order_status
-----------------------------------------
1 PO_100 0
2 PO_101 1
3 PO_102 1
===================================================
PURCHASE_ORD_CONTENTS TABLE
===================================================
id | order_number | purchase_price | sku
---------------------------------------------------
1 PO_100 1.50 APPLE
2 PO_100 1.50 ORANGE
3 PO_101 2.00 APPLE
4 PO_101 2.00 ORANGE
5 PO_102 1.75 BANANA
The query should return rows 3, 4 and 5, since PO_101 was accepted, whereas PO_100 was declined and row 5 is not only the only record for the given SKU, it was also on an accepted order. I've tried a few different approaches, but I always seem to end up either leaving out parts that were on an unaccepted Purchase Order, or retrieving the wrong order_number for the lowest purchase_price.
Here is what I have thus far (not working properly)
SELECT a.*
FROM purchase_ord_contents AS a
JOIN (SELECT sku,
MIN(purchase_price) AS min_price
FROM purchase_ord_contents
GROUP BY sku) AS b
ON ( a.sku = b.sku
AND a.purchase_price = b.min_price )
WHERE a.order_number
IN (
SELECT order_number
FROM purchase_orders
WHERE order_status != 0
)
This query successfully returns the records from the purchase_ord_contents table, however it omits records of the lowest purchase_price that were on a Purchase Order with an order_status of 0.
Any guidance would be greatly appreciated, I am not very well versed in "advanced" SQL queries as you have probably determined by now. Thank you for your time and please do not hesitate to ask if I should provide any further information.
This could be what you are looking for:
SELECT sku, purchase_price, order_number
FROM (
SELECT MIN(purchase_price) AS purchase_price, sku
FROM purchase_ord_contents
JOIN purchase_orders USING (order_number)
WHERE purchase_orders.order_status = 1
GROUP BY sku
) AS min_sku_price -- this is the lowest sale price for each SKU
JOIN purchase_ord_contents USING (sku, purchase_price) -- gets all orders having sold a SKU at its lowest price
JOIN purchase_orders USING (order_number)
WHERE purchase_orders.order_status = 1
Notice this will return several rows for one given SKU if the lowest price for this SKU was offered in several orders.
If I understand correctly I think you want this:
SELECT po.order_number, poc.sku, min(poc.purchase_price)
FROM purchase_orders AS po
JOIN purchase_ord_contents AS poc ON poc.order_number = po.order_number
WHERE po.order_status != 0
GROUP by po.order_number, poc.sku
order by po.order_number, poc.sku

How do I fetch count of two columns for a given criteria grouped by another column?

DataBase: SQL Fiddle
Query needed: To return the number of women and men of age 25-35 years for each Insurance Company.
My Progress:
CREATE VIEW MenInAge AS
SELECT p.pname,p.pid,p.cid
FROM Patient p
WHERE p.gender = 'm' and p.age between 25 and 35;
CREATE VIEW WomenInAge AS
SELECT p.pname,p.pid,p.cid
FROM Patient p
WHERE p.gender = 'f' and p.age between 25 and 35;
CREATE VIEW MenInAgeCount AS
SELECT m.cid, COUNT(m.pid) as c
FROM MenInAge m
GROUP BY m.cid;
CREATE VIEW WomenInAgeCount AS
SELECT w.cid, COUNT(w.pid) as c
FROM WomenInAge w
GROUP BY w.cid;
How do I show for every InsuranceCompany.cid the WomenInAgeCount.c and the MenInAgeCount.c columns?
Explanation:
You have to join the tables InsuranceCompanies and Patient using the LEFT OUTER JOIN by joining the records on cid column in both tables and also apply the filter to select only patients between age 25 and 35 (including those boundary values). The CASE statement simply checks whether the patient is male or female and computes two different columns by assigning values of 1 if the values match and 0 if the values don't match. Finally you have to group the result by cname to fetch the count by insurance company name.
Explanation about CASE:
In the CASE expression, the query states WHEN gender field value is f assign the column female with the value 1. The value 1 is hard coded because it means the query found 1 row matching the gender='f' record and this also represent 1 person. You can also state ELSE 0 but it is implicit so not necessary to specify that. This CASE expression evaluates for every record in the query result. Finanlly, you will get all the rows with female column containing either 1 or 0. When you sum this column female, you will get the total number of females, the same logic goes for male column.
With COALESCE:
COALESCE here replaces any NULL values with the given value in the second parameter (here in this case zero).
Click here to view the demo in SQL Fiddle.
Script:
SELECT ic.cname
, COALESCE(SUM(CASE WHEN gender = 'f' THEN 1 END), 0) female
, COALESCE(SUM(CASE WHEN gender = 'm' THEN 1 END), 0) male
FROM InsuranceCompanies ic
LEFT OUTER JOIN Patient p
ON p.cid = ic.cid
AND age BETWEEN 25 AND 35
GROUP BY ic.cname;
Output:
CNAME FEMALE MALE
---------- ------ ----
Clalit Inc 0 2
Harel Inc 2 0
Without COALESCE:
Click here to view the demo in SQL Fiddle
Script:
SELECT ic.cname
, SUM(CASE WHEN gender = 'f' THEN 1 END) female
, SUM(CASE WHEN gender = 'm' THEN 1 END) male
FROM InsuranceCompanies ic
LEFT OUTER JOIN Patient p
ON p.cid = ic.cid
AND age BETWEEN 25 AND 35
GROUP BY ic.cname;
Output:
CNAME FEMALE MALE
---------- ------ ----
Clalit Inc NULL 2
Harel Inc 2 NULL
How about a JOIN?
SELECT I.cname, ISNULL(W.c,0) AS WomenCount, ISNULL(M.c,0) as MenCount
FROM InsuranceCompanies AS I
LEFT JOIN MenInAgeCount AS M ON M.cid = I.cid
LEFT JOIN WomenInAgeCount AS W ON W.cid = I.cid
LEFT JOIN here in case the Men or Women view don't contain an entry for each row in the InsuranceCompanies table. The ISNULL is for SQL Server, but you can modify for MySQL, Oracle as needed.
this should give you some help -
select count(pid) as numPatients, cid, gender
from patient
group by cid, gender