How to group and count in one row? - mysql

To simplify, I have tree tables: products, products-vs-orders, orders
products fields : 'ProductID', 'Name', 'isGratis', ...
products-vs-orders fields : 'ProductID', 'OrderID'
orders fields : 'OrderID', 'Title', ...
Actually, I have a query like this:
SELECT orders.OrderID, orders.Title, COUNT(`products`.`isGratis`) AS "Quantity", `products`.`isGratis`
FROM `orders`, `products-vs-orders`, `products`
WHERE `orders`.`OrderID` = `products-vs-orders`.`OrderID` AND `products-vs-orders`.`ProductID` = `products`.`ProductID`
GROUP BY `products`.`PackID`, `products`.`isGratis`
This query works and return this surch of result:
OrderID, Title, Quantity, isGratis
1 My Order 20 0
1 My Order 3 1
2 An other 8 0
2 An other 1 1
How can I retrieve the count of products 'gratis' and 'paid' in to separate cols ?
OrderID, Title, Qt Paid, Qt Gratis
1 My Order 20 3
2 An other 8 1

Try this:
SELECT
orders.OrderID,
orders.Title,
COUNT(orders.OrderId) - SUM(`products`.`isGratis`) AS "Qt Paid",
SUM(`products`.`isGratis`) AS "Qt Gratis"
WHERE `orders`.`OrderID` = `products-vs-orders`.`OrderID`
AND `products-vs-orders`.`ProductID` = `products`.`ProductID`
GROUP BY `products`.`PackID`

SUM(products.isGratis) depends on the fact that a boolean value is internally represented by the database as a single numeric bit, so false = 0 and true = 1.
This may not be the case in ALL DB implementations. Therefore, SUM over a boolean field may cause implementation-dependent behavior.
Converting the boolean into actual 0 and 1 values before summing should be more proper:
SELECT orders.OrderID, orders.Title,
SUM(CASE WHEN products.isGratis THEN 0 ELSE 1 END) AS "Qt Paid",
SUM(CASE WHEN products.isGratis THEN 1 ELSE 0 END) AS "Qt Gratis"
FROM orders INNER JOIN `products-vs-orders` ON (orders.OrderID = `products-vs-orders`.OrderID)
INNER JOIN products ON (`products-vs-orders`.ProductID = products.ProductID)
GROUP BY orders.OrderID, orders.Title

select orderid,title,sum(if(isgratis=0,quantity,0)) as paid,sum(if(isgratis=1,quantity,0)) as gratis from ...

Related

MYSQL sum/Count fails inside inner join group by

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;

Calculating acceptance_ratio with LEFT JOIN and SELF JOIN and aggregate function

Trying to calculate daily acceptance ratios from the 'connecting' table which has 4 fields with sample values:
date action sender_id recipient_id
'2017-01-05', 'request_link', 'frank', 'joe'
'2017-01-06', 'request_link', 'sally', 'ann'
'2017-01-07', 'request_link', 'bill', 'ted'
'2017-01-07', 'accept_link', 'joe', 'frank'
'2017-01-06', 'accept_link', 'ann', 'sally'
'2017-01-06', 'accept_link', 'ted', 'bill'
Because there are 0 accepts and 1 request on 01-05, its daily acceptance ratio should be 0/1 = 0. Similarly, the ratio for 01-06 should be 2/1, and it should be 1/1 for 01-07.
It is important however that each accept_link has a corresponding request_link where the sender_id of the request_link = the recipient_id of the accept_link (and vice versa). So here a self-join is required I believe to ensure that Joe accepts Frank's request, regardless of the date.
How can the below query be corrected so that the aggregation works correctly while retaining the required join conditions? Will the query calculate correctly as is if the two WHERE conditions are removed, or are they necessary?
SELECT f1.date,
SUM(CASE WHEN f2.action = 'accept_link' THEN 1 ELSE 0 END) /
SUM(CASE WHEN f2.action = 'request_link' THEN 1 ELSE 0 END) AS acceptance_ratio
FROM connecting f1
LEFT JOIN connecting f2
ON f1.sender_id = f2.recipient_id
LEFT JOIN connecting f2
ON f1.recipient_id = f2.sender_id
WHERE f1.action = 'request_link'
AND f2.action = 'accept_link'
GROUP BY f1.date
ORDER BY f1.date ASC
Expected output should look something like:
date acceptance_ratio
'2017-01-05' 0.0000
'2017-01-06' 2.0000
'2017-01-07' 1.0000
Thanks in advance.
Once again, I don't think you need to be using a self join here. Instead, just use conditional aggregation over the entire table, and count the number of requests and accepts which happened on each day:
SELECT t.date,
CASE WHEN t.num_requests = 0
THEN 'No requests available'
ELSE CAST(t.num_accepts / t.num_requests AS CHAR(50))
END AS acceptance_ratio
FROM
(
SELECT c1.date,
SUM(CASE WHEN c1.action = 'accept_link' AND c2.action IS NOT NULL
THEN 1 ELSE 0 END) AS num_accepts,
SUM(CASE WHEN c1.action = 'request_link' THEN 1 ELSE 0 END) AS num_requests
FROM connecting c1
LEFT JOIN connecting c2
ON c1.action = 'accept_link' AND
c2.action = 'request_link' AND
c1.sender_id = c2.recipient_id AND
c2.recipient_id = c1.sender_id
GROUP BY c1.date
) t
ORDER BY t.date
Note here that I use a CASE expression to handle divide by zero, which could occur should a certain day no requests. I also assume here that the same invitation will not be sent out more than once.

Join tables and get the same column to more columns based on the value

Lets say I join two tables and get a result like
id vendor vendor_id quantity
1 Sony 1 25
1 Apple 2 12
1 HTC 3 5
And I want the result to be like
id Quantity_Sony Quantity_Apple Quantity_HTC
1 25 12 5
How can I do that, I use Left joins to join the tables. I use mySql
SELECT ID,
MAX(CASE WHEN vendor = 'Sony' THEN Quantity END) Quantity_Sony,
MAX(CASE WHEN vendor = 'Apple' THEN Quantity END) Quantity_Apple,
MAX(CASE WHEN vendor = 'HTC' THEN Quantity END) Quantity_ATC
FROM
(
-- add your existing query here
) x
GROUP BY ID

mysql conditional field update

I have 3 columns in CATEGORY TABLE for storing pre-calculated counts of records for it in another table PRODUCTS.
CATEGORY(c_id,name,c30,c31,c32)
c30=count for New Products (value 30)
c31 count for used products (value 31)
c32 count for Damaged products (value 32)
PRODUCT(p_id,c_id,name,condition)
condition can be 30,31 or 32.
I am thinking to write a single UPDATE statement so, it will update respective category count.
Althogh below statement is syntactically wrong, but i am looking for similar type of solution.
select case product.condition
when 30 then update category set category.c30=category.c30+1 where category.c_id=product.category3
when 31 then update category set category.c31=category.c31+1 where category.c_id=product.category3
when 32 then update category set category.c32=category.c32+1 where category.c_id=product.category3
end case
from product
where product.c_id=12
Any suggestion!
You can do this:
UPDATE CATEGORY c
INNER JOIN
(
SELECT
c_id,
SUM(CASE WHEN `condition` = 30 THEN 1 ELSE 0 END) c30,
SUM(CASE WHEN `condition` = 31 THEN 1 ELSE 0 END) c31,
SUM(CASE WHEN `condition` = 32 THEN 1 ELSE 0 END) c32
FROM product
GROUP BY c_id
) p ON c.c_id = p.c_id
SET c.c30 = p.c30,
c.c31 = p.c31,
c.c32 = p.c32;
SQL Fiddle Demo
You can join both the tables and then update the value in same join query.

MySQL SELECT to Rank A Number out of a set of numbers

I have rows of data from a SELECT query with a few prices (say three for this example). One is our price, one is competitor1 price, one is competitor2 price. I want to add a column that spits out the rank of our price as compared to the other two prices; if our price is the lowest it would spit out the number 1 if the highest it would spit out the number it is out of.
Something like this:
Make | Model | OurPrice | Comp1Price | Comp2Price | Rank | OutOf
MFG1 MODEL1 350 100 500 2 3
MFG1 MODEL2 50 100 100 1 3
MFG2 MODEL1 100 NULL 50 2 2
MFG2 MODEL2 9999 500 NULL 2 2
-Sometimes the competitor price will be NULL as seen above, and I believe this is where my issue lies. I have tried a CASE and it works when only on one competitor but when I add a AND statement it spits out the ranks as all NULL. Is there a better way of doing this through a MySQL query?
SELECT
MT.MAKE as Make,
MT.MODEL as Model,
MT.PRICE as OurPrice,
CT1.PRICE as Comp1Price,
CT2.PRICE as Comp2Price,
CASE
WHEN MT.PRICE < CT1.PRICE AND MT.PRICE < CT2.PRICE
THEN 1 END AS Rank
(CT1.PRICE IS NOT NULL) + (CT2.PRICE IS NOT NULL) + 1 as OutOf
FROM mytable MT
LEFT JOIN competitor1table as CT1 ON CT1.MODEL = MT.MODEL
LEFT JOIN competitor2table as CT2 ON CT2.MODEL = MT.MODEL
ORDER BY CLASS
Not tested, but you can try:
SELECT
a.MAKE AS Make,
a.MODEL AS Model,
a.PRICE AS OurPrice
MAX(CASE WHEN a.compnum = 1 THEN pricelist END) AS Comp1Price,
MAX(CASE WHEN a.compnum = 2 THEN pricelist END) AS Comp2Price,
FIND_IN_SET(a.PRICE, GROUP_CONCAT(a.pricelist ORDER BY a.pricelist)) AS Rank,
COUNT(a.pricelist) AS OutOf
FROM
(
SELECT MAKE, MODEL, PRICE, PRICE AS pricelist, 0 AS compnum
FROM mytable
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT1.PRICE, 1
FROM mytable a
LEFT JOIN competitor1table CT1 ON a.MODEL = CT1.MODEL
UNION ALL
SELECT a.MAKE, a.MODEL, a.PRICE, CT2.PRICE, 2
FROM mytable a
LEFT JOIN competitor2table CT2 ON a.MODEL = CT2.MODEL
) a
GROUP BY
a.MAKE, a.MODEL
(CT1.PRICE IS NOT NULL AND CT1.PRICE < MT.PRICE) + (CT2.PRICE IS NOT NULL AND CT2.PRICE < MT.PRICE) + 1 as Rank