How can I summarise several columns in MySQL - mysql

I have a table with a couple of thousand rows: from this I need to extract a total for column WTE for each value in column band, including those where the total is 0. I also need each total to be in a column of its own, so that I can easily update a summary table.
The code I have at present returns the values from the relevant rows:
SELECT
IF(band="E",WTE,0) AS `Band6_WTE`
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
But when I add SUM(), the return is incorrect (zero, when it should be several thousands):
SELECT
IF(band="E",SUM(WTE),0) AS `Band6_WTE`
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
I have looked at http://en.wikibooks.org/wiki/MySQL/Pivot_table, but I do not understand how that approach should be applied to my problem.
What should I do?

you must sum if:
SELECT
SUM(IF(band="E",WTE,0)) AS `Band6_WTE`
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14

In this particular case, wouldn't it be easier to use WHERE?
SELECT SUM(WTE) AS `Band6_WTE`
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
AND band = "E"
For the general case, you could use GROUP BY since you say you need the result for each band (each value in column band):
SELECT band, SUM(WTE) AS `WTE`
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
GROUP BY band
This will give either NULL or 0, you can instead use IFNULL to convert NULL to 0 if you like:
IFNULL(SUM(WTE), 0)
Edit: as you pointed out in the comments, you'd like multiple columns for the different bands rather than multiple rows. Generally speaking, you should not do that from SQL (use the second query and perform a pivot operation from your code), but there are exceptions, cases where it's significantly more complicated to do that outside of SQL, so here's how you could do it:
SELECT
(SELECT SUM(WTE)
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
AND band = "E") AS `Band6_WTE`,
(SELECT SUM(WTE)
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
AND band = "F") AS `Band7_WTE`,
(SELECT SUM(WTE)
FROM `orthoptists` AS o
LEFT JOIN `instances` AS i
ON o.instance_FK = i.id
WHERE i.region = 14
AND band = "G") AS `Band8_WTE`
The precise syntax might need a little bit of tweaking (some databases require each SELECT to include a FROM clause, some may require a name for each column in a subselect, I don't think MySQL does but I can't check right now), but the principle should be applicable regardless.

Noody has explained why your original query give you the incorrect result.
The original query has an aggregation function in the SELECT clause. This tells MySQL that this is an aggregation. There is no GROUP BY clause, so it returns one row, treating all rows as a single group.
Now, what happens to band in this case? In the SQL standard or any other dialect of SQL, the original query would return an error, saying something like band is not aggregated.
MySQL has a (mis)feature called Hidden Columns, which allows this syntax. It takes a random value of band from all the rows for the comparison. The value might have the value "E", in which case the sum of all WTE is returned. Or, it might have another value, in which case 0 is returned.
In general, you should avoid using Hidden Columns. Any "bare" column in your SELECT statement should also be in the GROUP BY clause, when you have an aggregation query.

Related

MySQL - How to get one of the repeated records given a condition in SQL?

I have the next results from a query. I did this:
Where the user "Adriana Smith" with ID 6 is repeated because she has different contract dates, to do that I did a left join from table bo_users to bo_users_contracts (1:m One to Many Relation). The query is below:
SELECT bo_users.ID, bo_users.display_name, COALESCE (bo_users_contracts.contract_start_date,'-') AS contract_start_date, COALESCE (bo_users_contracts.contract_end_date, '-') AS contract_end_date, COALESCE (bo_users_contracts.current,'-') AS current
FROM bo_users
LEFT JOIN bo_users_contracts ON bo_users.ID = bo_users_contracts.bo_users_id
LEFT JOIN bo_usermeta ON bo_users.ID = bo_usermeta.user_id
WHERE (bo_usermeta.meta_key = 'role' AND bo_usermeta.meta_value = 'member')
But I want to get all users, but from user Adriana I just want to get the occurrence where "current" column = 1.
So the final result would be the 3 user's records:
Alejandro, Rhonda and Adriana (with "current" = 1)
Thank you!
Since you want to limit on a table being outer joined, the limit should be placed on the join itself so the all records from bo_users is retained. (as indicated desired by the outer join)
Essentially the limit is applied before the join so the unmatched records from BO_users to bo_users_contracts are kept. If applied after the join in a where clause the records from BO_user without a matching record would have a null value for current and thus be excluded when the current=1 filter is applied.
In this example the only values which should be in the where would be from table BO_USERS.
I'd even move the bo_usermeta filters to the join or you may lose bo_users; or the left join on the 3rd table should be an inner join.
SELECT bo_users.ID
, bo_users.display_name
, COALESCE (bo_users_contracts.contract_start_date,'-') AS contract_start_date
, COALESCE (bo_users_contracts.contract_end_date, '-') AS contract_end_date
, COALESCE (bo_users_contracts.current,'-') AS current
FROM bo_users
LEFT JOIN bo_users_contracts
ON bo_users.ID = bo_users_contracts.bo_users_id
and bo_users_contracts.current = 1
LEFT JOIN bo_usermeta --This is suspect
ON bo_users.ID = bo_usermeta.user_id
WHERE (bo_usermeta.meta_key = 'role' --this is suspect
AND bo_usermeta.meta_value = 'member') --this is suspect
The lines reading this is suspect are that way because you have a left join which means you want all users from bo_users.. However if a user doesn't have a meta_key or meta_value defined, they would be eliminated. Either change the join to an inner join or move the where clause limits to the join. I indicate this as you're query is "inconsistent" in it's definition leading to ambiguity when later maintained.

SQL Join with Value Missing in One Column

I'm trying to create a SQL query that uses one table to count the number of blade servers our company has in each chassis and groups those, while joining it with chassis information from another table.
However, one of the chassis has no blades in it, so the name does not appear in the blade inventory table. Using an INNER JOIN creates a table that doesn't contain that blade in any capacity. A LEFT JOIN achieves the same effect, but a RIGHT JOIN gives me an extra row with a null value for the chassis name.
I'm guessing this is because the non-existence of that blade name in the first table is being given precedence over the second, but not sure how to correct that. My query, as of now, looks like this:
SELECT e.EnclosureName, e.PDUName, q.Blades, r.Serial#
FROM bladeinventory.table e JOIN
(
SELECT EnclosureName,COUNT(*) Blades
FROM bladeinventory.table
GROUP BY EnclosureName
) q ON e.EnclosureName = q.EnclosureName
LEFT JOIN chassisinventory.table r
ON e.EnclosureName = r.EnclosureName
GROUP BY e.EnclosureName, e.PDUName, q.Blades, r.Serial#
Is it possible to edit this in such a way that the name of the chassis with 0 blades is actually generated by the query?
Just pull the name from the chassisinventory table. I'll use coalesce(), just in case you switch the order of the joins (again):
SELECT COALESCE(r.EncloseName, e.EnclosureName) as EnclosureName, e.PDUName, q.Blades, r.Serial#
FROM bladeinventory.table e JOIN
(SELECT EnclosureName,COUNT(*) Blades
FROM bladeinventory.table
GROUP BY EnclosureName
) q
ON e.EnclosureName = q.EnclosureName LEFT JOIN
chassisinventory.table r
ON e.EnclosureName = r.EnclosureName
GROUP BY COALESCE(r.EncloseName, e.EnclosureName), e.PDUName, q.Blades, r.Serial#;
You can also use below code where case is being used which is much simpler and effective
SELECT e.EnclosureName, r.PDUName,
case when q.Blades IS NULL then 0
else q.Blades end Blades,
e.Serial#
FROM chassisinventory.table e
LEFT OUTER JOIN bladeinventory.table r on e.EnclosureName = r.EnclosureName
LEFT OUTER JOIN (SELECT EnclosureName,COUNT(*) Blades
FROM bladeinventory.table
GROUP BY EnclosureName
) q on e.EnclosureName = q.EnclosureName

Debugging MySQL query result. Uses count function, inner join and a sub-query

This is my query. The output looks fine except the COUNT function is returning numbers which seem totally arbitrary (e.g. 7-digit numbers where I'd expect 3-digit numbers):
SELECT tc.tableName, m.fieldName, COUNT(m.fieldName)
FROM apiResult, (
SELECT cc.surveyID, cc.fieldName
FROM apiResult as ar
INNER JOIN columnConversion as cc
ON substring(ar.triggerName,-10)=cc.fieldID
) AS m
INNER JOIN tableConversion as tc
ON m.surveyID=tc.surveyID
GROUP BY tc.tableName, m.fieldName;
I think, for a start, that COUNT(m.fieldName) is probably wrong, since it doesn't correspond with GROUP BY tc.tableName, m.fieldName.
Here's what the query is meant to do: one of the tables in the sub-query, apiResult, has a column called 'triggerName' which contains an ID I call 'fieldID', plus a column called 'surveyID'. The tables columnConversion and tableConversion are tables which match the IDs to human readble names. So, the follow query produces the count that I want, but, I want the IDs replaced by the human readable names, hence the above query:
SELECT cc.surveyID, cc.fieldName, COUNT(ar.triggerName)
FROM apiResult as ar
INNER JOIN columnConversion as cc
ON substring(ar.triggerName,-10)=cc.fieldID
GROUP BY (ar.triggerName)
Any ideas what I've done wrong?
Why are you mixing explicit and implicit joins? You appear to have missed a join condition on the first table. Well, actually, I don't think it is needed. This should work:
SELECT tc.tableName, m.fieldName, COUNT(m.fieldName)
FROM (SELECT cc.surveyID, cc.fieldName
FROM apiResult ar INNER JOIN
columnConversion cc
ON substring(ar.triggerName, -10) = cc.fieldID
) m INNER JOIN
tableConversion as tc
ON m.surveyID = tc.surveyID
GROUP BY tc.tableName, m.fieldName;

Comparing two values from the same select query

I have a select query which selects all products from my inventory table and joins them with two other tables (tables l_products and a_products)
SELECT
i.*,
b.title,
ROUND((i.price/100*80) - l.price,2) AS margin,
l.price AS l_price,
a.price AS a_price,
ROUND((a.price/100*80) - l.price, 2) AS l_margin
FROM inventory i
LEFT JOIN products b ON i.id = b.id
LEFT JOIN a_products a ON i.id = a.id
LEFT JOIN l_products l ON i.id = l.id
WHERE
a.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
AND l.condition LIKE IF(i.condition = 'New', 'New%', 'Used%')
This select query will normally give me a table such as...
id, title, condition, margin, l_price, a_price ...
001-new ... new 10 20 10
001-used ... used 10 25 20
002....
Now I need a condition in the query which will ignore all used products that are more expensive (have a higher a_price) than their 'new' counterparts, such as in the example above you can see that 001-used has a higher a_price than 001-new.
How can I achieve this with out having to resolve to using php
FULL JOIN this query with it self on a column which has a uniquely same value for each id prefix.
You may achieve this effect by adding another field to your SELECT call which produces same unique value for 001-new and 001-used, 002-new and 002-used...
Such value generation can be done by defining your own SQL Routine to extract first 3 characters from a column.

Need assistance using a JOIN to retrieve sums, or 0 from the RIGHT table if there is no entry present

I need a bit of assistance with the following query. I'm sure I'm overthinking this, but here goes:
SELECT
bc.name,
bc.category_id,
bc.ref_id,
COALESCE(bc.parent_id,bc.category_id) as parent_category_id,
(select name FROM budget_categories bc2 WHERE bc2.category_id = bc.parent_id) as parent_name,
(select ref_id FROM budget_categories bc2 WHERE bc2.category_id = bc.parent_id) as parent_ref_id,
COALESCE(sum(bee.amount),0) as amount
FROM
budget_expected_expenses bee, budget_categories bc
WHERE
bc.group_id IN (1,139) AND
bee.group_id = 1 AND
bee.date BETWEEN '2012-01-01' AND '2012-01-31' AND
bee.cat_id = bc.category_id
GROUP BY bee.cat_id ORDER BY bc.ref_id ASC
Basically the goal is to return the sum of each category in the 'bc' table (whether created by the group or the headquarters -- in this case 1 and 139 respectively) from the 'bee' table. If there is no entry in 'bee' for the specific group, I need it to return 0. However, this query only returns sums for values that actually have entries in the 'bee' table. I'm sure there's something I'm missing but, well, I'm missing it. Thanks in advance.
NOTE: I've tried various JOINS and nothing seems to work. Obviously it's possible, I'm just blanking.
I'v rewritten using newer ANSI style than older format of listing tables and explicitly adding the joining criteria in the WHERE clause. This way you should better see the relationship of how each table is related to the others and the explicit limits (such as date criteria) you want applied to those JOINED elements. The where clause is left with just the criteria against the first table of the select.
I've applied TWO joins to the category table, once based on your intent to find the PARENT category and name and reference ID (provided a parent ID exists). If not, it will look at the Default category ID found.
The only hiccup I THINK I see is the join to the expected expenses where your criteria is only that of group_ID = 1, yet your criteria for the budget categories is either 1 OR 139 (via in clause). If they are in-fact SUPPOSED to be the same, then adjust the join to be
" AND bc.group_ID = bee.group_ID"
Finally, confirm what you want grouped by... the original category, or the parent its associated with, but that too is your call.
SELECT
bc.name,
bc.category_id,
bc.ref_id,
COALESCE( bc.parent_id,bc.category_id) as Which_Category_id,
COALESCE( bcParent.Name, bcDefCat.Name ) as Category_Name,
COALESCE( bcParent.Ref_ID, bcDefCat.Ref_ID ) as Category_Ref_ID,
COALESCE(sum(bee.amount),0) as amount
FROM
budget_categories bc
LEFT JOIN budget_expected_expenses bee
ON bc.category_id = bee.cat_id
AND bee.group_ID = 1
AND bee.date BETWEEN '2012-01-01' AND '2012-01-31'
LEFT JOIN budget_categories bcParent
on bc.parent_ID = bcParent.category_ID
JOIN budget_categories bcDefCat
on bc.Category_ID = bcDefCat.category_ID
WHERE
bc.group_id IN (1,139)
GROUP BY
bc.category_id
ORDER BY
bc.ref_id ASC
FROM budget_expected_expenses bee, budget_categories bc
If you want entries that appear in bc but not necessarily bee, then do a LEFT JOIN:
FROM budget_categories bc LEFT JOIN budget_expected_expenses bee ON bc.category_id = bee.cat_id
You'll also need to group by the column that's guaranteed to not be null:
GROUP BY bc.category_id
Also, your query is way more complicated than it needs to be, with the cross join and complicated subqueries. You could simplify it to something like this:
SELECT
bc.name,
bc.category_id,
bc.ref_id,
COALESCE(bc.parent_id,bc.category_id) as parent_category_id,
parent.name as parent_name,
parent.ref_id as parent_ref_id,
COALESCE(sum(bee.amount),0) as amount
FROM
budget_categories bc
LEFT JOIN
budget_expected_expenses bee ON bc.category_id = bee.cat_id
LEFT JOIN
budget_categories parent ON bc.parent_id = parent.category_id
WHERE
bc.group_id IN (1,139) AND
bee.group_id = 1 AND
bee.date BETWEEN '2012-01-01' AND '2012-01-31'
GROUP BY bc.category_id ORDER BY bc.ref_id ASC
Suggest you rewrite the query to have a LEFT join:
SELECT
....
COALESECE(SUM(bee.amount,0.00))
FROM
budget_categories bc
LEFT JOIN
budget_expected_expenses bee ON bee.cat_id = bc.category_id
WHERE
...
LEFT JOIN will always end up with at least one result record for every record in the left hand side of the join. In the event there were no matching right-hand records, NULL will be used, which is why you need COALESCE() to turn that to a 0.00.