I have one table named Mydata as follows
id name type
--------------------------------------------
1 vinu 1
2 rinu 2
3 dilu 1
4 raju 2
5 manu 3
6 saju 3
7 ragu 3
8 sonu 1
9 sam 1
10 rag 1
--------------------------------------------
I want to print records with alternating type, for example:
First row with type =1
Second row with type =2
Third row with type =3
4th row type=1
5th row type=2 and so on
Required result as follows
id name type
-----------------------------------------
1 vinu 1
2 rinu 2
5 manu 3
3 dilu 1
4 raju 2
6 saju 3
8 sonu 1
7 ragu 3
9 sam 1
10 rag 1
----------------------------------------------
Sample data:
CREATE TABLE t
(`id` int, `name` varchar(4), `type` int)
;
INSERT INTO t
(`id`, `name`, `type`)
VALUES
(1, 'vinu', 1),
(2, 'rinu', 2),
(3, 'dilu', 1),
(4, 'raju', 2),
(5, 'manu', 3),
(6, 'saju', 3),
(7, 'ragu', 3),
(8, 'sonu', 1),
(9, 'sam', 1),
(10, 'rag', 1)
;
Query:
SELECT id, name, type FROM (
SELECT
t.*,
#rn := IF(#prev_type = type, #rn + 1, 1) AS rownumber,
#prev_type := type
FROM
t
, (SELECT #rn := 0, #prev_type := NULL) var_init_subquery
ORDER BY type
) sq
ORDER BY rownumber, type
Result:
| id | name | type |
|----|------|------|
| 1 | vinu | 1 |
| 4 | raju | 2 |
| 5 | manu | 3 |
| 9 | sam | 1 |
| 2 | rinu | 2 |
| 7 | ragu | 3 |
| 8 | sonu | 1 |
| 6 | saju | 3 |
| 10 | rag | 1 |
| 3 | dilu | 1 |
see it working live in an sqlfiddle
Caveat:
Don't expect this to be performant when you have lots of data. It's doing a full table scan.
Here's a manual entry to read when you're interested about how this variables work.
This cannot be done via a raw SQL query. Extract the rows you need to display, and then sort them via your application.
Alternatively... you could write a stored procedure, but I don't recommend this. You will need a temporary table and a cursor (that transparently creates another temporary table). Too much for a query that should be executed often.
Related
I got this table:
id | score
1 | 1
2 | 4
3 | 4
4 | 3
5 | 2
6 | 2
7 | 1
8 | 4
9 | 2
10 | 3
I need to order it by score desc:
id | score
2 | 4
3 | 4
8 | 4
4 | 3
10 | 3
5 | 2
6 | 2
9 | 2
1 | 1
7 | 1
and get first 3 rows which starts with id 6
So the result should be:
6 | 2
9 | 2
1 | 1
Is this possible? Thanks in advance
I would approach this with a cumulative sum() (available in MySQL 8.0):
select
id,
score
from mytable
order by
sum(id = 6) over(order by score desc, id) desc,
score desc,
id
limit 3
The sum() orders record in the required direction; as soon as the record that has id = 6 is met, the sum takes value 1. It allows to put these records on top. The rest is just adding the additional sorting criteria and limiting the number of results.
Demo on DB Fiddle:
| id | score |
| --- | ----- |
| 6 | 2 |
| 9 | 2 |
| 1 | 1 |
In earlier versions of mysql, you can emulate the window sum with a user variable, as follows:
select
id,
score
from
(select #sm := 0) s
cross join (select id, score from mytable order by score desc, id) t
order by
case when id = 6 then #sm := #sm + 1 end desc,
score desc,
id
limit 3
Demo on DB Fiddle: same results
With this:
select t.*
from tablename t cross join (select * from tablename where id = 6) c
where t.score < c.score or (t.score = c.score and t.id >= c.id)
order by t.score desc, t.id
limit 3
See the demo.
Results:
| id | score |
| --- | ----- |
| 6 | 2 |
| 9 | 2 |
| 1 | 1 |
With this table
CREATE TABLE table3
(`id` int, `score` int)
;
INSERT INTO table3
(`id`, `score`)
VALUES
(1, 1),
(2, 4),
(3, 4),
(4, 3),
(5, 2),
(6, 2),
(7, 1),
(8, 4),
(9, 2),
(10, 3)
;
And this select
SELECT `id`, `score`
FROM (SELECT `id`,`score`,if (id = 8,#scoreid:= #scoreid +1,#scoreid) scoreid
From table3, (SELECT #scoreid :=0) s Order by score desc) t1
Where scoreid > 0 LIMIT 3;
you get
id score
8 4
4 3
10 3
DBFiddle example https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=95e2051d560c2ac27fdcc8f9d04acf5d
In MYSQL there are 2 columns in table column A and column B and if in column A continuously comes one 10th time and in column B 11th time comes true(B can be 1 or 0 between these 10 times ) so I want that column id of B.
+----+---+---+
| id | A | B |
+----+---+---+
| 1 | 1 | 0 |
| 2 | 0 | 1 |
| 3 | 1 | 0 |
| 4 | 1 | 0 |
| 5 | 1 | 0 |
| 6 | 1 | 1 |
| 7 | 1 | 0 |
| 8 | 1 | 1 |
| 9 | 1 | 1 |
| 10 | 1 | 0 |
| 11 | 1 | 1 |
| 12 | 1 | 0 |
| 13 | 0 | 1 |
+----+---+---+
I need this (column B id) (Where Column A continuously come 1 (10 times) and Column B (11th id after contenious 10 time 1 in column A )
You could use a running total in a sub query to help you with this on versions prior to mysql 8.0
drop table if exists t;
create table t
(id int,A int,B int);
insert into t values
(1, 1 ,0),
(2, 0 ,1),
(3, 1 ,0),
(4, 1 ,0),
(5, 1 ,0),
(6, 1 ,1),
(7, 1 ,0),
(8, 1 ,1),
(9, 1 ,1),
(10, 1 ,0),
(11, 1 ,1),
(12, 1 ,0),
(13, 1 ,1),
(14, 1 ,1),
(15, 1 ,1),
(16, 0 ,1);
select t1.id,t1.a,t1.b
from
(
select t.*,
if(t.a = 1, #rt:=#rt+1,#rt:=0) rt
from t
cross join (select #rt:=0) r
order by t.id
) t1
where t1.rt >= 10;
+------+------+------+
| id | a | b |
+------+------+------+
| 12 | 1 | 0 |
| 13 | 1 | 1 |
| 14 | 1 | 1 |
| 15 | 1 | 1 |
+------+------+------+
4 rows in set (0.00 sec)
This is a complicated one. Using window functions available in MySQL 8.0, I would proceed in 3 steps:
first compute row numbers in the overall group and in groups of A values
then do a cumulative sum of A values within groups of consecutive A values (using the difference between the 2 above groups), while using LEAD() to recover the id and B value of the next record
finally, filter on the record whose cumulative SUM is 10 and whose next B value is 1; the id of the next record is what you are looking for
Query:
SELECT leadID id
FROM (
SELECT
id,
SUM(A) OVER(PARTITION BY rn1 - rn2 ORDER BY id) sm,
LEAD(id) OVER(ORDER BY id) leadID,
LEAD(B) OVER(ORDER BY id) leadB
FROM (
SELECT
id,
A,
B,
ROW_NUMBER() OVER(ORDER BY id) rn1,
ROW_NUMBER() OVER(PARTITION BY A ORDER BY id) rn2
FROM mytable
) x
) x
WHERE sm = 10 AND leadB = 1
This demo on DB Fiddle with your sample data yields:
| id |
| --- |
| 13 |
Is there a way to make two or more same values in the same column to be different by database engine?
Imagine that I have column like this:
id | votes
----------
1 | 20
2 | 20
3 | 19
4 | 16
5 | 15
I have to make all the votes different in that way that i increment first of two equal values, and substract other one.
After one iteration my DB should look like this:
----------
1 | 21
2 | 19
3 | 19
4 | 16
5 | 15
Because we still have two same values (id 2 and 3) we keep going with second iteration:
----------
1 | 21
2 | 20
3 | 18
4 | 16
5 | 15
Can i update mysql database somehow to make that for me? Or should i select values, compare them in php and update?
Thank you!
This does not exactly match your expected result, but may lead you toward an acceptable compromise.
MySQL does not yet have window functions such as rank(), dense_rank() or row_number() but you can mimic row_number using #variables such as seen below.
See this demo
MySQL 5.6 Schema Setup:
CREATE TABLE Table1
(`id` int, `votes` int)
;
INSERT INTO Table1
(`id`, `votes`)
VALUES
(1, 20),
(2, 20),
(3, 19),
(4, 16),
(5, 15)
;
Query 1:
SELECT
t.id
, t.votes
, m.v - #row_num AS adjusted_votes
, #row_num := #row_num+1 AS RowNumber
FROM Table1 t
CROSS JOIN (SELECT MAX(votes) v from Table1) m
CROSS JOIN (SELECT #row_num :=0) vars
ORDER BY
t.votes DESC
, t.id
Results:
| id | votes | adjusted_votes | RowNumber |
|----|-------|----------------|-----------|
| 1 | 20 | 20 | 1 |
| 2 | 20 | 19 | 2 |
| 3 | 19 | 18 | 3 |
| 4 | 16 | 17 | 4 |
| 5 | 15 | 16 | 5 |
Note that the reduction of 20 to 19 for id 2 appears to be completely arbitrary and that id is used purely as a tie-breaker when values are equal.
I have a table called 'winners' with columns 'id', 'category', 'score', 'rank'.
I need to update my table and asign a rank within the subcategories (category_id) which as per the sample is 2 but could be more than that in the future.
Most anwers I've found are based around select statements which simply tends to just output the table view but I did find a very good 'Update' answer (https://stackoverflow.com/a/2727239/4560380) specifically the answer update where ties are required to share the same rank.
Sample
CREATE TABLE winners (
id int,
category_id int,
score double,
rank int
);
INSERT INTO winners VALUES
(1, 1, 4.36, NULL),
(2, 1, 2.35, NULL),
(3, 1, 1.25, NULL),
(4, 2, 4.21, NULL),
(5, 2, 3.33, NULL),
(6, 1, 4.24, NULL),
(7, 1, 1.22, NULL),
(8, 1, 1.25, NULL),
(9, 2, 4.21, NULL),
(10, 2, 3.63, NULL);
+----------+-------------+-------+------+
| id | category_id | score | rank |
+----------+-------------+-------+------+
| 1 | 1 | 4.36 | |
| 2 | 1 | 2.35 | |
| 3 | 1 | 1.25 | |
| 4 | 2 | 4.21 | |
| 5 | 2 | 3.33 | |
| 6 | 1 | 4.24 | |
| 7 | 1 | 1.22 | |
| 8 | 1 | 1.25 | |
| 9 | 2 | 4.21 | |
| 10 | 2 | 3.63 | |
+----------+-------------+-------+------+
The linked answer above works perfectly for the data when there is only one category to worry about but not when there are multiple categories or subgroups to rank within.
I had attempted to add in a where clause to the code (line 8)
1. UPDATE winners
2. JOIN (SELECT w.score,
3. IF(#lastPoint <> w.score,
4. #curRank := #curRank + 1,
5. #curRank) AS rank,
6. #lastPoint := w.rating
7. FROM winners w
8. WHERE category_id = 1
9. JOIN (SELECT #curRank := 0, #lastPoint := 0) r
10. ORDER BY w.score DESC
11. ) ranks ON (ranks.score = winners.score)
12. SET winners.rank = ranks.rank;
with the intention of attempting to run the code twice for each category_id but the script fails.
Any options on modifying the answer above for multiple categories would be fantastic.
Needed result just to clarify (ranked within categories).
+----------+-------------+-------+------+
| id | category_id | score | rank |
+----------+-------------+-------+------+
| 1 | 1 | 4.36 | 1 |
| 6 | 1 | 4.24 | 2 |
| 2 | 1 | 2.35 | 3 |
| 8 | 1 | 1.25 | 4 |
| 3 | 1 | 1.25 | 4 |
| 7 | 1 | 1.22 | 5 |
| 4 | 2 | 4.21 | 1 |
| 9 | 2 | 4.21 | 1 |
| 10 | 2 | 3.63 | 2 |
| 5 | 2 | 3.33 | 3 |
+----------+-------------+-------+------+
Thanks Guys!
UPDATE
Managed to find another bit of code https://stackoverflow.com/a/13270603/4560380 that I had somehow originally missed and was able to modifiy it with the where clause for each category_id succesfully. Not an ideal way of doing it - running multiple times for multiple categories but at this point in time it is fine.
set #currentRank = 0,
#lastRating = null,
#rowNumber = 1;
update winners r
inner join (
select
score,
#currentRank := if(#lastRating = score, #currentRank, #rowNumber) rank,
#rowNumber := #rowNumber + if(#lastRating = score, 0, 1) rowNumber,
#lastRating := score
from winners
where category_id = 1
order by score desc
) var on var.score = r.score
set r.rank = var.rank
further answers for a more 'automatic' handling of multiple categories within the ranking in 1 script run are still very welcome and appreciated.
Thanks
UPDATE
Just noticed that the answer that I found doesn't deal with zero scores (0.00) very well and places them ranked in the middle of other scores.
shawnt00 answer below is working and evaluates zero scores correctly.
https://stackoverflow.com/a/34667112/4560380
update winners
set rank = (
select count(score) + 1
from winners w2
where w2.category_id = winners.category_id and w2.score > winners.score
)
count(*) will evaluate to zero even when no rows match the condition, i.e., the top ranking. If you wanted a dense rank you could do this:
update winners
set rank = (
select count(distinct score) + 1
from winners w2
where w2.category_id = winners.category_id and w2.score > winners.score
)
EDIT: Per your comment I've found a query that does work. (It works on SQL Server and I'm not familiar with MySQL's quirks.) http://sqlfiddle.com/#!9/1159f/1
update winners
inner join (
select w.id, w.category_id, count(w2.score) + 1 rank
from winners w left outer join winners w2
on w2.category_id = w.category_id and w2.score > w.score
group by w.id
) r
on r.id = winners.id
set winners.rank = r.rank
Performing a WITH ROLLUP when grouping by multiple fields, MySQL returns a rollup row for each group, as well as the overall summary:
CREATE TABLE test (name VARCHAR(50), number TINYINT);
INSERT INTO test VALUES
('foo', 1), ('foo', 1), ('foo', 2), ('foo', 3), ('foo', 3),
('bar', 1), ('bar', 2), ('bar', 2), ('bar', 2), ('bar', 3),
('baz', 1), ('baz', 2), ('bar', 2);
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
+------+--------+----------+
| name | number | count(1) |
+------+--------+----------+
| bar | 1 | 1 |
| bar | 2 | 3 |
| bar | 3 | 1 |
| bar | NULL | 5 |
| baz | 1 | 1 |
| baz | 2 | 2 |
| baz | NULL | 3 |
| foo | 1 | 2 |
| foo | 2 | 1 |
| foo | 3 | 2 |
| foo | NULL | 5 |
| NULL | NULL | 13 |
+------+--------+----------+
I'm not interested in the rollups for foo/bar/baz, only the overall summary. What's the most efficient way to achieve this?
I know I can't use HAVING due to the rollup rows being added afterwards. Is the best solution to use a nested query for this, selecting where name and number are either both NOT NULL or both NULL?
HAVING can do the trick with no subquery:
SELECT `name`, number, COUNT(1) FROM test GROUP BY `name`, number WITH ROLLUP
HAVING number IS NOT NULL OR `name` IS NULL;
This filters out the post-rollup rows except for the grand total:
name number COUNT(1)
------ ------ --------
bar 1 1
bar 2 4
bar 3 1
baz 1 1
baz 2 1
foo 1 2
foo 2 1
foo 3 2
(NULL) (NULL) 13
Try to use a subquery, e.g. -
SELECT * FROM (
SELECT name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP) t
WHERE name IS NULL OR number IS NULL
You also may want to change NULL values with appropriate texts.
SELECT COALESCE(name, 'TOTAL') as Name, number, COUNT(1) FROM test GROUP BY name, number WITH ROLLUP;
Below Name column it would Display as Total. If you have issue with number as null same can be done for that too.