SQL Limit to number and if equal - mysql

Can I limit rows that I displaying depending on if number in row equal to show all equal rows. For example I have database that contains football statistics, and I want to display first five scorers in one season but ib some seasons some scorers on third place have same number of goals and my query doesnt display it all beacuse i limited to sql clause limit to 5. Is there a way to tell in some clause to show all rows that are equal number of goals?
For example list of goalscorers
Goalscorer1 7 goals
Goalscorer2 6
Goalscorer3 4
Goalscorer4 3
Goalscorer5 3
...and there are goalscorers 6 and 7 with also 3 goals that are not displayed

You need to decide what is the controlling factor, limit that factor in a subquery, then list the facts related to it. Here the controlling factor is the number of goals, and the related facts are the players who kicked that number of goals.
see this SQL Fiddle
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`Player` varchar(20), `Goals` int)
;
INSERT INTO Table1
(`Player`, `Goals`)
VALUES
('Goalscorer1', 7),
('Goalscorer2', 6),
('Goalscorer3', 4),
('Goalscorer6', 3),
('Goalscorer7', 3),
('Goalscorer4', 3),
('Goalscorer5', 3),
('Goalscorer11', 1)
;
Query 1:
select
t.*
from table1 t
inner join (
select distinct Goals from table1
order by goals DESC
limit 4
) sq on t.Goals = sq.Goals
Results:
| Player | Goals |
|-------------|-------|
| Goalscorer1 | 7 |
| Goalscorer2 | 6 |
| Goalscorer3 | 4 |
| Goalscorer6 | 3 |
| Goalscorer7 | 3 |
| Goalscorer4 | 3 |
| Goalscorer5 | 3 |
My re-worked version of the pastebin query referenced below:
SELECT
t.*
FROM jos_playerstats t
INNER JOIN (
SELECT DISTINCT
jos_playerstats.goals
FROM jos_playerstats
WHERE jos_playerstats.idSeason = '".$urlID."'
ORDER BY
jos_playerstats.goals DESC
LIMIT 4
) sq ON t.goals = sq.goals
WHERE t.idSeason = '".$urlID."'
The subquery MUST ONLY contain a distinct list of goals, no other columns at all. Also: Please don't get into the habit of adding distinct into every query because performance will get worse. Only use "select distinct" when you truly need it and this should be rare and usually associated with one or very few columns.

Related

MySQL query loads forever

I have a table with 1v1 matches like this:
match_number|winner_id|loser_id
------------+---------+--------
1 | 1 | 2
2 | 2 | 3
3 | 1 | 2
4 | 1 | 4
5 | 4 | 1
and I would like to get something like this:
player|matches_won|matches_lost
------+-----------+------------
1 | 3 | 1
2 | 1 | 2
3 | 0 | 1
4 | 1 | 1
My MySQL Query looks like this
SELECT win_matches."winner_id" player, COUNT(win_matches."winner_id") matches_won, COUNT(lost_matches."loser_id") matches_lost FROM `matches` win_matches
JOIN `matches` lost_matches ON win_matches."winner_id" = lost_matches."winner_id"
I don't know what I did wrong, but the query just loads forever and doesn't return anything
You want to unpivot and then aggregate:
select player_id, sum(is_win), sum(is_loss)
from ((select winner_id as player_id 1 as is_win, 0 as is_loss
from t
) union all
(select loser_id, 0, 1
from t
)
) wl
group by player_id;
Your query is simply not correct. The two counts will produce the same same value -- COUNT(<expression>) returns the number of non-NULL rows for that expression. Your two counts return the same thing.
The reason it is taking forever is because of the Cartesian product problem. If a player has 10 wins and 10 losses, then your query produces 100 rows -- and this gets worse for players who have played more often. Processing all those additional rows takes time.
If you have a separate players table, then correlated subqueries may be the fastest method:
select p.*,
(select count(*) from t where t.winner_id = p.player_id) as num_wins,
(select count(*) from t where t.loser_id = p.player_id) as num_loses
from players p;
However, this requires two indexes for performance on (winner_id) and (loser_id). Note these are separate indexes, not a single compound index.
You are joining the same table twice.
Both the alias win_matches and lost_matches are on the table matches, causing your loop.
You probably don't need separate tables for win and losses, and could do both in the same table by writing one or zero in a column for each.
I don't to change your model too much and make it difficult to understand, so here is a slight modification and what it could look like:
SELECT m."player_id" player,
SUM(m."win") matches_won,
SUM(m."loss") matches_lost
FROM `matches` m
GROUP BY player_id
Without a join, all in the same table with win and loss columns. It looked to me like you wanted to know the number of win and loss per player, which you can do with a group by player and a sum/count.

MySQL group/order behaves differently in 5.7

I have a table that looks like this:
id | text | language_id | other_id | dateCreated
1 | something | 1 | 5 | 2015-01-02
2 | something | 1 | 5 | 2015-01-01
3 | something | 2 | 5 | 2015-01-01
4 | something | 2 | 6 | 2015-01-01
and I want to get all latest rows for each language_id that have other_id 5.
my query looks like this
SELECT * (
SELECT *
FROM tbl
WHERE other_id = 5
ORDER BY dateCreated DESC
) AS r
GROUP BY r.language_id
With MySQL 5.6 I get 2 rows with ID 1 and 3, which is what I want.
With MySQL 5.7.10 I get 2 rows with IDs 2 and 3 and it seems to me that the ORDER BY in the subquery is ignored.
Any ideas what might be the problem ?
You should go with the query below:
SELECT
*
FROM tbl
INNER JOIN
(
SELECT
other_id,
language_id,
MAX(dateCreated) max_date_created
FROM tbl
WHERE other_id = 5
GROUP BY language_id
) AS t
ON tbl.language_id = t.language_id AND tbl.other_id = t.other_id AND
tbl.dateCreated = t.max_date_created
Using GROUP BY without aggregate function will pick row in arbitrary order. You should not rely on what's row is returned by the GROUP BY. MySQL doesn't ensure this.
Quoting from this post
In a nutshell, MySQL allows omitting some columns from the GROUP BY,
for performance purposes, however this works only if the omitted
columns all have the same value (within a grouping), otherwise, the
value returned by the query are indeed indeterminate, as properly
guessed by others in this post. To be sure adding an ORDER BY clause
would not re-introduce any form of deterministic behavior.
Although not at the core of the issue, this example shows how using *
rather than an explicit enumeration of desired columns is often a bad
idea.
Excerpt from MySQL 5.0 documentation:
When using this feature, all rows in each group should have the same
values for the columns that are omitted from the GROUP BY part. The
server is free to return any value from the group, so the results are
indeterminate unless all values are the same.

sql get couple of elements

I have the following sql table:
| ID | numbers |
|----|-----------------------------|
| 1 | 1,3,19,23,28,32,39,42,60,80 |
| 2 | 1,3,18,24,29,33,40,43,61,80 |
| 3 | 1,2,3,25,30,34,41,44,62,78 |
In Numbers I have a string with 10 numbers.
I want to get all couple of two elements (and if it is possible for three, four etc) in SQL Server or MySQL.
For example for two elements:
1,3 appers in all rows (3 times)
1, 80 appears in the first and second row (2 times)
etc
I tried to split numbers from every row and insert into a temporary table and after generate combinations of 10 choose k (where k is numbers of elements in a couple) but something doesn't work. I don't know if it's the best idea.
My code in this moment: http://pastebin.com/qRjPdfay
Thanks
Yes, splitting your numbers coulmns to rows would make things easier. If you are using MySQL you could use a query like this:
CREATE TABLE mytable2 AS
SELECT
ID, SUBSTRING_INDEX(SUBSTRING_INDEX(numbers, ',', n),',',-1) AS number
FROM
mytable CROSS JOIN (SELECT 1 AS n
UNION ALL SELECT 2 AS n
UNION ALL SELECT 3 AS n
UNION ALL SELECT 4 AS n
UNION ALL SELECT 5 AS n
UNION ALL SELECT 6 AS n
UNION ALL SELECT 7 AS n
UNION ALL SELECT 8 AS n
UNION ALL SELECT 9 AS n
UNION ALL SELECT 10 AS n) d;
(this will work if all numbers contains exactly 10 numbers an no less, if there are less this query needs some improvements). Then you can count the time each number appears:
SELECT number, COUNT(*) as appears
FROM mytable2
GROUP BY number
ORDER BY appears DESC
and you can group number by the number of times they appear:
SELECT
appears, GROUP_CONCAT(number) AS numbers
FROM (
SELECT number, COUNT(*) as appears
FROM mytable2
GROUP BY number
ORDER BY appears DESC
) g
GROUP BY
appears
ORDER BY
appears DESC
(MySQL only) and the result will be like this:
| appears | numbers |
|---------|---------------|
| 3 | 3,1 |
| 2 | 80 |
| 1 | 43,23,40..... |
Please see a fiddle here.

Sorting rows in MySql based on algebra on two or more columns

I am designing a Test-System for a school.The table is like this
------------------------------------------------------------------------------
Student_id | Total Questions | Questions Attempted | Correct
------------------------------------------------------------------------------
36 | 60 | 42 | 20
19 | 60 | 38 | 32
119 | 60 | 37 | 31
Now, marking scheme is +3 for correct and -1 for wrong
Number of wrong questions will be calculated as wrong = attempted - correct
Questions
1) I want to give the give the student some points based on their ranks, so I want to sort the table on the decreasing order of their score i.e. score = 3 * correct - wrong.Though,I could have stored the score as well but since it is redundant data I don't want to store it into the table.How can I sort the table using SQL query.
2)When I will be updating the points of students based on their performance into the table student,I am picking student_id from result table and making updations into the student table i.e. 1 query per student.This means that if 4000 students sat for the test ,4000 queries !!! .Can I improve the situation (minimise queries)?
EDIT
Student schema for question 2
------------------------------------------------------------------------------
Student_id | fname | lname | contact | points
------------------------------------------------------------------------------
you can just specify what you want in the sort
select student_id,correct, attempted,field4,field5, (3 * correct - (attempted-correct)) as score
from students sort by score desc
yes, take a look at bulk update of sql, you can prepare the query and update 10 by 10 or 100 by 100, but not too much since sql command have limit on its length
Question 1.
Supposing the table is named Results, and that Student_id is unique, here is a possible solution to your question:
SELECT Results.*, (3*Correct-(Total_Questions-Correct)) AS score
FROM Results
ORDER BY score DESC
Question 2.
Supposing the Students are already added to the table Students, or that they already have a score, this is a possible SQL Query to update the students table without making the 4k queries:
UPDATE StudentsTable AS s
INNER JOIN PointsTable AS p
ON s.Student_id = p.Student_id
SET
s.Points = s.Points + (3 * p.Correct - (p.Questions_Attempted - p.Correct))
If you need to perform more tests in the future you can add a Test_ID column to you Points Table and then add a WHERE clause to the UPDATE query in order to just add up the score from a given test.
Optimization
You can optimize the queries a little bit by changing the way you calculate the score:
SELECT Results.*, (2*Correct-Total_Questions) AS score
FROM Results
ORDER BY score DESC
UPDATE StudentsTable AS s
INNER JOIN PointsTable AS p
ON s.Student_id = p.Student_id
SET
s.Points = s.Points + (2 * p.Correct - p.Questions_Attempted)
To rank students by score you can do
SELECT student_id,
(
SELECT 1 + COUNT(*)
FROM student_results
WHERE 3 * correct - (total - correct) >=
3 * r.correct - (r.total - r.correct)
AND student_id <> r.student_id
) rank
FROM student_results r
Output:
| STUDENT_ID | RANK |
|------------|------|
| 36 | 3 |
| 19 | 1 |
| 119 | 2 |
Now you can update student points in one go using multi-table UPDATE syntax instead of hitting the database with number of update queries.
UPDATE students s JOIN
(
SELECT student_id,
(
SELECT 1 + COUNT(*)
FROM student_results
WHERE 3 * correct - (total - correct) >=
3 * r.correct - (r.total - r.correct)
AND student_id <> r.student_id
) rank
FROM student_results r
) q
ON s.student_id = q.student_id
SET s.points = s.points +
CASE q.rank -- implement your logic of translating ranks to points here
WHEN 1 THEN 100
WHEN 2 THEN 50
WHEN 3 THEN 10
ELSE 0
END;
Here is SQLFiddle demo

Group by - Overriding default behaviour of deciding row under each group in result

Extending further from this question Query to find top rated article in each category -
Consider the same table -
id | category_id | rating
---+-------------+-------
1 | 1 | 10
2 | 1 | 8
3 | 2 | 7
4 | 3 | 5
5 | 3 | 2
6 | 3 | 6
There is a table articles, with fields id, rating (an integer from 1-10), and category_id (an integer representing to which category it belongs). And if I have the same goal to get the top rated articles in each query (this should be the result):-
Desired Result
id | category_id | rating
---+-------------+-------
1 | 1 | 10
3 | 2 | 7
6 | 3 | 6
Extension of original question
But, running the following query -
SELECT id, category_id, max( rating ) AS max_rating
FROM `articles`
GROUP BY category_id
results into the following where everything, except the id field, is as desired. I know how to do this with a subquery - as answered in the same question - Using subquery.
id category_id max_rating
1 1 10
3 2 7
4 3 6
In generic terms
Excluding the grouped column (category_id) and the evaluated columns (columns returning results of aggregate function like SUM(), MAX() etc. - in this case max_rating), the values returned in the other fields are simply the first row under every grouped result set (grouped by category_id in this case). E.g. the record with id =1 is the first one in the table under category_id 1 (id 1 and 2 under category_id 1) so it is returned.
I am just wondering is it not possible to somehow overcome this default behavior to return rows based on conditions? If mysql can perform calculation for every grouped result set (does MAX() counting etc) then why can't it return the row corresponding to the maximum rating. Is it not possible to do this in a single query without a subquery? This looks to me like a frequent requirement.
Update
I could not figure out what I want from Naktibalda's solution too. And just to mention again, I know how to do this using a subquery, as again answered by OMG Ponies.
Use:
SELECT x.id,
x.category_id,
x.rating
FROM YOUR_TABLE x
JOIN (SELECT t.category_id,
MAX(t.rating) AS max_rating
FROM YOUR_TABLE t
GROUP BY t.category_id) y ON y.category_id = x.category_id
AND y.max_rating = x.rating