So I have this bit of mysql that I'm trying to work out. My goal is to insert the count of a grouping into the primary records to tell me how many of each status is within the related table for the record, so the result might look like this:
| id | name | count1 | count2 |
------------------------------------
| 1 | primary 1 | 5 | 3 |
| 1 | primary 2 | 2 | 7 |
select * from primaryTable
left join (
select
case
when relationTable.relation_status_id = 1
then count(*)
END as count1,
case
when relationTable.relation_status_id = 2
then count(*)
END as count2
) relationTable
on relationTable.primary_id = primaryTable.id
I tried using a subquery to do it, which worked, but requires a select per count, which I'm trying to avoid.
Adding a group by to the subquery resulted in an error that more than one row was being returned.
In the subquery, rather than aggregate COUNT()s inside CASE, you may more easily use SUM() to add up the result of a boolean comparison (0 or 1) to return a result resembling a count.
SELECT
primaryTable.*,
count1,
count2
FROM
primaryTable
JOIN (
SELECT
primary_id,
-- Sum the results of a boolean comparison
SUM(relation_status_id = 1) AS count1,
SUM(relation_status_id = 2) AS count2
FROM relationTable
-- Group in the subquery
GROUP BY primary_id
-- Join the subquery to the main table by primary_id
) counts ON primaryTable.primary_id = counts.primary_id
Note that because MySQL treats the booleans the same as 0 or 1, the comparison relation_status_id = 1 returns 1 or 0. The syntax above isn't supported in every RDBMS. To be more portable, you would need to use a CASE inside SUM() to explicitly return an integer 1 or 0.
SUM(CASE WHEN relation_status_id = 1 THEN 1 ELSE 0 END) AS count1,
SUM(CASE WHEN relation_status_id = 2 THEN 1 ELSE 0 END) AS count2
Your original attempt has some syntax problems. Chiefly, it has no FROM clause, which is causing MySQL to think it should be treated as a scalar value and then complain that it returns more than one row.
I've written a query that builds a small table of information from a couple of data sources, it uses a self made table to reference the vehicle model for the final group by which is how the data needs to be viewed, however when I group by vehicle it misses out figures in the subquery column from the group by, i.e. if I group by Prefix it shows the correct numbers, grouped by Vehicle hides off some of the data.
The Prefix can relate to a couple of like vehicle models and hence the need to group by vehicle. Can anyone see what I've done wrong easily from the SQL query below please.
SELECT Vehicle, COUNT(`Chassis-No`) AS Stock,
ROUND((100/COUNT(`Chassis-No`)) * SUM(CASE WHEN `Vehicle Age` > '182' THEN 1 ELSE 0 END),1) AS Perc6Months,
ROUND((100/COUNT(`Chassis-No`)) * SUM(CASE WHEN `Vehicle Age` > '365' THEN 1 ELSE 0 END),1) AS Perc12Months,
(SELECT COUNT(VIN_Prefix) FROM Orderdownload
INNER JOIN VehicleMatrix ON (`VIN_Prefix` LIKE 'S%' AND Prefix = LEFT(`VIN_Prefix`,2)) OR (`VIN_Prefix` NOT LIKE 'S%' AND Prefix = LEFT(`VIN_Prefix`,1)) WHERE DealerCode = 'AA12345' AND `VIN_Prefix` = IF(LEFT(`Chassis-No`,1)='S',LEFT(`Chassis-No`,2),LEFT(`Chassis-No`,1))) As Qty
FROM DealerAgedStock
INNER JOIN VehicleMatrix AS VM
ON (`Chassis-No` LIKE 'S%' AND Prefix = LEFT(`Chassis-No`,2)) OR (`Chassis-No` NOT LIKE 'S%' AND Prefix = LEFT(`Chassis-No`,1))
WHERE `DL Dealer Code` = 'AA12345'
GROUP BY Vehicle
Grouped on Vehicle I get the following:
Vehicle | Perc6Months | Perc12Months | Qty
Mondeo | 37.5 | 0 | 2
Grouped on Prefix I get the following:
VIN_Prefix | Perc6Months | Perc12Months | Qty
S1 | 25 | 0 | 2
S2 | 50 | 0 | 2
Ideally it should look this this:
Vehicle | Perc6Months | Perc12Months | Qty
Mondeo | 37.5 | 0 | 4
Where S1 and S2 are relative to the Vehicle Mondeo, thus it gives me the first instance of subquery rather than adding them together.
My question is: why does the Group By not add the figures together properly from the subquery? I need it to add them to have the correct figures...
Once again i need yours help ;). I have a lot data and mysql request are slower and slower so the need request that i need i want group in one comand.
My example DB structure:
|product|opinion (pos/neg)|reason|
__________________________________
|milk | pos | good |
|milk | pos |fresh |
|chocolate| neg | old |
|milk | neg | from cow|
So i need information about all diffrent product (GROUP BY) count of it, and count of pos opinion for each product. I want output like that:
|product|count|pos count|
_________________________
|milk | 3 | 2 |
|chocolate| 1 | 0 |
I hope that my explain was good enought ;)
Or go to work: I write two commands
SELECT COUNT(*) as pos count FROM table WHERE product = "milk" AND opinion = "pos" GROUP BY `opinion`
And Second one
SELECT product, COUNT(*) FROM table GROUP BY `product`
I don't know how to join this two request, maybe that is impossible? In my real use code i have additional category collumn and i use WHERE in second command too
select product,
count(*) as total_count
sum(
case when opinion='pos' then 1
else 0 end
) as pos_count
from the_table
group by product;
SELECT product,
COUNT(*) TotalCount,
SUM(opinion = 'pos') POSCount
FROM tableName
GROUP BY product
SUM(opinion = 'pos') is MySQL specific syntax that counts the total of result based from the result of boolean arithmethic. If you want it to be more RDBMS friends, use CASE
SUM(CASE WHEN opinion = 'pos' THEN 1 ELSE 0 END)
SQLFiddle Demo
I'm having trouble with summing the fields values based on another fields value.
I need to SUM(activities.points) based on activities.activity_type if it's used_points or added_points and put it in AS used_points/added_points.
Table activities:
id | subscription_id | activity_type | points
--------------------------------------------------
1 | 1 | used_points | 10
2 | 1 | used_points | 50
3 | 1 | added_points | 20
4 | 1 | added_points | 30
5 | 2 | used_points | 20
6 | 2 | used_points | 45
7 | 2 | added_points | 45
8 | 2 | added_points | 45
Table subscriptions:
id | name | current_points
-------------------------------------
1 | card_1 | 700
2 | card_2 | 900
What I need:
name | current_points | used_points | added_points
-----------------------------------------------------------
card_1 | 700 | 60 | 50
card_2 | 900 | 65 | 90
What I tried :
SELECT
subscriptions.name,
subscriptions.current_points,
IF(activities.activity_type="used_points", SUM(activities.points), null)
AS used_points,
IF(activities.activity_type="added_points", SUM(activities.points), null)
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription.id = subscription.id
GROUP BY subscriptions.name
Which is wrong.
Thanks
You want to use SUM(IF( )). You want to add up the values returned from the IF. You want that IF expression to be evaluated for each individual row. Then, use the SUM aggregate to add up the value returned for each row.
Remove the SUM aggregate from inside the IF expression and instead, wrap the IF inside a SUM.
Followup
Q But why SUM() inside of IF doesn't work ?
A Well, it does work. It's just not working the way you want it to work.
The MySQL SUM function is an "aggregate" function. It aggregates rows together, and returns a single value.
For an expression of this form: IF(col='foo',SUM(numcol),0)
What MySQL is doing is aggregating all the rows into the SUM, and returning a single value.
Other databases would pitch a fit, and throw an error with the reference to the non-aggregate col in that expression. MySQL is more lenient, and treats the col reference like it was an aggregate (like MIN(col), or MAX(col)... working on a group of rows, and returning a single value. In this case, MySQL is selecting a single, sample row. (It's not determinate which row will be "chosen" as the sample row.) So that reference to col is sort of like a GET_VALUE_FROM_SAMPLE_ROW(col). Once the aggregates are completed, then that IF expression gets evaluated once.
If you start with this query, this is the set of rows you want to operate on.
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
When you add a GROUP BY clause, that's going to aggregate some of those rows together. What you will get back for the non-aggregates is values from a sample row.
Try adding GROUP BY s.name to the query, and see what is returned.
Also try adding in some aggregates, such as SUM(a.points)
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
, SUM(a.points) AS total_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
GROUP BY s.name
Finally, we can add in the expressions in your query into the SELECT list:
, IF(a.activty_type='used_points',SUM(a.points),NULL) AS if_used_sum
, IF(a.activty_type='added_points',SUM(a.points),NULL) AS if_added_sum
What we discover is that the value returned from these expressions will either be SUM(a.points), which will match the total_points, or it will be NULL. And we can see the value of the activity_type column, retrieved from a single, sample row for each group, and we can see that this is expression is "working", it's just not doing what we you really want to happen: for the conditional test to run on each individual row, returning a value for points or a null, and then summing that up for the group.
Your code is only slightly out:
SELECT
subscriptions.name,
subscriptions.current_points,
SUM(IF(activities.activity_type="used_points", 0, activities.points))
AS used_points,
SUM(IF(activities.activity_type="added_points", 0, activities.points))
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription_id = subscription.id
GROUP BY subscriptions.name, subscriptions.current_points
Note the fixed typo in the second last line - you wrote subscription.id instead of subscription_id. Also you only grouped by name instead of name and current_points, not sure if that's allowed in mysql (I use T-SQL), it's good practice to have it there anyway.
Well, I did it not using the IF statement. Here's the example (http://sqlfiddle.com/#!2/076c3f/12):
SELECT
subs.name,
subs.current_points,
(SELECT SUM(points) FROM activities WHERE type = 1 AND subs_id = subs.id) AS used_points,
(SELECT SUM(points) FROM activities WHERE type = 2 AND subs_id = subs.id) AS added_points
FROM activities
JOIN subs ON activities.id = subs.id
GROUP BY subs.name
NOTE: I changed the type from VARCHAR to INT to simplify.
Try change
IF(activities.activity_type="used_points", null, SUM(activities.points))
AS used_points,
IF(activities.activity_type="added_points", null, SUM(activities.points))
AS added_points
To next
SUM(IF(activities.activity_type="used_points", activities.points, 0))
AS used_points,
SUM(IF(activities.activity_type="added_points", activities.points, 0))
AS added_points
In this way you check column and sum points or 0
To sum a column of integer values(c1) based on another column of character values(c2). And if you need to sum only not null values, the below code will help.
SELECT SUM(c1) FROM table_name WHERE c2 <> '' AND c2 IS NOT NULL
I have table like this:
+----+---------+---------+--------+
| id | value_x | created | amount |
+----+---------+---------+--------+
value_x is set of six strings, lets say "one", "two", "three", etc.
I need to create report like this:
+--------------+-------------------------+-------------------+----------------------+
| day_of_month | "one" | "two" | [etc.] |
+--------------+-------------------------+-------------------+----------------------+
| 01-01-2011 | "sum(amount) where value_x = colum name" for this specific day |
+--------------+-------------------------+-------------------+----------------------+
Most obvious solution is:
SELECT SUM(amount), DATE(created) FROM `table_name` WHERE value_x=$some_variable GROUP BY DATE(created)
And loop this query six times with another value for $some_variable in every iteration, but I'm courious if is it possible to do this in single query?
What you're asking is called a "pivot table" and is typically achieved as below. The idea is for each potential value of value_x you either produce a 1 or 0 per row and sum 1's and 0's to get the sum for each value.
SELECT
DATE(created),
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'one',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'two',
SUM(CASE WHEN value_x = 'one' THEN SUM(amount) ELSE 0 END) AS 'three',
etc...
FROM table_name
GROUP BY YEAR(created), MONTH(created), DAY(created)
This will come close:
SELECT
s.day_of_month
,GROUP_CONCAT(CONCAT(s.value_x,':',s.amount) ORDER BY s.value_x ASC) as output
FROM (
SELECT DATE(created) as day_of_month
,value_x
,SUM(amount) as amount
FROM table1
GROUP BY day_of_month, value_x
) s
GROUP BY s.day_of_month
You will need to read the output and look for the value_x prior to the : to place the items in the proper column.
The benefit of this approach over #Michael's approach is that you do not need to know the possible values of field value_x beforehand.