One query for multiple where? - mysql

I have a Query which gets the average score from the answers table, And groups it by the category which the question is in.
This query gets the desired result for answers where the coach_id = 0
This is the desired result in which for every coach_id i get another row
Now my question is: Is it possible to also get the same answers from the same table ( so almost the same query ) but where the coach_id = 1 or coach_id = 2.. in the same query?##
This is my query
SELECT
ROUND((SUM(score) / COUNT(vragen.id) * 10),1) as score
FROM antwoorden
JOIN vragen
ON vragen.id = antwoorden.vraag_id
JOIN categorieen
ON categorieen.id = vragen.categorie_id
WHERE antwoorden.werknemer_id = 105 AND antwoorden.coach_id = 0
GROUP BY categorieen.id
Any ideas? Thanks!

What you want is a conditional sum, I think.
Column score_0, that gets the average score for coach_id = 0.
Column score_1, that gets the average score for coach_id = 1.
The count will not work neither, as count ... counts everything! Both coach_id 0 and 1. So you'll have to use a conditional sum there, too.
Besides you'll need the coach_id filter suggested by Neville K.
So:
SELECT
ROUND((
SUM(CASE WHEN antwoorden.coach_id = 0 THEN score ELSE 0 END) /
SUM(CASE WHEN antwoorden.coach_id = 0 THEN 1 ELSE 0 END) * 10
), 1) as score_0,
ROUND((
SUM(CASE WHEN antwoorden.coach_id = 1 THEN score ELSE 0 END) /
SUM(CASE WHEN antwoorden.coach_id = 1 THEN 1 ELSE 0 END) * 10
), 1) as score_1
FROM antwoorden
JOIN vragen
ON vragen.id = antwoorden.vraag_id
JOIN categorieen
ON categorieen.id = vragen.categorie_id
WHERE antwoorden.werknemer_id = 105 AND antwoorden.coach_id IN (0,1)
GROUP BY categorieen.id
I think this is what you meant.

Since you haven't provided your table schemas I would have a hard time writing the query for your actual example. What you want is the SUM(IF(...)) pattern, aggregating on a conditional:
SELECT
foo_id,
SUM(IF(bar_id = 1, baz, 0)) as sum_baz_bar_1,
SUM(IF(bar_id = 2, baz, 0)) as sum_baz_bar_2,
SUM(IF(bar_id = 3, baz, 0)) as sum_baz_bar_3
FROM table
WHERE ...
GROUP BY foo_id
You need to think carefully about your aggregation functions when using this pattern, especially with COUNT or other functions that deal with the presence of a value (such as 0) rather than the value of it.
If you post your table schemas (SHOW CREATE TABLE) or even better set up a sample data set on sqlfiddle.com, I would be happy to help show how to do it with your actual schemas.

Yes.
You can use the in clause
... antwoorden.coach_id in (0, 1, 2)

Related

How to randomly assign value to a column from a subquery in SQL?

I have been struggling with something in MySQL.
Here is my problem:
I have a query returning a list of dates, here it is:
SELECT MIN(act_date) AS d FROM acts WHERE act_type_id = 'PA' GROUP BY contact_id;
Now, I want to randomly assign values from this list to each row of another query so that the distribution of dates stay the same. Then, my higher level query will compute things based on that dates. Here is the higher level query:
SELECT
a1.contact_id,
DATEDIFF(d, MAX(a1.act_date)) / 365 AS recency,
COUNT(a1.amount) AS frequency,
AVG(a1.amount) AS avg_amount,
MAX(a1.amount) AS max_amount,
DATEDIFF(d, MIN(a1.act_date)) / 365 AS seniority,
COUNT(CASE WHEN payment_method_id = 'CH' THEN 1 ELSE NULL END) AS nb_ch,
0 AS switched
FROM
acts a1
WHERE
YEAR(act_date) >= 1991
GROUP BY 1
HAVING SUM(CASE WHEN act_type_id = 'PA' THEN 1 ELSE 0 END) = 0;
I don't really know if it is possible to do that with SQL. Should I rather import my data in R at first in order to do it?
Thank you all very much for your help!
Some notes:
A subquery that returns a scalar can be used in the SELECT list.
The MySQL RAND() function returns a pseudo-random value.
We can use an expression in an ORDER BY clause.
...
For small sets, or where performance isn't a concern, we can do something like this:
SELECT ...
, 0 AS switched
, ( SELECT MIN(r.act_date)
FROM acts r
WHERE r.act_type_id = 'PA'
GROUP BY r.contact_id
ORDER BY RAND()
LIMIT 1
) AS act_date_min_by_contact_id_rand
FROM acts a1
...

How to merge 2 queries which check for same flag columns but different value

I have 2 query which i used to fetch sum of values based on flag value
SELECT SUM(stock_deliveries.stockValue) AS stockValues1
FROM `stock_deliveries`
LEFT JOIN `delivery` ON `delivery`.`pk_deliveryId` = `stock_deliveries`.`fk_deliveryId`
WHERE `stock_deliveries`.`fk_stockID` = '189'
AND `delivery`.`delivery_completed` = '1'
SELECT SUM(stock_deliveries.stockValue) AS stockValues2
FROM `stock_deliveries`
LEFT JOIN `delivery` ON `delivery`.`pk_deliveryId` = `stock_deliveries`.`fk_deliveryId`
WHERE `stock_deliveries`.`fk_stockID` = '189'
AND `delivery`.`delivery_completed` = '0
Is it possible to merge this 2 query in single query ? thanks in advance.
Yes, you'd use conditional aggregation (i.e. CASE WHEN inside an aggregation function like SUM):
select
sum(case when d.delivery_completed = 0 then sd.stockvalue end) as stockvalues0,
sum(case when d.delivery_completed = 1 then sd.stockvalue end) as stockvalues1
from stock_deliveries sd
left join delivery d on d.pk_deliveryid = sd.fk_deliveryid
where sd.fk_stockid = 189;
You can use a single query with the help of GROUP BY
SELECT SUM(stock_deliveries.stockValue) AS stockValues, `delivery`.`delivery_completed` as deliveryCompleted
FROM `stock_deliveries`
LEFT JOIN `delivery` ON `delivery`.`pk_deliveryId` = `stock_deliveries`.`fk_deliveryId`
WHERE `stock_deliveries`.`fk_stockID` = '189'
GROUP BY `delivery`.`delivery_completed`;
Doing so you will have a row for every value of delivery_completed with the associated sum (if you need only 0 and 1 values you can add to the where clause and delivery.delivery_completed IN (0,1)).
Example:
stockValues deliveryCompleted
1000 0
700 1
you can use "if" inside sum to check if you need the value in stockValues1 or stockValues0
SELECT SUM(IF(d.delivery_completed = 1, sd.stockValue, 0)) AS stockValues1, SUM(IF(d.delivery_completed = 0, sd.stockValue, 0)) AS stockValues2
FROM stock_deliveries sd
LEFT JOIN delivery d ON d.pk_deliveryId = sd.fk_deliveryId
WHERE sd.fk_stockID = '189'
AND (d.delivery_completed = 1 OR d.delivery_completed = 0)
obviously if "delivery_completed" is only 1 or 0, you can omit the last AND in the query ;)
Edit: as Thorsten Kettner answered you can also use case-when syntax, with only one "case" they are substantially equivalent. The if syntax in this case is more concise, but if you have more options is more flexible, for instance:
SELECT CASE sometable
WHEN 'top' THEN 'high'
WHEN 'center' THEN 'medium'
ELSE 'low'
END AS position
You can use UNION ALL operator to keep the logic of 2 result rows but these are now in the same result instead of 2 separated

SQL query to count total ocurrences of values in one column and relative occurrence in another column

This is my first post, so any general corrections to format/content are also welcome. I'm relatively new to SQL.
Say I have a database which collects test results from an classification evaluation. I know what the expected outcome is for each test. I also have a column indicating whether the test was successful, ie the expected value returned matched the expected value. It looks something like this:
Expected_Result Result Success
A A True
A B False
B B True
A A True
B A False
I know I can return the total occurrences of each expected type withSELECT Expected_Result, COUNT(Expected_Result) FROM Evaluation_Results GROUP BY Expected_Result.
I know how to count the number of false detections for a specific expected outcome with SELECT COUNT(*) FROM Evaluation_Results WHERE Success = 'True' AND Expected_Result = 'A'
Where I'm struggling is combining the two. I would like the query to return a list of all distinct expected results, the total of each, the count of successful results, and the percentage of the total, like so:
Expected_Result Total Num_Successful Success_Rate
A 3 2 66.67
B 2 1 50.00
You could use a CASE expression to perform a condition check during aggregation. A case statement identifies a conditional outcome. For instance you could use:
select evaluation_result
, count(*) AS total
, sum(case when success='true' and result='a' then 1 else 0 end) AS num_successful
, sum(case when success='true' and result='a' then 1 else 0 end)/count(*) AS success_rate
from evaluation_results group by evaluation_result;
Basically what's happening there is you're taking a count(*) of all grades, a sum() of a 1 or 0 based on a conditional outcome, then performing the ratio math. There's no need for a join here. The CASE Expression is a powerful conditional statement which can be used in so many diverse ways.
Or for a more flexible solution have a look at this:
select evaluation_result
, count(*) AS total
, sum(case when success='true' and result=evaluation_result then 1 else 0 end) AS num_successful
, sum(case when success='true' and result=evaluation_result then 1 else 0 end)/count(*) AS success_rate
from evaluation_results group by evaluation_result;
You can use self join if table is same like.
SELECT distinct e.Expected_Result, COUNT(Expected_Result), sum(e1.columns name), avg(e1.column name)
FROM Evaluation_Results e
left join Evaluation_Results e1 on e1.col=e.col
GROUP BY e.Expected_Result
Use this simple Query and check for the result..
select Expected_Result, count(Expected_Result) Total,
sum(IF ('True' = Success, 1, 0) ) Num_Successful,
avg(IF ('True' = Success, 1, 0 )) Success_Rate
from Evaluation_Results group by Expected_Result

Query to get different values depending on all column values

I have a table tasks with a status column The value of status can either be 0,1 or 2.
I need to implement the following logic:
If any value in status is 0 then the answer is 0.
If all values are 2 then the result is 2.
Otherwise the result is one.
Right now I'm doing this with 3 queries.
a) SELECT status FROM tasks
b) SELECT status FROM tasks WHERE status = 2;
c) SELECT status FROM tasks WHERE status = 0
So a) is for getting the total number of rows. Then I compare that to the number of rows given by b). If the match then the answer is 2. If they don't I do query c). If it is non-empty the answer is 0 otherwise the answer is 1.
Is there any way to write a single query for this? Or is there a better way of doing it?
You can do it using conditional aggregation:
SELECT CASE
WHEN s0 >= 1 THEN 0
WHEN sAll = s2 THEN 2
ELSE 1
END
FROM (
SELECT COUNT(*) AS sAll,
COUNT(CASE WHEN status = 0 THEN 1 END) AS s0,
COUNT(CASE WHEN status = 2 THEN 1 END) AS s2
FROM tasks ) t
The sub-query performs all counts required to apply your business logic. The outer query simply uses this info, to produce the required result.
It's a bit unclear from your description whether the first condition in the CASE takes precedence over the second. If not, then just switch the position of the two WHENs.
Try:
SELECT MIN(status) FROM tasks;
- since when all status values are 2, the minimum will be 2, when any value is 0 then the minimum will be 0 and otherwise the minimum wll be 1.
Use following
SELECT (case
when ((SELECT count(*) FROM tasks WHERE status = 0)>0) then 0
when ((SELECT count(*) FROM tasks WHERE status = 2)=(SELECT count(*) FROM tasks)) then 2
else 1 end) AS countOfStatus

Alternative to "IN" that works like "AND" instead of "OR"

From my understanding, IN works like this:
$arrayName = array(1, 2, 3);
SELECT *
FROM tableName
WHERE productID IN ($arrayName)
is the equivalent of:
SELECT *
FROM tableName
WHERE productID = 1 OR productID = 2 OR productID = 3
I'm wondering if there's a SQL function that works like IN but uses AND in place of OR to compare to an array. Something that would expand to this:
SELECT *
FROM tableName
WHERE productID = 1 AND productID = 2 AND productID = 3
Not that it's necessary, but for context I'm simply creating a sort list for some search results that are being populated on a PHP page via jQuery. I can do what I need with PHP, I'll simply create the query dynamically depending on what options the user has selected, but I'd rather use an intelligent SQL function if possible.
***EDIT: Thanks everyone for the help. I explained my problem very poorly and you were still able to sort it out, which I appreciate. I found that someone else had asked this question more eloquently and received an answer that I can use:
Is there something in MySQL like IN but which uses AND instead of OR?
I'm trying to figure out how to accept an answer and close this but I'm having a bit of trouble...
You cannot possibly do this,
SELECT *
FROM tableName
WHERE productID = 1 AND productID = 2 AND productID = 3
the condition will always returns false because a row can have only one value on its column, the alternative way to do this is by grouping the result, ex.
SELECT colName
FROM tableName
WHERE productID IN (1,2,3)
GROUP BY colName
HAVING COUNT(DISTINCT colName) = 3
by having a condition HAVING COUNT(DISTINCT colName) = 3, this means that the instance of a record must be equal to the total count of parameters supplied on IN clause.
As written, your query will produce no rows. It is not possible for productID in a row to be equal to both 1 and 2 at the same time.
You are probably looking for a group of rows that contain these three products. Say you want to find orders that have all three products. You can use something like:
select orderid
from orderlines ol
group by orderid
havnig max(case when ol.productid = 1 then 1 else 0 end) > 0 and
max(case when ol.productid = 2 then 1 else 0 end) > 0 and
max(case when ol.productid = 3 then 1 else 0 end) > 0
The GROUP BY with the HAVING clause will find orders where all three products are present.
SELECT orderid
FROM tableName
WHERE productID IN (1, 2, 3)
GROUP BY orderid
HAVING COUNT(DISTINCT productID) = 3 --this number must match the number of unique IDs in the IN clause