SQL - how to add a value with a condition to a selection? - mysql

I have the following table structure:
name
value
success
name 1
10
0
name 2
20
0
name 2
30
1
And my query is:
SELECT name, SUM(value) as valueTotal FROM TableName GROUP BY name
The result is:
name
valueTotal
name 1
10
name 2
50
Now I want to add a new column which will contain the sum of only successful rows. But if I add this condition, it will apply to all selected fields:
SELECT name, SUM(value) as valueTotal, SUM(value) as successValueTotal FROM TableName WHERE success = 1 GROUP BY name
Here is the result I want to get:
name
valueTotal
successValueTotal
name 1
10
0
name 2
50
30
How can I add a field with a separate condition that does not affect the main query? Thx)

You can use the SUM function with a conditional aggregation on whether success is 1 or not. When success is 1, then take the value of the value field, otherwise sum up 0.
SELECT name,
SUM(value) AS valueTotal,
SUM(IF(success = 1, value, 0)) AS successValueTotal
FROM TableName
GROUP BY name
Try it here.

This is the typical use case for CASE WHEN:
SELECT name,
SUM(value) AS valueTotal,
SUM(CASE WHEN success = 1 THEN value ELSE 0 END) AS successValueTotal
FROM TableName
GROUP BY name
You can (like lemon showed) also use an if clause in MYSQL. This is a bit shorter, but the query will not work on every DB while CASE WHEN does. So I think both is fine.

Related

MySQL - Match certain IDs, but only those IDs

I have a table like so:
id_type id_option
"1" "1"
"1" "5"
"2" "1"
"2" "5"
"2" "8"
I am trying to write a query that given a list of option IDs finds the "type" that matches the list, but only those ID's
For example, if given 1 and 5 as options, it should return the type 1 but only the type 1 as the 8 required to match type 2 is not present.
I have tried the following:
SELECT *
FROM my_table
WHERE id_option IN (1, 5)
GROUP BY id_type
HAVING COUNT(DISTINCT id_option) = 2
This returns both "types" - I had hoped that the COUNT restriction of 2 would have helped but I now understand why it doesn't, but I can't think of a clever way to limit this.
I could just pull the first record as typically the types with less options are saved first but I don't think I can rely on this 100%
Thank you for your time
Here's a solution:
SELECT *
FROM my_table
GROUP BY id_type
HAVING SUM(id_option IN (1,5)) = COUNT(*)
It relies on a trick specific to MySQL: boolean true is literally the integer 1. So you can use SUM() to count the rows where a condition is true, but putting a boolean expression inside SUM().
For folks reading this who use other databases besides MySQL, you'd have to use an expression to convert the boolean condition to the integer 1:
HAVING SUM(CASE WHEN id_option IN (1,5) THEN 1 ELSE 0 END) = COUNT(*)
In this case, let all rows become part of the groups. That is, do not use a WHERE clause to restrict the query to rows where the id_option is 1 or 5. Then count the total rows in the group, and "count" (i.e. use the SUM() trick) the rows where the id_options is 1 or 5. Comparing these counts will be equal if there are no id_options values besides 1 or 5.
If you also want to make sure that both 1 and 5 are found, you need another condition:
SELECT *
FROM my_table
GROUP BY id_type
HAVING SUM(id_option IN (1,5)) = COUNT(*)
AND COUNT(DISTINCT CASE WHEN id_option IN (1,5) THEN id_option END) = 2
The CASE expression will return 1 or 5, or if there are any other values, those are converted to NULL. The COUNT() function ignores NULLs.
If you can pass the options as a sorted comma separated list string, then use GROUP_CONCAT():
SELECT id_type
FROM my_table
GROUP BY id_type
HAVING GROUP_CONCAT(id_option ORDER BY id_option) = '1,5'
If there are duplicate options for each type, use DISTINCT:
HAVING GROUP_CONCAT(DISTINCT id_option ORDER BY id_option) = '1,5'
While I can't comment yet, here's a tiny adjustment to Bill Karwin's last example (in the accepted solution):
SELECT *
FROM my_table
GROUP BY id_type
HAVING SUM(id_option IN (1,5)) = COUNT(*)
AND COUNT(DISTINCT id_option) = 2

MySQL Query to replace string with value

I have requirement like as below.
Need a MYSQL query to replace value with maching the below condition.
i have a table containg the Product ID
Product_ID
1
2
3
4
5
15
25
I want to replace the 5 with value of 1.111. My requiremnet is this that it should only replace the 5 value not the 15 value.
example 5 should be 1.111 but it sould not replace the 15 value.
You can use IF() or CASE to select a different value when the value meets a condition.
SELECT IF(product_id = '5', '1.111', product_id)
FROM yourTable
or
SELECT CASE product_id
WHEN '5' THEN '1.111'
ELSE product_id
END
FROM yourTable
CASE generalizes more easily to other values that you want to replace, since you can have multiple WHEN clauses.

Query which Find string and increment the count

I have table like that,
id name count
1 rrr 2
2 www 3
3 qqq 4
4 aaa 5
5 gyhhh 4
6 dfgdfg 5
I want to write the query which find the name in table and if it find then increment the count in count column for that name. The count maintain the no of time name used by the user.If user used the name , then I am check the name in db , if it found then I want to update row with increment in count.
A simple update query required:
If you want to increase count only if the input parameter exactly matches the name then use this:
UPDATE your_table
SET `count` = `count` + 1
WHERE `name` = ?
And if you want to increase count if the input parameter is a substring of name then you can use LIKE
UPDATE your_table
SET `count` = `count` + 1
WHERE `name` LIKE CONCAT('%',?,'%')
Note: Replace the question mark (?) by your input parameter.
Try this:
select id,name, id + 1 from
(Select id,name from table_name where name in('sa','da','ba','ca')) as a;
hope it helps..

Creating view in mysql for a group of rows

I have a mysql table with the following columns
1. id - int, auto increasing;
2. loc_id - int;
3. group_id - int;
4. target_date - int (epoch timestamp for each date at 00:00);
5. required - int;
6. actual - int;
7. updated_on;
Records in the table will be inserted in blocks, where the number of inserted rows is equal to the number of groups (referenced by group_id). The number of groups is 3, i.e. each transaction will insert 3 rows:
loc_id + target_date + updated_on will be the same for each of the 3 rows;
the group_id will be 1 to 3 for each row;
required and actual will be specific for each row.
My question is: how to create a view so each block of 3 rows is represented as a single row in a table with columns as follows:
1. loc_id,
2. target_date,
3. required_for_group1,
4. actual_for_group1,
5. required_for_group2,
6. actual_for_group2,
7. required_for_group3,
8. actual_for_group3
9. updated_on
Many thanks in advance for your help!
With the example above is it possible to find which record is the latest for each loc_id/target_date if the cut-off point is 03:00AM for each target_date (which is always epoch at 00:00)? Something like an extra column to say "latest" for the entry "closest" to (target_date+10800). i tried adding a column "case when min((target_date+10800)-updated_on) then "latest" else 0 end", but did not quite make it work..
You can do get the results you want using conditional aggregation:
select loc_id, target_date,
max(case when group_id = 1 then required end) as required_1,
max(case when group_id = 1 then actual end) as actual_1,
max(case when group_id = 2 then required end) as required_2,
max(case when group_id = 2 then actual end) as actual_2,
max(case when group_id = 3 then required end) as required_3,
max(case when group_id = 3 then actual end) as actual_3,
updated_on
from table t
group by loc_id, target_date, updated_on;
You can put this statement into a view by putting create view <viewname> as before it.

SQL where particular column values appears

I wasn't sure how to really search for this..
Lets say I have a simple table like this
ID Type
1 0
1 1
2 1
3 0
4 0
4 1
How could I select all ID's which have a type of both 0 and 1?
SELECT id,type
FROM t
GROUP BY id
HAVING SUM(type=0)>0
AND SUM(type=1)>0
You just group by id ,than with HAVING you use post aggregation filtering to check for 0 and 1.
Having is pretty expensive and that query can't hit keys.
SELECT ID FROM foo AS foo0 JOIN foo AS foo1 USING (ID) WHERE foo0.Type=0 AND foo1.Type=1 GROUP BY foo0.id.
A more generalized way of doing this would by to use a CASE column for each value you need to test combined with a GROUP BY on the id column. This means that if you have n conditions to test for, you would have a column indicating if each condition is met for a given id. Then the HAVING condition becomes trivial and you can use it like any multi-column filter, or use the grouping as your subquery and the code looks simpler and the logic becomes even easier to follow.
SELECT id, Type0,Type1
FROM (
SELECT id,
Type0 = max(CASE WHEN type = 0 THEN TRUE END)
, Type1 = max(CASE WHEN type = 1 THEN TRUE END)
FROM t
GROUP BY id
) pivot
WHERE Type0 = TRUE and Type1 = TRUE