mysql - Get two greatest values from multiple columns - mysql

We can use GREATEST to get greatest value from multiple columns like below
SELECT GREATEST(mark1,mark2,mark3,mark4,mark5) AS best_mark FROM marks
But now I want to get two best marks from all(5) marks.
Can I do this on mysql query?
Table structure (I know it is wrong - created by someone):
student_id | Name | mark1 | mark2 | mark3 | mark4 | mark5

This is not the most elegant solution but if you cannot alter the table structure then you can unpivot the data and then apply a user defined variable to get a row number for each student_id. The code will be similar to the following:
select student_id, name, col, data
from
(
SELECT student_id, name, col,
data,
#rn:=case when student_id = #prev then #rn else 0 end +1 rn,
#prev:=student_id
FROM
(
SELECT student_id, name, col,
#rn,
#prev,
CASE s.col
WHEN 'mark1' THEN mark1
WHEN 'mark2' THEN mark2
WHEN 'mark3' THEN mark3
WHEN 'mark4' THEN mark4
WHEN 'mark5' THEN mark5
END AS DATA
FROM marks
CROSS JOIN
(
SELECT 'mark1' AS col UNION ALL
SELECT 'mark2' UNION ALL
SELECT 'mark3' UNION ALL
SELECT 'mark4' UNION ALL
SELECT 'mark5'
) s
cross join (select #rn := 0, #prev:=0) c
) s
order by student_id, data desc
) d
where rn <= 2
order by student_id, data desc;
See SQL Fiddle with Demo. This will return the top 2 marks per student_id. The inner subquery is performing a similar function as using a UNION ALL to unpivot but you are not querying against the table multiple times to get the result.

I think you should change your database structure, because having that many marks horizontally (i.e. as fields/columns) already means you're doing something wrong.
Instead put all your marks in a separate table where you create a many to many relationship and then perform the necessary SELECT together with LIMIT.
Suggestions:
Create a table that you call mark_types. Columns: id, mark_type. I
see that you currently have 5 type of marks; it would be very simple
to add additional types.
Change your marks table to hold 3 columns: id,
mark/grade/value, mark_type (this column foreign constraints to
mark_types).
Write your SELECT query with the help of joins, and GROUP BY mark_type.

you can create a temporary table and then
Create a temporary table in a SELECT statement without a separate CREATE TABLE
query that table as follows
SELECT TOP 2 * FROM temp
ORDER BY mark DESC
then
drop temp table

Okay here's a new answer that's should work with the current table structure:
SELECT `student_id`, `Name`, `mark` FROM (SELECT `student_id`, `Name`, `mark1` AS `mark` FROM `marks`
UNION ALL
SELECT `student_id`, `Name`, `mark2` AS `mark` FROM `marks`
UNION ALL
SELECT `student_id`, `Name`, `mark3` AS `mark` FROM `marks`
UNION ALL
SELECT `student_id`, `Name`, `mark4` AS `mark` FROM `marks`
UNION ALL
SELECT `student_id`, `Name`, `mark5` AS `mark` FROM `marks`) AS `marks`
ORDER BY `mark` DESC
LIMIT 2

Related

MySQL select all table data, gouped by one column data

I have a table that looks like this:
serial|vehicule|alert_emails
12411|AAA|yes
12411|BBB|yes
13411|CCC|yes
13411|DDD|yes
14411|EEE|yes
I want to do a mysql query to select all data and organize it by serial field to get a array result like this:
12411
AAA|yes
BBB|yes
13411
CCC|yes
DDD|yes
14411
EEE|yes
I tried group by the field serial but I'm not getting the desired result:
SELECT * FROM mytable GROUP BY serial;
Any help please?
Thanks.
Use UNION ALL to get the distinct serials of the table and all the rows of the table:
SELECT CASE WHEN t.col IS NULL THEN t.serial END serial, t.col
FROM (
SELECT DISTINCT serial, null AS col
FROM mytable
UNION ALL
SELECT serial, CONCAT(vehicule, '|', alert_emails)
FROM mytable
) t
ORDER BY t.serial, t.col IS NULL DESC
See the demo.

Combining tables while changing id - SQL

I'm trying to create a search function in different tables using UNION and what happened is that the id's are duplicating making the search go wrong. How can I merge different tables into one while no id's are in common?
Here is the example
table1
id name desc
1 henry post
2 albert doth
3 jun cloth
table2
id name desc
1 kin revenge
2 pot eve
The result SHOULD be like this
id name desc
1 henry post
2 albert doth
3 jun cloth
4 kin revenge
5 pot eve
Please help me. Thanks.
In most databases, you would add a new id using the ANSI standard row_number() function:
select row_number() over (order by which, id) as newid, name, description
from (select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
) t;
Note that desc is a really bad name for a column, because it is a SQL keyword and usually a reserved word.
EDIT:
MySQL doesn't support this ANSI standard functionality. Instead, use variables:
select (#rn := #rn + 1) as newid, name, description
from (select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
) t cross join
(select #rn := 0) vars
order by which, id;
I've include the order by so the rows remain in the same order that you seem to want them in -- rows from the first table followed by rows from the second table. If you don't care about the order, just drop the order by.
For SQLite, the calculation is much more painful:
with cte as (
select 1 as which, t1.* from table1 t1 union all
select 2 as which, t2.* from table2 t2
)
select (select count(*)
from cte cte2
where cte2.which < cte.which or (ct2.which = cte.which and cte2.id <= cte.id
) as id,
name, description
from cte;
In MySql, you can simulate the row_number() function of Sql Server and Oracle using a mutating variable hack:
set #rownum := 0;
SELECT #rownum:=#rownum+1 AS` row_number`, `name`, `desc`
FROM
(
SELECT `name`, `desc` FROM table1
UNION
SELECT `name`, `desc` FROM table2
) AS x;
SqlFiddle
It looks like you have to Generate Id's so you can make you Union query as Sub select and generate Id's in Outer Query
MySQL does not have any system function like SQL Server’s row_number () to generate the row number for each row. However, it can be generated using the variable in the SELECT statement
SET #row_number:=0;
SELECT #row_number:=#row_number+1 As Id,
NAME,
desc
FROM (SELECT NAME,desc
FROM table1
UNION ALL
SELECT NAME,desc
FROM table2
UNION ALL
........
........) A
Order by NAME -- Change the column in Order by in which order you want to create New ID's

repeat result multiple times in mysql

I have a table having id and no field, what I really want is the result raw will be repeated no filed times, if the no field is 2 then that raw must be repeated twice in result.
this is my sample table structure:
id no
1 3
2 2
3 1
now I need to get a result like:
1 3
1 3
1 3
2 2
2 2
3 1
I tried to write mysql query to get the result like above, but failed.
You need a table of numbers to accomplish this. For just three values, this is easy:
select t.id, t.no
from t join
(select 1 as n union all select 2 union all select 3
) n
on t.no <= n.no;
This query must do what you want to achieve:
select t.id, t.no from test t cross join test y where t.id>=y.id
not completely solve your problem, but this one can help
set #i=0;
select
test_table.*
from
test_table
join
(select
#i:=#i+1 as i
from
any_table_with_number_of_rows_greater_than_max_no_of_test_table
where
#i < (select max(no) from test_table)) tmp on no >= i
order by
id desc
EDIT :
This is on SQL Server. I checked online and see that CTEs work on MySQL too. Just couldn't get them to work on SQLFiddle
Try this, remove unwanted columns
create table #temp (id int, no int)
insert into #temp values (1, 2),(2, 3),(3, 5)
select * from #temp
;with cte as
(
select id, no, no-1 nom from #temp
union all
select c.id, c.no, c.nom-1 from cte c inner join #temp t on t.id = c.id and c.nom < t.no and c.nom > 0
)
select * from cte order by 1
drop table #temp

SELECT index of single row in table

I have a MYSQL table which stores teams.
Table structure:
CREATE TABLE teams (
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(28) COLLATE utf8_unicode_ci NOT NULL,
UNIQUE KEY id (id)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;
Sample data:
INSERT INTOteamsVALUES
(1, 'one'),
(2, 'two'),
(3, 'three'),
(4, 'four'),
(5, 'five');
Use:
SELECT id, name, id as rowNumber FROM teams WHERE id = 4
Returns the correct rowNumber, as there are really three rows infront f it in the table. But this only works as long as I don't remove a row.
Example:
Let's say I DELETE FROM teams WHERE id = 3;
When I now use SELECT id, name, id as rowNumber FROM teams WHERE id = 4 the result is wrong as there are now only two rows (id's 1&2) infront of it in the table.
How can I get the "real" row number/index ordered by id from one specific row?
You are rturning ID as rowNumber, so it simply returning ID column value. Why do you expect it to be different?
I think you may want to define and #curRow variable to get the row number as and use sub query as below:
SELECT * from
(SELECT ID,
NAME,
#curRow := #curRow + 1 AS rowNumber
FROM Teams t
JOIN (SELECT #curRow := 0) curr
ORDER by t.ID asc) as ordered_team
WHERE ordered_team.id = 4;
It's not a good way, but with plain sql:
SELECT
t.id,
t.name,
(SELECT COUNT(*)+1 FROM teams WHERE id < t.id) as row_number
FROM teams t
WHERE t.id = 4
Why do you bother row indexes inside the persistance layer?
If your really need to rely on the "index" of the tupples stored, you could introduce a variable and increment it in the query/ program code for each row.
EDIT:
Just found that one:: With MySQL, how can I generate a column containing the record index in a table?

PHP SQL - Advanced delete query

I have a table with 3 columns: id, date and name. What I am looking for is to delete the records that have a duplicate name. The rule should be to keep the record that has the oldest date. For instance in the example below, there is 3 records with the name Paul. So I would like to keep the one that has the oldest date (id=1) and remove all the others (id = 4 and 6). I know how to make insert, update, etc queries, but here I do not see how to make the trick work.
id, date, name
1, 2012-03-10, Paul
2, 2012-03-10, James
4, 2012-03-12, Paul
5, 2012-03-11, Ricardo
6, 2012-03-13, Paul
mysql_query(?);
The best suggestion I can give you is create a unique index on name and avoid all the trouble.
Follow the steps as Peter Kiss said from 2 to 3. Then do this
ALTER Table tablename ADD UNIQUE INDEX name (name)
Then Follow 4 Insert everything from the temporary table to the original.
All the new duplicate rows, will be omitted
Select all the records what you want to keep
Insert them to a temporary table
Delete everything from the original table
Insert everything from the temporary table to the original
Like Matt, but without the join:
DELETE FROM `table` WHERE `id` NOT IN (
SELECT `id` FROM (
SELECT `id` FROM `table` GROUP BY `name` ORDER BY `date`
) as A
)
Without the first SELECT you will get "You can't specify target table 'table' for update in FROM clause"
Something like this would work:
DELETE FROM tablename WHERE id NOT IN (
SELECT tablename.id FROM (
SELECT MIN(date) as dateCol, name FROM tablename GROUP BY name /*select the minimum date and name, for each name*/
) as MyInnerQuery
INNER JOIN tablename on MyInnerQuery.dateCol = tablename.date
and MyInnerQuery.name = tablename.name /*select the id joined on the minimum date and the name*/
) /*Delete everything which isn't in the list of ids which are the minimum date fore each name*/
DELETE t
FROM tableX AS t
LEFT JOIN
( SELECT name
, MIN(date) AS first_date
FROM tableX
GROUP BY name
) AS grp
ON grp.name = t.name
AND grp.first_date = t.date
WHERE
grp.name IS NULL
DELETE FROM thetable tt
WHERE EXISTS (
SELECT *
FROM thetable tx
WHERE tx.thename = tt.thename
AND tx.thedate > tt. thedate
);
(note that "date" is a reserver word (type) in SQL, "and" name is a reserved word in some SQL implementations)