Slow running query, is there a better way? - mysql

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.

Related

How to convert this table into one row table?

I have a very simple static table:
type qty
A 10
B 20
C 30
I'd like to convert this table into one row result. I use this query:
SELECT type,
CASE WHEN type = 'A' THEN qty END AS 'A',
CASE WHEN type = 'B' THEN qty END AS 'B',
CASE WHEN type = 'C' THEN qty END AS 'C'
FROM mytable
GROUP BY type
The result is:
type A B C
A 10 NULL NULL
B NULL 20 NULL
C NULL NULL 30
my desired output:
A B C
10 20 30
You need to have one more column to transpose this using max() or sum() and group by. If you are grouping by Type, we get separate sets for each of 'A', 'B' and 'C', so we see separate rows.
example:
id Type Quantity
1 A 10
1 B 20
1 C 30
The query below would work with both MySQL 5.7(ONLY_FULL_GROUP_BY mode) and 5.6 as well. (Tested with v5.7.18 default settings.)
SELECT
MAX(CASE
WHEN Type = 'A' THEN qty
ELSE NULL
END) AS A,
MAX(CASE
WHEN Type = 'B' THEN qty
ELSE NULL
END) AS B,
MAX(CASE
WHEN Type = 'C' THEN qty
ELSE NULL
END) AS C
FROM
myTable
GROUP BY id;
where as this one fails with MySQL 5.7(ONLY_FULL_GROUP_BY mode)
Select id, Type from MyTable group by id;

SQL Select where user1 has more than one and user2 has none

Here's my Query to find id's for userid 2, I want to run a query that finds entries where userid=2 and amount>1 AND userid 1 has none of that id
SELECT id, amount FROM collection WHERE userid='2' AND amount>1
I'm not sure how to do an if statement inside a SQL query, but there has to be a way to easily do this.
Any help would be appreciated
If I understand right, you want The list of users having user ID = 2 and amount > 1. This list should ignore the records where ID is not in user ID = 1
Sample Input/Ouput:
ID UserID Amount Returned?
1 2 0 No (Amount 0)
2 2 10 Yes
3 1 10
3 2 5 (No, since ID =3 exists with Userid = 1)
Below Query should help you with it.
SELECT C.ID, C.AMOUNT
FROM COLLECTION C
WHERE C.USERID = 2 AND C.AMOUNT > 1
AND C.ID NOT IN
( SELECT D.ID
FROM COLLECTION D
WHERE D.USERID = 1
);
Fiddle here
"where userid=2 and amount>1 AND userid 1 has none of that id"
You can use NOT EXISTS:
SELECT id, amount
FROM collection c
WHERE c.userid = '2'
AND c.amount > 1
AND NOT EXISTS
(
SELECT 1 FROM collection c2
WHERE c2.userid = '1'
AND c.id = c2.id
)
SELECT id, amount
FROM collection
WHERE userid='2' AND amount > 1 AND
NOT id in (SELECT id FROM collection WHERE userid='1' AND amount > 1)
I'm not sure this is how your database is working, but this is how I would've solved it.

MySQL subtract two count columns

I have a table like this:
client msg_type msg_body id
------ -------- -------- ---
123 typeA success abc
123 typeB success abc
456 typeA success abc
456 typeB failure abc
123 typeA success abc
123 typeA success abc
789 typeA success def
789 typeB success def
etc.
I would like output like this:
client diff id
------ ---- ---
123 2 abc
456 1 abc
789 0 def
where diff is the count of typeA:success messages - typeB:success messages. I can get the count of the typeA success using something like:
select client, count(*) from mytable
where msg_type="typeA" and msg_body="success"
However, I can't figure out how to put another count in there (for typeB) and also subtract.
I tried something like:
select client, count(*) from mytable
where msg_type="typeA" and msg_body="success" - count(*)
from mytable where msg_type="typeB" and msg_body="success"
But of course it didn't work, or I wouldn't be asking here. :) Any advice?
Edit: added another column. I tried the two suggestions given, but it only seems to return the results for one of the ids, not both.
Edit #2: I tried wrapping the SELECT query with:
select id, count(*) from (select ...) as anothertable where count_a_minus_count_b = 0;
I was hoping the output would be like:
id count
--- -----
abc 2
def 1
where count is the number of clients where the difference between typeA:success and typeB:success is 0.
COUNT counts non-null values, so you can construct an expression that's non-null when msg_type = 'typeA', and an expression that's non-null when msg_type = 'typeB'. For example:
SELECT client,
COUNT(CASE WHEN msg_type = 'typeA' THEN 1 END) AS count_a,
COUNT(CASE WHEN msg_type = 'typeB' THEN 1 END) AS count_b,
COUNT(CASE WHEN msg_type = 'typeA' THEN 1 END)
- COUNT(CASE WHEN msg_type = 'typeB' THEN 1 END) AS count_a_minus_count_b
FROM mytable
WHERE msg_body = 'success'
GROUP
BY client
;
(Disclaimer: not tested.)
Another way:
SELECT
d.client, COALESCE(a.cnt, 0) - COALESCE(b.cnt, 0) AS diff, d.id
FROM
( SELECT DISTINCT client, id
FROM mytable
) AS d
LEFT JOIN
( SELECT client, COUNT(*) AS cnt, id
FROM mytable
WHERE msg_type = 'typeA'
AND msg_body = 'success'
GROUP BY client, id
) AS a
ON d.client = a.client
AND d.id = a.id
LEFT JOIN
( SELECT client, COUNT(*) AS cnt, id
FROM mytable
WHERE msg_type = 'typeB'
AND msg_body = 'success'
GROUP BY client, id
) AS b
ON d.client = b.client
AND d.id = b.id ;
Tested at SQL-Fiddle
Here you go:
select client,
(sum(case when msg_type='typeA' and msg_body='success' then 1 else 0 end) -
sum(case when msg_type='typeB' and msg_body='success' then 1 else 0 end)) as diff
from your_table
group by client
Here's one way to get the result:
SELECT t.client
, SUM(t.msg_type<=>'typeA' AND t.msg_body<=>'success')
- SUM(t.msg_type<=>'typeB' AND t.msg_body<=>'success') AS diff
FROM mytable t
GROUP BY t.client
(The expressions in this query are MySQL specific; for a more portable query, use a less concise CASE expression to obtain an equivalent result.)
As more terse and obfuscated alternative to return the same result:
SELECT t.client
, SUM((t.msg_body<=>'success')*((t.msg_type<=>'typeA')+(t.msg_type<=>'typeB')*-1)) AS diff
FROM mytable t
GROUP BY t.client

Return Yes if only one record is found

I have a table that has this structure:
table
id | site_id
-------------------
240 | 1
240 | 2
240 | 3
320 | 1
320 | 2
421 | 1
520 | 2
-------------------
300k records
Now i am trying to write a query to return a yes or a no for each record (id).
For example if the records with id 240 only have a site_id 1 then return 'Yes', if it has 2, 3 and so on return 'No'
I am not sure how to approach it but here is a result sample:
result_table
.-----------------------.
| id | result |
|-----------------------|
| 240 | No | -- has a site_id 1, 2 and 3
| 320 | No | -- has a site_id 1 and 2
| 421 | Yes | -- has a site_id 1 only
| 520 | No | -- has a site_id 2
'-----------------------'
Here is the query i have so far, but it seems to be incorrect
SELECT CASE WHEN count(id) > 1 THEN 'N' ELSE 'Y' END as Result
FROM table sm
WHERE sm.site_id IN (1)
AND sm.site_id NOT IN (2,3,4,5,6,7,8,9)
AND id = 240
UPDATE
SO Here is my full query, i added the answer from #gordon
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isTjoos, -- y or no
(select (case when count(site_id) = 2 then 'Yes' else 'No' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
group by merchant_id) as isUCOnly
-- isdlkonly -- y or no
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
I interpreted this question as you want ids that have only one value for site_id. I took the example in the question to be an example, with site_id = 1. To do this:
You want to use count(distinct):
select id, (case when count(distinct site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
A slightly more efficient version is to use min() and max(), assuming that the site_id is never NULL:
select id, (case when min(site_id) = max(site_id) then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
This is because min and max generally require a bit less processing than count(distinct).
If you want to check that the site_id is "1" and never anything else, then add the condition and min(site_id) = 1 to the when clause.
If you want to check that the site_id is 1 and there is exactly one row, then you can do:
select id, (case when count(site_id) = 1 and min(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
And, if you want to check that there is exactly one row:
select id, (case when count(site_id) = 1 then 'Yes' else 'No' end) as result
from site_merchant sm
group by id
SELECT
it,
CASE WHEN COUNT(CASE WHEN site_id THEN 1 END)=1
AND COUNT(CASE WHEN site_id!=1 THEN 1 END)=0 THEN 'Yes'
ELSE 'No'
END
FROM sm
GROUP BY it
Please see fiddle here.
SELECT ID, CASE WHEN EXTRA > 1 THEN 'No' ELSE 'Yes' END AS Result
FROM
(SELECT ID, Sum(site_id) AS Extra
from myTable
GROUP BY ID
) AS Test
EDIT: I suppose this should work in MySQL. I haven't worked on it though.
The idea is to SUM up the site_id. For records with only site_id = 1, the sum will be 1.
Your query seems overcomplicated. Just to start, why the IN(1) and NOT IN(2,3...9)? And why limit to a single ID (AND id = 240) when your "result sample" clearly doesn't want that? It does not make any sense. How about this?
SELECT CASE WHEN count(merchant_id) > 1 THEN 'N' ELSE 'Y' END as isTjoos
FROM site_merchant
GROUP BY site_id;
I would use a Having Count Statement. Something like that:
SELECT site_id
FROM site_merchant
HAVING (count(merchant_id) > 1)
GROUP BY site_id;
Here is the solution i found. I used #Gordons query to get started and what was missing was the site_id, and the group by was not needed:
SELECT
m.merchant_name,
m.merchant_id,
ut.realname,
a.affiliate_name,
(select (case when count(*)>0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 1) as isTjoos,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 2) as isUCOnly,
(select (case when count(*)> 0 then 'No' else 'Yes' end) as result
from site_merchant sm
WHERE sm.merchant_id = m.merchant_id
AND site_id != 3) as isDLKonly
FROM merchant m
LEFT JOIN merchant_editor_assignment mea ON mea.merchant_id = m.merchant_id
LEFT JOIN user_table ut ON ut.user_id = mea.user_id
LEFT JOIN affiliate a ON a.affiliate_id = m.affiliate_id_default
Thank you for the help.

Count by ratio and then group by MSQL

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