I have a table like
ID Name
1 ABC
1 DEF
1 VVV
1 BBB
1 BCD
2 ZZZ
2 BAA
3 AAA
3 BBB
3 BBC
I want to get the ratio of all the names that start with A to All the names that start with B group by ID.
So the output should be
ID Ratio
1 0.5
2 0
3 0.33
.
SELECT (ID, (SELECT COUNT(*) FROM `table` WHERE name LIKE 'A%') /
(SELECT COUNT(*) FROM `table` WHERE name LIKE 'B%')) AS `ratio` from table Group by ID
does not give me the right answer. It takes the total ratio of A/B across all ID's into account and writes that number for all the ID's.
I'd try:
SELECT ID, CASE WHEN B = 0 THEN 0 ELSE A/B END AS Ratio FROM
( SELECT ID,
SUM(CASE WHEN Name LIKE 'A%' THEN 1 ELSE 0 END) AS A,
SUM(CASE WHEN Name LIKE 'B%' THEN 1 ELSE 0 END) AS B
FROM my_table GROUP BY ID ) AS grouped;
The inner SELECT gets the group IDs, and for every ID, the number of items beginning with A and those beginning with B.
The outer SELECT (you can omit it if you're sure that there'll always be at least one B-row) checks that the ratio makes sense before attempting to calculate it.
Or else:
SELECT ID, COALESCE(SUM(CASE WHEN Name LIKE 'A%' THEN 1 ELSE 0 END)
/ SUM(CASE WHEN Name LIKE 'B%' THEN 1 ELSE 0 END), 0)
FROM my_table GROUP BY ID;
This takes advantage of the fact that, if the number of B-rows is zero, the division will yield NULL. The COALESCE transforms that NULL in a 0.
This will do it:
SELECT
id,
SUM(IF(name LIKE 'A%',1,0))/SUM(IF(name like 'B%',1,0))
FROM `table`
GROUP BY ID
Related
Let's say I have this table:
name| value
----|------
A | 0
B | 0
A | 1
C | 1
A | 1
The select I want is to give the result like this:
A | 2
B | 0
C | 1
In first phase I tried with:
SELECT name, count(0)
FROM table
WHERE value > 0
GROUP BY name;
Which result
A | 2
C | 1
I also want to include B with count(0) = 0. How I can do this?
You want to aggregate your rows and get one result row per name. This translates to GROUP BY name in SQL. For counting use COUNT and inside use CASE WHEN to decide what to count.
select name, count(case when value > 0 then 1 end)
from mytable
group by name
order by name;
This works because COUNT only counts non-null occurences. We could just as well use SUM for counting: sum(case when value > 0 then 1 else 0 end).
In your example there is only 0 and 1. If these are the only possible values, you can just add them up:
select name, sum(value)
from mytable
group by name
order by name;
Do you just want aggregation? The following query would actually produce the correct results for your sample data:
select name, sum(value) sum_value
from mytable
group by name
Try this:
SELECT a.name, count(*)*(case when sum(a.value) >= 1 then 1 else 0 end)
FROM table a
GROUP BY name;
For a better solution, give more details about the nature of the value column
I have a table like this
NAME FLAG
---------------------------
abc 1
abc 0
abc 2
def 1
def 2
xyz 0
xyz 0
xyz 1
efg 1
I need query that list the name (group the result) which flag not included 0 and included 1.The result simply like below:-
NAME FLAG
------------------
def 1
efg 1
I have tried the query but not results the same.
select * mytable where FLAG NOT IN (0) GROUP BY NAME;
This is the simplest solution I can come up with. I believe there are more elegant answers possible.
SELECT *
FROM mytable
WHERE name NOT IN (SELECT name FROM mytable WHERE flag = 0)
AND flag = 1;
You can use NOT EXISTS clause in this way.
select a.*
from tbl a
where not exists
(select *
from tbl b
where b.name=a.name
and b.flag=0)
and a.flag=1;
We can use aggregation. The trick is to run a condition on each row, and then use an aggregate function to pick out the net result of the condition from all of the rows...
SELECT t.name
FROM a_table_like_this t
GROUP BY t.name
HAVING MAX(t.flag=0) <> 1
AND MAX(t.flag=1) = 1
The expressions in the HAVING clause are MySQL shorthand.
A more ANSI standards compliant equivalent would be:
SELECT t.name
FROM a_table_like_this t
GROUP BY t.name
HAVING MAX( CASE WHEN t.flag=0 THEN 1 ELSE 0 END) <> 1
AND MAX( CASE WHEN t.flag=1 THEN 1 ELSE 0 END) = 1
To get a better handle on what this is doing, take a look at how those expressions are evaluated on each row. Remove the GROUP BY clause, and move those expressions into the SELECT list...
SELECT t.name
, t.flag
, CASE WHEN t.flag=0 THEN 1 ELSE 0 END AS `fl=0`
, CASE WHEN t.flag=1 THEN 1 ELSE 0 END AS `fl=1`
FROM a_table_like_this t
ORDER
BY t.name
, t.flag
We get something like
name flag fl=0 fl=1
------ ---- ---- ----
abc 1 0 1
abc 0 1 0
abc 2 0 0
def 1 0 1
def 2 0 0
xyz 0 1 0
xyz 0 1 0
xyz 1 0 1
efg 1 0 1
If we now aggregate these rows by name (GROUP BY name), we can use MAX() aggregate to pick out a 1 (if there's one in the group. Otherwise, we'll get back a 0.)
And we can do conditional tests on the results of aggregates functions in the HAVING clause.
I have a table:
id flag
1 Y
1 Y
1 Y
1 N
1 N
2 Y
2 N
2 N
3 Y
3 N
i want to do as select statement which will give me the following output.
id count_flag_Y count_flag_N
1 3 2
2 1 2
3 1 1
I was trying using the select case method but getting syntax error.
SELECT id,SUM(CASE WHEN flag= 'Y') as count_flag_Y,
SUM(CASE WHEN flag= 'N') as count_flag_N
from tablename
GROUP BY id
Is there any way to do it?
Your query was not far off, you only have a slight problem with the CASE expressions. Try this:
SELECT id,
SUM(CASE WHEN flag= 'Y' THEN 1 ELSE 0 END) AS count_flag_Y,
SUM(CASE WHEN flag= 'N' THEN 1 ELSE 0 END) AS count_flag_N
FROM tablename
GROUP BY id
You are very close, but in MySQL the CASE is not needed:
SELECT id, SUM(flag = 'Y') as count_flag_Y,
SUM(flag = 'N') as count_flag_N
FROM tablename
GROUP BY id;
MySQL treats boolean expressions like integers in a numeric context, with "1" for true and "0" for false.
Before a user starts a private chat (between 2 members, not a group chat) I want to check and see if there is already a chat consisting of only these two members. In case they've deleted the chat on their end, when they go to message that same user again I want it to merge with the old chat instead of starting a duplicate chat for the same two members.
This is my structure
`chats` table
id created_time
1 [TIMESTAMP]
2 [TIMESTAMP]
`chats.parties` table
id chat_id member_id invited_by
1 1 1 0 // creator of chat
2 1 2 1
3 1 3 1
4 2 1 0
5 2 2 1
Group by chat_id but only return results that contain a row with member_id=1 and member_id=2; no more, no less.
In the case of the tables above, only the chat_id=2 row(s) would be returned because chat_id=1 contains a 3rd member.
Is this possible with raw SQL? I'd prefer to not loop through in php as it would take a while with a lot of chats.
Here are two different ways to get the result you are looking for:
-- using conditional aggregation
select chat_id from chat_parties
group by chat_id
having sum(case when member_id = 1 then 1 else 0 end) > 0
and sum(case when member_id = 2 then 1 else 0 end) > 0
and sum(case when member_id not in (1, 2) then 1 else 0 end) = 0
-- using a correlated subquery
select chat_id from chat_parties c1
where member_id in (1,2)
and not exists (
select 1 from chat_parties where chat_id = c1.chat_id and member_id not in (1,2)
)
group by chat_id having count(distinct member_id) = 2
Change the table names to fit your actual setup.
Using conditional COUNT
SQL Fiddle Demo
SELECT c.`id`
FROM chats c
LEFT JOIN chats_parties cp
ON c.`id`= cp.`chat_id`
GROUP BY c.`id`
HAVING COUNT(case when `member_id` = 1 then 1 END) >= 1
AND COUNT(case when `member_id` = 2 then 1 END) >= 1
AND COUNT(DISTINCT `member_id` ) = 2
I've got a query that produces the right result, its just very slow. I feel like there must be a better way (perhaps without subqueries).
Table, result and query are below. I've anonymized the data and I have 8 subqueries rather than 2, but the format is the same.
Table "a":
id userId type amount
------------------------------------
1 1 a 400
2 1 b 300
3 1 c 230
4 2 a 600
5 2 b 500
6 2 c 430
I've got an index on each column and one additional one that encompasses the userId and type columns. I can also guarantee you that userId and type are unique (i.e. there would't be two type 'a' for user 1).
Desired Result:
userId typeAtotal typeBtotal
--------------------------------
1 400 300
2 600 500
My Query:
SELECT userId,
(SELECT amount
FROM a AS a2
WHERE a2.userId = a1.userId
AND a2.type = 'a') AS aAmt,
(SELECT amount
FROM a AS a3
WHERE a3.userId = a1.userId
AND a3.type = 'b') AS bAmt
FROM a AS a1
WHERE type IN ('a','b')
GROUP BY userId
Use:
SELECT t.userid,
MAX(CASE WHEN t.type = 'a' THEN amount ELSE NULL END) AS typeAtotal,
MAX(CASE WHEN t.type = 'b' THEN amount ELSE NULL END) AS typeBtotal
FROM YOUR_TABLE t
GROUP BY t.userid
If there can be more than one amount for either type - this will return the highest. If you want such situations added, use SUM:
SELECT t.userid,
SUM(CASE WHEN t.type = 'a' THEN amount ELSE NULL END) AS typeAtotal,
SUM(CASE WHEN t.type = 'b' THEN amount ELSE NULL END) AS typeBtotal
FROM YOUR_TABLE t
GROUP BY t.userid
Looks like cross-tabulation to me. You might try something like this:
SELECT userId,
SUM(IF(a.type = 'a'), a.amount, 0) AS aAmount,
SUM(IF(a.type = 'b'), a.amount, 0) AS bAmount
FROM a
WHERE type IN ('a', 'b')
GROUP BY a.userId
You might want to read this rather well-written tutorial: http://dev.mysql.com/tech-resources/articles/wizard/index.html
Edit: fixed the ELSE condition.