SELECT COUNT() GROUP BY Set of data in IN() mysql - mysql

My table is like
Loves table (loves)
animal_id | user_id | time
1 1
2 1
1 3
1 5
2 3
3 1
Animals table(animal)
id | type | name
1 air animal 1
2 ground animal 2
3 water animal 3
4 space animal 4
5 air animal 5
6 ground animal 6
My Query
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN ($MYANIMALTYPES)
ORDER BY lp.time DESC
LIMIT 5
Now if $MYUSERID = 1 and $MYANIMALTYPES = 'air','ground'
I should get output as from above example
n_l | name
3 animal 1
2 animal 2
But for some reason i want to get combinations of
$MYANIMALTYPES = 'air','ground' ,
$MYANIMALTYPES = 'air','water' ,
$MYANIMALTYPES = 'space',
and
$MYANIMALTYPES = 'space','water
seperately in groups i have to run the above query 4 times for each $MYANIMALTYPES.
My problem is that I'm using too many queries for what i want. Is there any way to get what i want in One single query ?
UPDATE
For a simple understanding how do i combine these two queries below into one query ?
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN ('ground','air')
ORDER BY lp.time DESC
LIMIT 5
AND
( select count(*) from loves where animal_id = a.animal_id) as n_l,
a.name
FROM animal a ,loves lp
WHERE a.animal_id = lp.animal_id
AND lp.user_id = $MYUSERID
AND a.type IN IN ('air','space')
ORDER BY lp.time DESC
LIMIT 5

You could use this adaptation of your query. It adds a join to the 4 type combinations you are interested in:
SELECT ( SELECT count(*)
FROM loves
WHERE animal_id = a.animal_id) as n_l,
a.name,
trim(concat(types.type1, ' ', types.type2)) grp
FROM animal a
INNER JOIN loves lp
ON a.animal_id = lp.animal_id
INNER JOIN ( SELECT 'air' type1, 'ground' type2
UNION ALL
SELECT 'air', 'water'
UNION ALL
SELECT 'space', ''
UNION ALL
SELECT 'space', 'water' ) AS types
ON a.type IN (types.type1, types.type2)
WHERE lp.user_id = $MYUSERID
GROUP BY a.name,
trim(concat(types.type1, ' ', types.type2))
ORDER BY 3, lp.time DESC
LIMIT 15
I also set the limit higher as you will now get all the results in one query.
Here is a fiddle.

The problem is you cant use string directly
if you string is
$MYANIMALTYPES = "'air','ground'"
That translate into
a.type IN ('air,ground');
and you need
a.type IN ('air', 'ground');
Try with FIND_IN_SET
WHERE FIND_IN_SET(a.type, #MYANIMALTYPES) > 0

Related

change the mysql order of the result with union

So i get 10 results from my first select and 1 from the other one after union like this:
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT a.*,
b.*
FROM all a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
i would like to get the result of the second select as the second last result like this
********
name_id 100
name_id 99
name_id 98
name_id 97
name_id 96
name_id 95
name_id 94
name_id 93
name_id 92
name_id 1 <- second select result as second last result
name_id 91
********
Can someone help pls?
Synthesize a row number column for the query as it stands and shuffle positions as needed.
SELECT x.name
, x.name_id
FROM (
SELECT #rownum:=#rownum + 1 as row_number,
t.name,
t.name_id
FROM (
-- original query from the question starts here
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY name_id DESC
LIMIT 10)
UNION
(SELECT b.name,
a.name_id
FROM allx a,
names b
WHERE b.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1)
) t,
(SELECT #rownum := 0) r
) x
ORDER BY CASE row_number
WHEN 10 THEN 11
WHEN 11 THEN 10
ELSE row_number
END
;
(Note that the query has been sightly modified to avoid syntax errors / support the demo: table all has been named allx, explicit projections of the union's subqueries).
That gets complicated quickly thus next to ad hoc reporting it is preferable to synthesize an attribute in the subqueries of the union that reflects a global order.
Demo here (SQL fiddle)
Credits
Row number synthesizing taken from this SO answer
Interesting question given
+----+--------+
| id | sname |
+----+--------+
| 1 | sname1 |
| 2 | sname2 |
| 3 | sname3 |
| 4 | sname4 |
| 5 | sname5 |
| 6 | sname6 |
+----+--------+
6 rows in set (0.001 sec)
(select id,sname,#r:=#r+1 rn
from users
cross join(select #r:=0) r
order by sname desc limit 3
)
union
(
select u.id,u.sname,
#r:=#r - .9
from users u
left join (select id from users order by sname desc limit 3) u1 on u1.id = u.id
where u1.id is null
order by u.id asc limit 0,1
)
order by rn;
Where a variable is used to calculate a row number in the first sub query, since this variable is not reset in the second query a simple piece of arithmetic works out where to position the second sub query result. Note the second sub query uses a left join to check that the result has not already appeared in the first sub query,
I would suggest union all and three selects:
SELECT an.*
FROM ((SELECT a.*, n.*, 1 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9
) UNION ALL
(SELECT a.*, n.*, 3 as ord
FROM all a JOIN
names n
ON n.name_id = a.name_id
ORDER BY n.name_id DESC
LIMIT 9 OFFSET 9
) UNION ALL
(SELECT a.*, b.*
FROM all a JOIN
names n
WHERE n.name_id = a.name_id
ORDER BY request_id ASC
LIMIT 1
)
) an
ORDER BY ord, name_id;

Mysql group_concat for count in sub query using parent filed is not allowed

I need to process album count for each of the country per artist; however, I have a problem once I do group_concat for count in mysql, I search a bit in stackoverflow, I found I have to do sub select for group_concat. The problem is once I do the sub select in from I can not use a.id from the parent from filed table. I got error like following Unknown column 'a.id' in 'where clause'
This is the query:
SELECT a.seq_id, a.id
(SELECT GROUP_CONCAT(cnt) AS cnt FROM (
SELECT CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.media_id
WHERE ma.artist_id = a.id
GROUP BY mgr.country_code
) count_table
) AS album_count
FROM music_artist AS a
WHERE a.seq_id > 0 and a.seq_id < 10000
The sample data in tables:
music_artists:
seq_id id name
1 1 Hola
2 2 Vivi
music_album_artists:
id artist_id album_id
1 1 1
2 1 2
3 1 5
4 1 10
5 2 2
6 2 10
6 2 1
media_geo_restrict:
album_id country_code
1 BE
1 CA
1 DE
1 US
2 CH
2 CA
2 CH
5 DE
10 US
The result I would like to have
seq_id id album_count
1 1 BE--1,CA--2,CH--1,DE--1,US--1
2 2 CA--1,US--2,CH--1
Here is what you need:
select seq_id, id, group_concat(concat(country_code, '--', qtd))
from (
select ma.seq_id, ma.id,
mgr.country_code, count(*) qtd
from music_artists ma
inner join music_album_artists maa
on ma.id = maa.artist_id
inner join media_geo_restrict mgr
on maa.album_id = mgr.album_id
where ma.seq_id > 0 and ma.seq_id < 10000
group by ma.seq_id, ma.id, ma.name,
mgr.country_code
) tb
group by seq_id, id
Here is the working sample: http://sqlfiddle.com/#!9/ff8b5/8
Try this and tell me:
SELECT a.seq_id, a.id, GROUP_CONCAT(cnt) AS cnt
FROM music_artist AS a,
(
SELECT ma.artist_id, CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.album_id
GROUP BY mgr.country_code
) AS count_table
WHERE a.seq_id > 0 and a.seq_id < 10000
and a.id=count_table.artist_id
group by a.id

Struggling with MySQL subquery

I have two tables, image and gradeReason. Each image is awarded a grade for it's quality and the user can select up 4 different reasons (reasonID_1, reasonID_2, reasonID_3, reasonID_4) by using selecting a reasonID. The breakdown of the reason is stored in the gradeReason table.
image
imageID auditID reasonID_1 reasonID_2 reasonID_3 reasonID_4
------- ------- ---------- ---------- ---------- ----------
1 123 1 13 7 3
2 124 8 13 8 6
4 125 3 2 5 6
5 125 7 4 2 3
gradeReason
reasonID category name
-------- -------- ----
1 exposure overexposed
2 exposure underexposed
3 patient patient moved
4 equipment sensor too big
5 equipment sensor too small
What I would like is a query that will return the number of times each reasonID has been used in an audit and what the name of gradeReason was
e.g.
audit 125 -
reasonID 3 was used twice - name 'patient moved',
reasonID 2 used twice - name 'underexposed'.
I'll be honest and say I have struggled with this for days and I can't even think where to begin.
This is harder because your data is not properly normalized. The following approach first normalizes the data, then does the join and aggregation:
select ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
from (select i.imageID, i.auditID,
(case when n.n = 1 then ReasonID_1
when n.n = 2 then ReasonID_2
when n.n = 3 then ReasonID_3
when n.n = 4 then ReasonID_4
end) as ReasonId
from image i cross join
(select 1 as n union all select 2 union all select 3 union all select 4
) n
) ir join
gradeReason gr
on ir.ReasonId = gr.ReasonId
group by ir.auditId, gr.ReasonId, gr.category, gr.name
order by cnt desc;
For your select, you have to join gradeReason once for every foreign key to gradeReason:
SELECT imageID
, auditID
, r1.name
, r2.name
, r3.name
, r4.name
FROM image i
LEFT JOIN gradeReason r1 on i.reasonID_1 = r1.reasonID
LEFT JOIN gradeReason r2 on i.reasonID_2 = r2.reasonID
LEFT JOIN gradeReason r3 on i.reasonID_3 = r3.reasonID
LEFT JOIN gradeReason r4 on i.reasonID_4 = r4.reasonID
Your example data is not sensible, as the foreign keys to gradeReason in your table image is not in gradeReason, but I guess you have more gradeReasons up your sleeve.
Moreover,your structure is not normalized:
gradeReason has duplicate entries in rows for category. This is not too bad, but it would be good practice to have a separate gradeReasonCategories table.
Another option using UNIONS:-
SELECT auditId, ReasonId, category, name, SUM(cnt)
FROM
(
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_1 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_2 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_3 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
UNION ALL
SELECT ir.auditId, gr.ReasonId, gr.category, gr.name, count(*) as cnt
FROM image ir
INNER JOIN gradeReason gr
ON ir.reasonID_4 = gr.ReasonId
WHERE ir.auditId = 123
GROUP BY ir.auditId, gr.ReasonId, gr.category, gr.name
) Sub1
GROUP BY auditId, ReasonId, category, name

Join with limit on right table

Say I have two tables I want to join.
Categories:
id name
----------
1 Cars
2 Games
3 Pencils
4 Books
And items:
id categoryid itemname
---------------------------
1 1 Ford
2 1 BMW
3 1 VW
4 2 Tetris
5 2 Pong
6 3 Foobar Pencil Factory
I want a query that returns the category and the last maximum N (for example: 2) itemname:
category.id category.name item.id item.itemname
-------------------------------------------------
1 Cars 2 BMW
1 Cars 3 VW
2 Games 4 Tetris
2 Games 5 Pong
3 Pencils 6 Foobar Pencil Factory
4 Books NULL NULL
I want write a query like below:
Select * From categories c
Left Join (select * from items order by id desc) i
On c.id=i=categoryid
AND LIMIT 2 #comment: N=2 this line not supported
Where i.categoryid = c.id
Group By c.id
Thanks!
I'm not saying it's efficient, but it should work:
SELECT c.*, i.id, i.itemname
FROM categories c
LEFT JOIN
(SELECT i.*
FROM items i
LEFT JOIN items i2 ON i.categoryid = i2.categoryid AND i2.id > i.id
GROUP BY i.id
HAVING COUNT(*) < 2 # this 2 = N
) i ON c.id = i.categoryid
Check http://sqlfiddle.com/#!2/9a132/1
SELECT *
FROM Categories c
LEFT JOIN -- Items i
(
SELECT * FROM Items WHERE LOCATE(id,
(
SELECT GROUP_CONCAT(it.itemids) AS its
FROM (
SELECT (SUBSTRING_INDEX(GROUP_CONCAT(CONVERT(id, CHAR(8))
ORDER BY id DESC), ',', 2)) AS itemids
FROM Items
GROUP BY categoryid
) it
)) <> 0
) i
ON i.categoryid = c.id;
where N=2 is: SUBSTRING_INDEX(GROUP_CONCAT(CONVERT(id, CHAR(8)) ORDER BY id DESC), ',', 2)
Because GROUP_CONCAT is by default sort ASC ; above will become SUBSTRING_INDEX(GROUP_CONCAT(CONVERT(id, CHAR(8)) ), ',', -2). But GROUP_CONCAT result is truncated to max len 1024 (group_concat_max_len).

SQL: finding differences between rows

I want to count how many times each user has rows within '5' of eachother.
For example, Don - 501 and Don - 504 should be counted, while Don - 501 and Don - 1600 should not be counted.
Start:
Name value
_________ ______________
Don 1235
Don 6012
Don 6014
Don 6300
James 9000
James 9502
James 9600
Sarah 1110
Sarah 1111
Sarah 1112
Sarah 1500
Becca 0500
Becca 0508
Becca 0709
Finish:
Name difference_5
__________ _____________
Don 1
James 0
Sarah 2
Becca 0
Use the ABS() function, in conjunction with a self-join in a subquery:
So, something like:
SELECT name, COUNT(*) / 2 AS difference_5
FROM (
SELECT a.name name, ABS(a.value - b.value)
FROM tbl a JOIN tbl b USING(name)
WHERE ABS(a.value - b.value) BETWEEN 1 AND 5
) AS t GROUP BY name
edited as per Andreas' comment.
Assuming that each name -> value pair is unique, this will get you the count of times the value is within 5 per name:
SELECT a.name,
COUNT(b.name) / 2 AS difference_5
FROM tbl a
LEFT JOIN tbl b ON a.name = b.name AND
a.value <> b.value AND
ABS(a.value - b.value) <= 5
GROUP BY a.name
As you'll notice, we also have to exclude the pairs that are equal to themselves.
But if you wanted to count the number of times each name's values came within 5 of any value in the table, you can use:
SELECT a.name,
COUNT(b.name) / 2 AS difference_5
FROM tbl a
LEFT JOIN tbl b ON NOT (a.name = b.name AND a.value = b.value) AND
ABS(a.value - b.value) <= 5
GROUP BY a.name
See the SQLFiddle Demo for both solutions.
Because the OP also wants de zero counts, we'll need a self- left join. Extra logic is needed if one person has two exactly the same values, these should also be counted only once.
WITH cnts AS (
WITH pair AS (
SELECT t1.zname,t1.zvalue
FROM ztable t1
JOIN ztable t2
ON t1.zname = t2.zname
WHERE ( t1.zvalue < t2.zvalue
AND t1.zvalue >= t2.zvalue - 5 )
OR (t1.zvalue = t2.zvalue AND t1.ctid < t2.ctid)
)
SELECT DISTINCT zname
, COUNT(*) AS znumber
FROM pair
GROUP BY zname
)
, names AS (
SELECT distinct zname AS zname
FROM ztable
GROUP BY zname
)
SELECT n.zname
, COALESCE(c.znumber,0) AS znumber
FROM names n
LEFT JOIN cnts c ON n.zname = c.zname
;
RESULT:
DROP SCHEMA
CREATE SCHEMA
SET
CREATE TABLE
INSERT 0 14
zname | znumber
-------+---------
Sarah | 3
Don | 1
Becca | 0
James | 0
(4 rows)
NOTE: sorry for the CTE, I had not seen th mysql tag,I just liked the problem ;-)
SELECT
A.Name,
SUM(CASE WHEN (A.Value < B.Value) AND (A.Value >= B.Value - 5) THEN 1 ELSE 0 END) Difference_5
FROM
tbl A INNER JOIN
tbl B USING(Name)
GROUP BY
A.Name