MYSQL SELECT BY GROUP - mysql

I have a table in which I need to query 9 returns, each of which need to be of a random order each time, they cannot duplicate except for one value of TYPE X. The problem is that if using GROUP BY, it only returns 8 results, but I require 9, with 2 results having the same TYPE value.
The draft query I have been using so far has been:
"SELECT * FROM (SELECT * FROM questions ORDER BY RAND() ) R GROUP BY type;
// TABLE REMOVED, FORMATTING NOT WORKING, IMAGE INSTEAD

If you want to fetch random rows from groups in MySQL, I think the safest way is to use variables. This is the ordering:
select q.*
from (select q.*,
(#rn := if(#q = questionid, #rn + 1,
if(#q := questionid, 1, 1)
)
) as rn
from questions q cross join
(select #q := 0, #rn := 0) params
order by questionid, rand()
) q
Then, if you know you have 8 questions but you want 2 from the second:
where rn = 1 or (questionid = 2 and rn = 2)
Or, if you want 9 random questions as non-overlapping as possible:
order by rn
limit 9

Related

MySQL SUM TOP 2 RECORDS FOR EACH CATEGORY

I have a table for students scores, am trying to sum top 2 marks for all student for a particular category.I have search for similar post but have not gotten correct answer
I have tried summing the marks but am only getting result for two students instead of all students and it does not give me correct value.
SELECT SUM(marks) as totalmarks,stdid
FROM (( select marks,stdid
from finalresult
where `subjectcategory` = 1
AND `classId`='3' AND `year`='2018'
AND `term`='2' AND `type`='23'
order by marks desc
LIMIT 2 ))t1
GROUP BY stdid
An auxiliary subquery might be used for iteration
SELECT
stdid, marks
FROM
(
SELECT stdid, marks,
#rn := IF(#iter = stdid, #rn + 1, 1) AS rn,
#iter := stdid
FROM finalresult
JOIN (SELECT #iter := NULL, #rn := 0) AS q_iter
WHERE `subjectcategory` = 1
AND `classId`='3'
AND `year`='2018'
AND `term`='2'
AND `type`='23'
ORDER BY stdid, marks DESC
) AS T1
WHERE rn <= 2
this solution ignores ties and takes only two for each Student ID.
Demo
In MySQL 8+, you would do:
SELECT stdid, SUM(marks) as totalmarks
FROM (SELECT fr.*,
ROW_NUMBER() OVER (PARTITION BY stdid ORDER BY marks DESC) as seqnm
FROM finalresult fr
WHERE subjectcategory = 1 AND
classId = 3 AND
year = 2018 AND
term = 2 AND
type = 23
) fr
WHERE seqnum <= 2
GROUP BY stdid;
Note that I removed the single quotes. Things that look like numbers probably are. And you should not mix type -- put the quotes back if the values really are stored as strings.
In earlier versions, probably the simplest method is to use variables, but you have to be very careful about them. MySQL does not guarantee the order of evaluation of variables in SELECT, so you cannot assign a variable in one expression and use it in another.
A complicated expression solves this. Also, it is best to sort in a subquery (the latest versions of MySQL 5+ require this):
SELECT stdid, SUM(marks) as totalmarks
FROM (SELECT fr.*,
(#rn := IF(#s = stdid, #rn + 1,
IF(#s := stdid, 1, 1)
)
) as seqnum
FROM (SELECT fr.*
FROM finalresult fr
WHERE subjectcategory = 1 AND
classId = 3 AND
year = 2018 AND
term = 2 AND
type = 23
ORDER BY stdid, marks DESC
) fr CROSS JOIN
(SELECT #s = '', #rn := 0) params
WHERE seqnum <= 2
GROUP BY stdid;

how to random select row in one table according to each row in other table?

there are 2 tables:
crash and traffic_flow.
crash table has attributes as crash_date, time, and corresponding detector ID.
traffic_flow table recorded by detectors has attributes date,time,detector_ID, auto-incrementing id and traffic flow parameters.
Now I'm willing to random select 10 rows in traffic_flow for each row in crash respectively and insert them in a new table.
The following is a trial:
select traffic_flow.id
from traffic_flow,crash
where traffic_flow.date=crash.date and traffic_flow.ID=crash.ID
order by rand()
limit 10;
but this sql statement select 10 rows in total for all crash records, not for each row in crash, so it can't meet my requirement. could you please modify the statement for me?
In MySQL, the simplest method is to use variables to enumerate the rows for each crash:
select *
from (select ct.*,
(#rn := if(#ct = id, #rn + 1,
if(#ct := id, 1, 1)
)
) as rn
from (select c.*, tf.id as tf_id
from traffic_flow tf join
crash c
on tf.date = c.date and tf.ID = c.ID
order by c.id, rand()
) ct cross join
(select #cid := -1, #rn := 0) params
)
where rn <= 10;

MySQL: Limiting result for WHERE IN list

Let's say there are millions of records in my_table.
Here is my query to extract rows with a specific name from list:
SELECT * FROM my_table WHERE Name IN ('name1','name2','name3','name4')
How do I limit the returned result per name1, name2, etc?
The following query would limit the whole result (to 100).
SELECT * FROM my_table WHERE Name IN ('name1','name2','name3','name4') LIMIT 100
I need to limit to 100 for each name.
This is a bit of a pain in MySQL, but the best method is probably variables:
select t.*
from (select t.*,
(#rn := if(#n = name, #rn + 1,
if(#n := name, 1, 1)
)
) as rn
from my_table t cross join
(select #n := '', #rn := 0) params
order by name
) t
where rn <= 100;
If you want to limit this to a subset of the names, then add the where clause to the subquery.
Note: If you want to pick certain rows -- such as the oldest or newest or biggest or tallest -- just add a second key to the order by in the subquery.
Try
SELECT * FROM my_table WHERE Name IN ('name1','name2','name3','name4') FETCH FIRST 100 ROWS ONLY

Top 20 percent by id - MySQL

I am using a modified version of a query similiar to another question here:Convert SQL Server query to MySQL
Select *
from
(
SELECT tbl.*, #counter := #counter +1 counter
FROM (select #counter:=0) initvar, tbl
Where client_id = 55
ORDER BY ordcolumn
) X
where counter >= (80/100 * #counter);
ORDER BY ordcolumn
tbl.* contains the field 'client_id' and I am attempting to get the top 20% of the records for each client_id in a single statement. Right now if I feed it a single client_id in the where statement it gives me the correct results, however if I feed it multiple client_id's it simply takes the top 20% of the combined recordset instead of doing each client_id individually.
I'm aware of how to do this in most databases, but the logic in MySQL is eluding me. I get the feeling it involves some ranking and partitioning.
Sample data is pretty straight forward.
Client_id rate
1 1
1 2
1 3
(etc to rate = 100)
2 1
2 2
2 3
(etc to rate = 100)
Actual values aren't that clean, but it works.
As an added bonus...there is also a date field associated to these records and 1 to 100 exists for this client for multiple dates. I need to grab the top 20% of records for each client_id, year(date),month(date)
You need to do the enumeration for each client:
SELECT *
FROM (SELECT tbl.*, #counter := #counter +1 counter
(#rn := if(#c = client_id, #rn + 1,
if(#c := client_id, 1, 1)
)
)
FROM (select #c := -1, #rn := 0) initvar CROSS JOIN tbl
ORDER BY client_id, ordcolumn
) t cross join
(SELECT client_id, COUNT(*) as cnt
FROM tbl
GROUP BY client_id
) tt
where rn >= (80/100 * tt.cnt);
ORDER BY ordcolumn;
Using Gordon's answer as a starting point, I think this might be closer to what you need.
SELECT t.*
, (#counter := #counter+1) AS overallRow
, (#clientRow := if(#prevClient = t.client_id, #clientRow + 1,
if(#prevClient := t.client_id, 1, 1) -- This just updates #prevClient without creating an extra field, though it makes it a little harder to read
)
) AS clientRow
-- Alteratively (for everything done in clientRow)
, #clientRow := if(#prevClient = t.client_id, #clientRow + 1, 1) AS clientRow
, #prevClient := t.client_id AS extraField
-- This may be more reliable as well; I not sure if the order
-- of evaluation of IF(,,) is reliable enough to guarantee
-- no side effects in the non-"alternatively" clientRow calculation.
FROM tbl AS t
INNER JOIN (
SELECT client_id, COUNT(*) AS c
FROM tbl
GROUP BY client_id
) AS cc ON tbl.client_id = cc.client_id
INNER JOIN (select #prevClient := -1, #clientRow := 0) AS initvar ON 1 = 1
WHERE t.client_id = 55
HAVING clientRow * 5 < cc.c -- You can use a HAVING without a GROUP BY in MySQL
-- (note that clientRow is derived, so you cannot use it in the `WHERE`)
ORDER BY t.client_id, t.ordcolumn
;

Limit result amount for each ID to x

I am trying to do something like this in MYSQL, but without making query multiple times (50 times, in my case) through a PHP foreach.
foreach($this->map_ids as $key => $val) {
$this->db->query("SELECT scores.profile_number, scores.score FROM scores
LEFT JOIN players ON scores.profile_number = players.profile_number
WHERE scores.map_id = {'$val'}
AND scores.profile_number IN (SELECT profile_number FROM players WHERE banned = 0) LIMIT 10");
}
This is how it looks approximately when I retrieve all scores without LIMIT.
profile score map_id
76561198026851335 2478 47455
76561198043770492 2480 47455
... ... ...
76561198043899549 1340 47452
76561198048179892 1345 47452
... ... ...
I want only 10 entries (scores) from each unique map_id.
This is surprisingly difficult to do but I've ended up using user variables to do the job, check out the following demo. Obviously my data structure is much simplified but it should be enough to get you going:
SQL Fiddle example
Here is the SQL for anyone who may be interested in skipping the demo (hideous, I know)
SELECT *
FROM (
SELECT profile_number, score, map_id
FROM (
SELECT
profile_number, score, map_id,
IF( #prev <> map_id, #rownum := 1, #rownum := #rownum+1 ) AS rank,
#prev := map_id
FROM scores
JOIN (SELECT #rownum := NULL, #prev := 0) AS r
ORDER BY map_id
) AS tmp
WHERE tmp.rank <= 10
) s
JOIN players p
ON s.profile_number = p.profile_number
Basically, what is happening is this:
ORDER BY map_id
Orders your table by map_id so that all the same ones are together.
Next we assign a rownumber to each row by using the following logic:
IF( #prev <> map_id, #rownum := 1, #rownum := #rownum+1 )
If the previous row's map_id is not equal to the current row's ID, set the row number = 1, otherwise increase the rownumber by 1.
Finally, only return the rows who have a rownumber less than or equal to 10
WHERE tmp.rank <= 10
Hope that makes it a little clearer for you.
You can use the limit directive.
SELECT * FROM `your_table` LIMIT 0, 10
This will display the first 10 results from the database.
SELECT * FROM `your_table` LIMIT 5, 5
This will show records 6, 7, 8, 9, and 10