MySQL return min value but not null - mysql

I have a table where there are columns students and grade obtained(A-F). A student can appear for test more than once. Sometimes students register but do not appear for test so the grade is not entered but student record entry is made.
I want to get best grade of each student. When I do min(grade) if there is any record with null, null gets selected instead of 'A-F' which indicate proper results. I want to get min of grade if grade exists or null if there are no grades.
SELECT `name`,min(grade) FROM `scores` group by `name`
Id | Name | Grade
1 | 1 | B
2 | 1 |
3 | 1 | A
4 | 2 | C
5 | 2 | D
For name 1 it is fetching second record not the third one having 'A'.

As per the conversations in the comments, the easiest solution may be to convert your empty strings to null, and let the builtin min function do the heavy lifting:
ALTER TABLE scores MODIFY grade VARCHAR(1) NULL;
UPDATE scores
SET grade = null
WHERE grade = '';
SELECT name, MIN(grade)
FROM scores
GROUP BY name
If this is not possible, a dirty trick you could use is to have a case expression convert the empty string to a something you know will come after F:
SELECT name,
MIN(CASE grade WHEN '' THEN 'DID NOT PARTICIPATE' ELSE grade END)
FROM scores
GROUP BY name
And if you really need the empty string back, you can have another case expression around the min:
SELECT name, CASE best_grade WHEN 'HHH' THEN '' ELSE best_grade END
FROM (SELECT name,
MIN(CASE grade WHEN '' THEN 'HHH' ELSE grade END) AS
best_grade
FROM scores
GROUP BY name) t

Change your query slightly to -
SELECT `name`,min(grade) FROM `scores` WHERE grade <> "" group by `name`
If the name has a grade/s assigned to it then the lowest will be returned else the resultset will be null

Related

How Do I convert this table from row to column?

I have a table that contains id and country name, and I need to convert them so the id with more than 1 country will display in 1 row.I have been searching in this forum for over an hour and found nothing.
I tried if using the pivot function can help me to achieve the result i wanted, but I feel like using pivot does not work on my case here.
This is a mini version of the table I have. The number of distinct value in the field "country" will be over 100, so I can just say something like when county = '..' as this will be to repetitive.
enter code here
+----+--------+
| id | country|
+----+--------+
| 1 | US |
| 1 | UK |
| 2 | JP |
+----+--------+
Desired outcome I am looking for:
enter code here
+----+-----------+-----------+
| id | country_1 | country_2 |
+----+-----------+-----------+
| 1 | US | UK |
| 2 | JP | null |
+----+-----------+-----------+
I found this question which is similar but it is the opposite of what I am trying to achieve.
MySQL statement to pivot table without using pivot function or a union
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
update:
Thank you so much for all of your helps. I may not have used the queries of yours to solve my problem - as of the fact that the syntax is a bit diff running in snowflake. However, I got the insights I need from all of you.
here is my solution:
enter code here
select t1.id,
max(iff(t1.row_number = 1, t1.country ,null)) as country_1,
max(iff(t1.row_number = 2, t1.country ,null)) as country_2,
max(iff(t1.row_number = 3, t1.country, null)) as country_3
from
(
select id, country, row_number() over (partition by id order by id ) as
row_number
from table
) t1
group by t1.id
Whereas you could do it with "pivoting", what will happen when you have 3 countries? Or 4? Or 17?
May I suggest this:
SELECT id,
GROUP_CONCAT(country)
FROM tbl
GROUP BY id;
You will get something like:
1 US,UK
2 JP
use aggregation
select id, max(case when id=1 then country end ) as country_1,
max(case when id=2 then country end ) as country_2
from tbale group by id
As you comment on #Rick answer you have max 3 country for each id then you can use this
select
id,
(select country from test where test.id=t.id limit 0,1)as country_1,
(select country from test where test.id=t.id limit 1,1)as country_2,
(select country from test where test.id=t.id limit 2,1)as country_3
from test as t
group by id;
DEMO
You can try this following script with RowNumber generated per id. As you confirmed there are maximum 3 country per id, we can easily generate your desired result set by handling RowNumber 1,2 & 3
SELECT ID,
MAX(CASE WHEN RowNumber = 1 THEN country ELSE NULL END) Country_1,
MAX(CASE WHEN RowNumber = 2 THEN country ELSE NULL END) Country_2,
MAX(CASE WHEN RowNumber = 3 THEN country ELSE NULL END) Country_3
FROM
(
SELECT id,
country,
#row_num :=IF(#prev_value = concat_ws('',id),#row_num+1,1)AS RowNumber
,#prev_value := concat_ws('',id)
FROM tbale
ORDER BY id
)A
GROUP BY id
There's no "dynamic" PIVOT in SQL. You need to specify the list of columns when writing the query. Your options are:
If you know the number of columns in advance, then #ZaynulAbadinTuhin solution is the easier. It seems, however, this is not your case.
If you don't know the number of columns in advance and you want them all concatenated in a single column, then #Rick James solution is the best.
Otherwise, you can still use some kind of dynamic SQL in your app or in a stored procedure that will build the SQL query at runtime, based on the existing values of the table. But this solution would require much more programming. It's not a single/simple SQL query anymore. See Rick James's Pivoting in MySQL stored procedure.

Join two subqueries and have a field: division of the results of two subqueries

I have a table like this:
userid | trackid | path
123 70000 ad
123 NULL abc.com
123 NULL Apply
345 70001 Apply
345 70001 Apply
345 NULL Direct
345 NULL abc.com
345 NULL cdf.com
And I want a query like this. When path='abc.com', num_website +1; when path='Apply', num_apply +1
userid | num_website | num_Apply | num_website/num_Apply
123 1 1 1
345 1 2 0.5
My syntax looks like this:
select * from
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num from
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
My question is
1. how to have the field num_website/num_apply in term of my syntax above?
2. is there any other easier way to get the result I want?
Any spots shared will appreciate.
The simplest way to do it would be to change the select line:
SELECT a1.userid, a1.is_CWS, a2.Apply_num, a1.is_CWS/a2.Apply_num FROM
(select userid,count(path) as is_CWS
from TABLE
where path='abc.com'
group by userid
having count(path)>1) a1
JOIN
(select userid,count(userid) as Apply_num
from TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
and then continue with the rest of your query as you have it. The star means "select everything." If you wanted to select only a few things, you would just list those things in place of the star, and if you wanted to select some other values based on those things, you would put those in the stars as well. In this case a1.is_CWS/a2.Apply_num is an expression, and MySql knows how to evaluate it based on the values of a1.is_CWS and a2.Apply_num.
In the same vein, you can do a lot of what those subqueries are doing in a single expression instead of a subquery. objectNotFound has the right idea. Instead of doing a subquery to retrieve the number of rows with a certain attribute, you can select SUM(path="abc.com") as Apply_num and you don't have to join anymore. Making that change gives us:
SELECT a1.userid,
SUM(path="abc.com") as is_CWS,
a2.Apply_num,
is_CWS/a2.Apply_num FROM
TABLE
JOIN
(select userid,count(userid) as Apply_num
FROM TABLE
where trackid is not NULL
group by userid) a2
on a1.userid=a2.userid
GROUP BY userid
Notice I moved the GROUP BY to the end of the query. Also notice instead of referencing a1.is_CWS I now reference just is_CWS (it's no longer inside the a1 subtable so we can just reference it)
You can do the same thing to the other subquery then they can share the GROUP BY clause and you won't need the join anymore.
to get you started ... you can build on top of this :
select
userid,
SUM(CASE WHEN path='abc.com'then 1 else 0 end ) as num_website,
SUM(CASE WHEN path='Apply' and trackid is not NULL then 1 else 0 end ) as Apply_Num
from TABLE
WHERE path='abc.com' or path='Apply' -- may not need this ... play with it
group by userid

MySQL count identical values

I have this table named prizes with the following structure
`id` (PRIMARY)
`id_multiple`
`desc`
`winner`
I want to select those who don't have a winner (NULL) and display them together if they have the same id_multiple showing the count of how many left to win of that id_multiple.
So for example, there's this values:
id_multiple | winner | desc
1 | NULL | voucher
1 | jonh | voucher
2 | NULL | car
2 | NULL | car
And I want to display:
Left to win:
1 Voucher
2 Car
(The desc will be the same for all id_multiple so it might be ambiguous to use id_multiple?)
Something like:
SELECT id_multiple,count(id_multiple),`desc`
FROM `yourtable`
WHERE `winner` IS NULL
GROUP BY `id_multiple`
You could count a case expression:
SELECT id_multiple, COUNT(CASE WHEN winner IS NULL THEN 1 END) AS left_to_win, `desc`
FROM mytable
GROUP BY id_multiple, `desc`
Or, even simpler, with a sum expression that takes advantage of the fact that true is interpreted as 1 and false as 0 in numerical contexts:
SELECT id_multiple, SUM(winner IS NULL) AS left_to_win, `desc`
FROM mytable
GROUP BY id_multiple, `desc`

Excluding results of nested SQL query

I have a table of winners vs losers (TABLE1) e.g.
+----+--------+-------+
| ID | Winner | Loser |
+----+--------+-------+
| 1 | 2 | 3 |
| 2 | 1 | 2 |
+----+--------+-------+
In the most recent game between Item 1 and Item 2, 1 won (ID 2). For this example, I'll refer to this as Current Winner and Current Loser.
I'm trying to build a query that works our inferences from past results.
e.g. if 2>3, and 1>2. Then I need to record a value for 1>3
The query I'm building would find multiple inferred losers against the current winner.
The ideal query would return an array of "losers", which I can loop through and record in the table as inferred results. In this case "3".
The table would be updated to:
+----+--------+-------+
| ID | Winner | Loser |
+----+--------+-------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 3 |
+----+--------+-------+
And if the query was run again, it would return nothing.
The process I have so far is:
Look up everything the Current Loser, has previously beaten (Previous losers to Current loser)
Check the table to see if any of the Previous Losers to Current Loser, has played the current winner, ever.
Any previous loser that has, should be removed
To get the list of things the Current Loser has beaten i use:
select * from TABLE1 where winner = 2
Then for the second bullet point, I've got two nested queries:
select * from TABLE1 where winner = 1 and loser = (select loser from rp_poss where winner = 2)
select * from TABLE1 where loser = 1 and winner = (select loser from rp_poss where winner = 2)
I really can't work out how to put these together, to remove the rows I don't want. Can somebody let me know what is best, and most efficient query for this for a example, a nested query, some kind of join? Pea brain is really struggling with this.
Thanks in advance
You can do it this way, by explicitly looking for certain records (a match between the two items) and counting to see if there are zero of them.
CURRENTLOSER and CURRENTWINNER are placeholders for variables or whatever.
select previous.loser
from table1 previous
where previous.winner=CURRENTLOSER and (
select count(*)
from table1 ancient
where (ancient.winner=CURRENTWINNER and ancient.loser=previous.loser) or
(ancient.loser=CURRENTWINNER and ancient.winner=previous.loser)
) = 0
Aliasing tables ("from table1 ancient") will help get the algorithm clear in your head.
This will get you one row for every person and competitor, and the last result with that competitor: (ie. if person 1 goes up against person 2 and loses, and then goes up against that person again and wins, this query will show person 1 with competitor 2 WIN, and person 2 with competitor 1 LOSE). It shows the LATEST result for each competitor, relative to the person.
http://sqlfiddle.com/#!2/823d3f/6/0
select x.person,
case when x.person <> t.winner then t.winner else t.loser end as competitor,
case when x.person = t.winner then 'WIN' else 'LOSE' end as result
from (select x.winner as person, max(y.id) as id
from (select winner from table1 union select loser from table1) x
join table1 y
on x.winner = y.winner
or x.winner = y.loser
group by x.winner) x
join table1 t
on x.person = t.winner
or x.person = t.loser
where x.id = t.id
The query below will insert inferred losers for the most recent match between 1 and 2 the first time it's run. The second time it won't insert any new rows.
Initially the not exists subquery had where id < current.id to remove previous losers, however, since inferred games are inserted with 'future' ids (i.e. 3 in your example), if you ran the query again, it would reinsert the rows, so I changed it to where id <> current.id, which means it will also exclude 'future' losers.
insert into mytable (winner, loser)
select current.winner, previous.loser
from (select id, winner, loser
from mytable where
(winner = 1 and loser = 2)
or (winner = 2 and loser = 1)
order by id desc limit 1) current
join mytable previous
on previous.winner = current.loser
and previous.id < current.id
where not exists (select 1 from mytable
where id <> current.id
and ((winner = current.winner and loser = previous.loser)
or (winner = previous.loser and loser = current.winner)))

Order MySql query using

I, I would like to make a query and sorting my members in a special way... Could someone help ?
Here's the problem.
I would like to select members in my table using a special sort order.
The profile fields values are stored in a table wp_bp_xprofile_data like this :
id | field_id | user_id | value
--------+----------+---------+----------
For example, I have 3 fields
NICKNAME (field_id = 1)
FIRSTNAME (field_id = 2)
LASTNAME (field_id = 3)
The table rows will look like this :
id | field_id | user_id | value
--------+----------+---------+----------
2544 1 100 fib
2545 2 100 john
2546 3 100 arenzich
2547 1 200 dog
2548 2 200 rick
2549 3 200 zarenburg
2550 1 300 fox
2551 2 300 frank
2552 3 300 arenzich
I've got this query to sort them using one field, for example to sort them by nickname alphabetically :
SELECT *
FROM wp_bp_xprofile_data u WHERE u.field_id = 1 ORDER BY u.value ASC
So they will be sorted like this : dog(200),fib(100), then fox(300).
Now, I would like to sort them not one but several fields (firstname and lastname; to differenciate people with same lastname) so that the query returns the users in this order :
frank arenzich (300), john arenzich (100), frank arenzich (200).
Any idea for doing this ?
Thanks A LOT !!!
This will probably need to be done by first pivoting this into a proper table by column, and then ordering that on multiple columns.
Note: this will not produce output in the same format as your original table, but is arguably a lot more flexible and useful as it combines all information about each user_id into a single row.
/* A column-wise pivot of NICKNAME, FIRSTNAME, LASTNAME */
SELECT
user_id,
MAX(CASE WHEN field_id = 1 THEN value ELSE null END) AS NICKNAME,
MAX(CASE WHEN field_id = 2 THEN value ELSE null END) AS FIRSTNAME,
MAX(CASE WHEN field_id = 3 THEN value ELSE null END) AS LASTNAME
FROM wp_bp_xprofile_data
GROUP BY user_id
/* Include the HAVING if you only want those who have both first & last names specified */
HAVING
FIRSTNAME IS NOT NULL
AND LASTNAME IS NOT NULL
/* Pivoted columns can then be treated in the ORDER BY */
ORDER BY
FIRSTNAME,
LASTNAME
Here is a demonstration...
It looks like this is a Wordpress table, so you may not be in a position to change its structure. But if you do have the option of modifying it, I would recommend changing the structure to resemble the pivot's output to begin with.