How to display mySQL rows even if there are not SUM results? - mysql

I am trying to display a report from mySQL. Here is my current query:
SELECT *,
Sum(CASE
WHEN alerts_data_status = 'goal' THEN 1
ELSE 0
END) AS goal,
Sum(CASE
WHEN alerts_data_status = 'delivered' THEN 1
ELSE 0
END) AS delivered,
Sum(CASE
WHEN alerts_data_status = 'closed' THEN 1
ELSE 0
END) AS closed
FROM alerts_data
WHERE alerts_data.company_id = 1
GROUP BY alerts_data.alerts_data_id
the thing is that if a alerts_data.id has 0 goal, 0 delivered, 0 closed, it won't be shown in the results. The query shows only the alerts_data.id with at least 1 goal or 1 delivered or 1 closed.
How can I achieve this?
Example output
company ---- id --- goal --- delivered --- closed
1 ---- 32 --- 1 ------ 4 ----- 10
1 ---- 11 --- 0 ------ 1 ----- 1
Thank you

I think the issue that you are having is that there are no rows in the table for the company. Use an aggregation query with no GROUP BY:
SELECT 1 as company_id,
COALESCE(SUM(alerts_data_status = 'goal'), 0) AS goal,
COALESCE(SUM(alerts_data_status = 'delivered'), 0) AS delivered,
COALESCE(SUM(alerts_data_status = 'closed'), 0) AS closed
FROM alerts_data ad
WHERE ad.company_id = 1;
This no GROUP BY, this is guaranteed to return one row -- even if the WHERE clause filters out all rows. A GROUP BY returns one row per group, so if all rows are filtered out, then there are no groups and no rows in the result set.
If you wanted to support multiple company ids, you could use a LEFT JOIN:
SELECT company_id,
COALESCE(SUM(alerts_data_status = 'goal'), 0) AS goal,
COALESCE(SUM(alerts_data_status = 'delivered'), 0) AS delivered,
COALESCE(SUM(alerts_data_status = 'closed'), 0) AS closed
FROM (SELECT 1 as company_id UNION ALL
SELECT 2 as company_id
) c LEFT JOIN
alerts_data ad
USING (company_id)
GROUP BY company_id;
The LEFT JOIN guarantees that there are rows for each company, so each will be in the result set.
You can also phrase this as:
SELECT 1 as company_id,
COALESCE(SUM(alerts_data_status = 'goal'), 0) AS goal,
COALESCE(SUM(alerts_data_status = 'delivered'), 0) AS delivered,
COALESCE(SUM(alerts_data_status = 'closed'), 0) AS closed
FROM alerts_data ad
WHERE ad.company_id = 1;
This no GROUP BY, this is guaranteed to return one row -- even if the WHERE clause filters out all rows. A GROUP BY returns one row per group, so if all rows are filtered out, then there are no groups and no rows in the result set.
If you wanted to support multiple company ids, you could use a LEFT JOIN:
SELECT c.company_id,
COALESCE(SUM(ad.alerts_data_status = 'goal'), 0) AS goal,
COALESCE(SUM(ad.alerts_data_status = 'delivered'), 0) AS delivered,
COALESCE(SUM(ad.alerts_data_status = 'closed'), 0) AS closed
FROM companies c LEFT JOIN
alerts_data ad
on c.company_id = ad.company_id
WHERE c.company_id IN (1) -- or a longer list
GROUP BY c.company_id;

You need a LEFT JOIN of alerts_list to alerts_data:
SELECT t.alerts_id,
SUM(a.alerts_data_status = 'goal') AS goal,
SUM(a.alerts_data_status = 'delivered') AS delivered,
SUM(a.alerts_data_status = 'closed') AS closed
FROM alerts_list AS t LEFT JOIN alerts_data AS a
ON a.alerts_data_id = t.alerts_id AND a.company_id = t.company_id
WHERE t.company_id = 1
GROUP BY t.alerts_data_id

Related

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.

MySQL - loop through rows

I have the following code
select count(*)
from (select Annotations.user_id
from Annotations, Users
where Users.gender = 'Female'
and Users.user_id = Annotations.user_id
and image_id = 1
group by Annotations.user_id
having sum(case when stem = 'taxi' then 1 else 0 end) > 0 and
sum(case when stem = 'zebra crossing' then 1 else 0 end) > 0
) Annotations
It produces a count of how many females who have given the stem 'taxi' and 'zebra crossing' for image 1.
Sample data
user id, image id, stem
1 1 image
1 1 taxi
1 1 zebra crossing
2 1 person
2 1 zebra crossing
2 1 taxi
3 1 person
3 1 zebra crossing
Expected result (or similar)
stem1, stem2, count
taxi , zebra crossing 2
person, zebra crossing 2
However, as there are over 2000 stems, I cannot specify them all.
How would I go around looping through the stem rows with the image_id = 1 and gender = female as opposed to specifying the stem string?
Thank you
As per my understanding, you need to fetch female users that have 2 or more stems
Update: It seems you need to display the user's that have a stem that is used by another user too, I have updated the query for the same
SELECT
distinct a.user_id,
group_concat(DISTINCT a.stem ORDER BY a.stem)
FROM
Annotations a
JOIN Users u ON ( a.user_id = u.user_id AND u.gender = 'Female' )
JOIN
(
SELECT
b.user_id,
b.stem
FROM
Annotations b
) AS b ON ( a.user_id <> b.user_id AND b.stem = a.stem )
WHERE
a.image_id = 1
GROUP BY
a.user_id
UPDATE: As I understand it, you want to select all combinations of 2 stems, and get a count of how many users have that combination of stems. Here is my solution:
SELECT stem1, stem2, count(*) as count FROM
(
SELECT a.user_id,a.image_id,a.stem as stem1,b.stem as stem2
FROM Annotations a JOIN Annotations b
ON a.user_id=b.user_id && b.image_id=a.image_id && a.stem!=b.stem
JOIN Users ON Users.user_id = a.user_id
WHERE Users.gender = "Female"
) as stems GROUP BY stem1, stem2 having count > 1 WHERE image_id=1;
The caveat here is that it will return 2 rows for each combinations of stems. (The second occurrence will have the stems in reverse order).
Here's my attempt to solve your problem:
SELECT COUNT(*) AS Count, a1.stem AS Stem1, a2.Stem AS Stem2
FROM Annotations AS a1
INNER JOIN Annotations AS a2 ON a1.user_id = a2.user_id AND a1.image_id = a2.image_id
AND a1.stem < a2.stem
WHERE a1.image_id = 1
GROUP BY a1.stem, a2.Stem
HAVING COUNT(*) > 1;
I did not include image_id logic.
Please see my SQL Fiddle here: http://sqlfiddle.com/#!2/4ee69/33
Based on the following data (copied from yours) I get the result posted underneath it.
CREATE TABLE Annotations
(`user_id` int, `image_id` int, `stem` varchar(14))
;
INSERT INTO Annotations
(`user_id`, `image_id`, `stem`)
VALUES
(1, 1, 'image'),
(1, 1, 'taxi'),
(1, 1, 'zebra crossing'),
(2, 1, 'person'),
(2, 1, 'zebra crossing'),
(2, 1, 'taxi'),
(3, 1, 'person'),
(3, 1, 'zebra crossing')
;
COUNT STEM1 STEM2
2 person zebra crossing
2 taxi zebra crossing

Mysql: Finding parent of multiple childrens

I have 2 tables: players and items. Now I'm looking for player who has item with known properties ex.
SELECT players.`name` FROM `players`
INNER JOIN `items` ON players.`id`=items.`ownerId`
WHERE items.`itemType` = 1 AND items.`itemClass` = 2 AND items.`itemColor` = 3
How i can find player which has more than one item i want? It is even possible in one query?
Ex. i wanna find player which has both items : type=1 class=2 color=3 , type=2 class=3 color=4
I have an idea how to do it in multiple querys: just add players.id IN (...) on every next query.
Thanks for all your help!
The best way to do this is with aggregation.
select i.ownerId
from Items i
group by i.ownerId
having max(case when i.itemType = 1 and i.itemClass = 2 and i.itemColor = 3
then 1 else 0
end) = 1 and
max(case when i.itemType = 2 and i.itemClass = 3 and i.itemColor = 4
then 1 else 0
end) = 1
If you want other information about the owner, you need to join in the players table.
In MySQL, you can simplify the having clause to:
having max(i.itemType = 1 and i.itemClass = 2 and i.itemColor = 3) = 1 and
max(i.itemType = 2 and i.itemClass = 3 and i.itemColor = 4) = 1

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.

How to select grouped records only if every record in a group meets a certain criteria in MySQL?

I'm using MySQL 5.0.88/Coldfusion8 and I have a table that contains products based on barcodes/EANs.
So a product 123 in size S,M,L,XL will have four records in the table
product size ean qty disregard_inventory
123 S 1111111111111 5 0
123 M 1111111111112 7 0
123 L 1111111111113 1 0
123 XL 1111111111114 2 0
Right now I'm searching this table like so:
SELECT count(a.id) AS total_records, a.disregard_inventory, a.qty
FROM artikelstammdaten a
...
GROUP BY a.style
HAVING sum(a.qty) != 0 OR (a.disregard_inventory = 1)
This works ok and selects all products which are not sold out (sum > 0 across all eans)/always available
I now want to add a function, so users can search for products that have at least 1pc in each size. In this case, style 123
123 S 1
123 M 0
123 L 12
123 XL 9
would not be included in the resultset as size M is sold out.
However I can't get it to work. This is what I have (produces rubbish):
GROUP BY a.style
<cfif form.select_type EQ "running_sizes">
HAVING a.qty!= 0 OR ( a.disregard_inventory = 1 )
<cfelse>
HAVING sum(a.bestand) != 0 OR (a.disregard_inventory = 1)
</cfif>
Question:
Is it at all possible to group by style and only include style when each underlying ean has a quantity > 0? If so, thanks for pointers!
EDIT:
here is my full query, which I'm testing with:
SELECT count(a.id) AS gesamt_datensaetze, a.nos, a.nos_anzeige, a.bestand, SUM(a.bestand>0) AS what
FROM artikelstammdaten a
WHERE a.aktiv = "ja"
AND a.firma LIKE "some_company"
// groups by seller_id, style
GROUP BY a.iln, a.artikelnummer
HAVING sum(a.bestand) != 0 OR (a.nos = "ja" AND a.nos_anzeige = "ja")
AND ( SUM(a.bestand > 0) = COUNT(*))
Solution:
Partenthesis mising:
HAVING (( sum(a.bestand) != 0 ) OR (a.nos = "ja" AND a.nos_anzeige = "ja" ))
AND ( SUM(a.bestand > 0) = gesamt_datensaetze )
This works.
I suggest the following query:
SELECT COUNT(a.id) AS total_records, a.disregard_inventory, a.qty
FROM artikelstammdaten a
...
GROUP BY a.style
HAVING (SUM(a.qty) != 0 OR (a.disregard_inventory = 1))
AND (SUM(qty>0) = total_records)
The last condition I added to the query enables to return a style only if the number of sizes for this product (total_records) is equal to the number of available sizes for this product (SUM(qty>0)).
qty>0 will either return 0 (when the product is not available in the given size, or 1 (when it is available). So SUM(qty>0) will return an integer number between 0 and the total number of sizes.
You can accomplish this using a join on a subquery. Basically, join on the set of product IDs where the quantity available is zero, and then only return results where there was no match.
SELECT count(a.id) AS total_records, a.disregard_inventory, a.qty
FROM artikelstammdaten a
LEFT JOIN (
SELECT DISTINCT id
FROM artikelstammdaten
WHERE qty = 0
) b
ON b.id = a.id
WHERE b.id IS NULL
...
GROUP BY a.style
HAVING sum(a.qty) != 0 OR (a.disregard_inventory = 1)
I think you should check MIN(a.qty):
select product from t group by product having min(qty)>0