MySQL Pivot Handling Values - mysql

I have a table like the one bellow:
+------+------+------+-------+
| pkey | name | exam | score |
+------+------+------+-------+
| 1 | Bob | Math| 75 |
| 2 | Bob | Eng | 77 |
| 3 | Bob | Phy | 78 |
| 4 | Sue | Math| 80 |
| 5 | Sue | Eng | 90 |
| 6 | Sue | Phy | 97 |
| 7 | Suzy | Math| 98 |
| 8 | Suzy | Eng | 99 |
| 9 | Suzy | Phy | 99 |
+------+------+------+-------+
And I want to make a query to pivot it into:
+------+------+------+-------+
| Name | Math | Phy | Eng |
+------+------+------+-------+
| Bob | 75 | 78 | 77 |
| Sue | 80 | 97 | 90 |
| Suzy| 90 | 99 | 99 |
+------+------+------+-------+
The problem is, the discipline of the exam can have like 500 different values and I can't define them all as they might change eventually and start having 600 different ones instead.
Is it possible to create a query that will read the value of the exam and create a new column to display the values of it?
Say for example they add Hist to the values of exam and say Suzy got a 100, it would then be like this:
+------+------+------+-------+-----+
| Name | Math | Phy | Eng | Hist|
+------+------+------+-------+-----+
| Bob | 75 | 78 | 77 | |
| Sue | 80 | 97 | 90 | |
| Suzy| 90 | 99 | 99 | 100 |
+------+------+------+-------+-----+
Thanks in advance

A SQL query has to have a well-defined set of columns. You can do what you want with a prepare statement and dynamic SQL.
Another approach would be to create a string of the possible values, such as Math:75, Phy78, Eng:77. If this works for you:
select name, group_concat(exam, ':', score separator ', ')
from table t
group by name;

Related

MYSQL adding columns value of rows

I have a problem in which I have a STUDENT table as
+-------------+-------------+-------------+-------------+---------------+
| roll_number | name | subject_one | subject_two | subject_three |
+-------------+-------------+-------------+-------------+---------------+
| 1 | Sheila | 32 | 48 | 64 |
| 2 | Rachel | 24 | 21 | 25 |
| 3 | Christopher | 55 | 12 | 10 |
+-------------+-------------+-------------+-------------+---------------+
I want the print the output as
+-------------+-------------+-------------+
| roll_number | name | total |
+-------------+-------------+-------------+
| 1 | Sheila | 144|
| 2 | Rachel | 70 |
| 3 | Christopher | 77 |
+-------------+-------------+-------------+
and select all student having marks greater than 75 ??
How can I achieve this using MYSQL ??
I think you just need the aggregate functions and using them is enough. I am not sure if it can help you or not.
SELECT roll_number , name , (subject_one + subject_two + subject_three) AS total FROM STUDENT HAVING total > 75 ;

Joining two MySQL tables and then performing basic filtering on one of them

I have two tables, they look roughly like below:
Categories:
+----+-----------+--------+----------------+
| id | entity_id | set_id | type |
+----+-----------+--------+----------------+
| 1 | 49 | 1 | signup |
| 2 | 57 | 1 | signup |
| 3 | 65 | 1 | signup |
| 4 | 69 | 1 | recommendation |
| 5 | 73 | 1 | signup |
| 6 | 77 | 1 | recommendation |
| 7 | 23 | 2 | comment |
| 8 | 45 | 2 | recommendation |
| 9 | 56 | 2 | signup |
| 10| 76 | 2 | signup |
+----+-----------+--------+----------------+
Steps:
+----+--------+----------+--------+
| id | set_id | start_id | end_id |
+----+--------+----------+--------+
| 1 | 1 | 49 | 57 |
| 2 | 1 | 57 | 65 |
| 3 | 1 | 65 | 69 |
| 4 | 1 | 69 | 73 |
| 5 | 1 | 77 | 57 |
| 6 | 2 | 23 | 45 |
| 7 | 2 | 45 | 56 |
| 8 | 2 | 56 | 76 |
+----+--------+----------+--------+
I need to select the rows of a given set_id from categories, but only if their entity_id is found in steps in the start_id column but NOT the end_id column.
So from this example, these are the two records I would expect to be returned:
+----+-----------+--------+----------------+
| id | entity_id | set_id | type |
+----+-----------+--------+----------------+
| 1 | 49 | 1 | signup |
| 6 | 77 | 1 | recommendation |
+----+--------+----------+-----------------+
I know how to perform a basic join on set_id, but I'm not sure how to search all the records of that set_id from steps and exclude those that have an entity_id that appears in end_id
Something like:
SELECT * FROM categories JOIN steps ON categories.set_id = steps.set_id WHERE ....categories.entity_id is present in steps.start_id but not in steps.end_id??
How should this logic be properly formed?
Exists logic works well here and is also straightforward:
SELECT c.*
FROM categories c
WHERE
EXISTS (SELECT 1 FROM steps s WHERE s.set_id = c.set_id AND c.entity_id = s.start_id)
AND
NOT EXISTS (SELECT 1 FROM steps s WHERE s.set_id = c.set_id AND c.entity_id = s.end_id);
The above query, read in plain English, says to find all category records such that there exists a matching record in steps where the entity_id matches the start_id, but there does not also exist a record in steps where the entity_id appears in end_id.

How to get only the second duplicated record in laravel 5.5?

Let's say i have a user table like this :
+----+-----------+----------------------+------+
| ID | Name | Email | Age |
+----+-----------+----------------------+------+
| 1 | John | john.doe1#mail.com | 24 |
| 2 | Josh | josh99#mail.com | 29 |
| 3 | Joseph | joseph410#mail.com | 21 |
| 4 | George | gge.48#mail.com | 28 |
| 5 | Joseph | jh.city89#mail.com | 24 |
| 6 | Kim | kimsd#mail.com | 32 |
| 7 | Bob | bob.s#mail.com | 38 |
| 8 | Joseph | psa.jos#mail.com | 34 |
| 9 | Joseph | joseph.la#mail.com | 28 |
| 10 | Jonathan | jonhan#mail.com | 22 |
+----+-----------+---------+------------+------+
In the actual, the database consists of more data and some of the data is duplicated, with more than two records. But the point is i want to get only the first and the second row of the duplicated rows that contains the name of "Joseph", How can i achieve this ? My code so far...
User::withTrashed()->groupBy('name')->havingRaw('count("name") >= 1')->get();
With that code the result will retrieve :
+----+-----------+----------------------+------+
| ID | Name | Email | Age |
+----+-----------+----------------------+------+
| 1 | John | john.doe1#mail.com | 24 |
| 2 | Josh | josh99#mail.com | 29 |
| 3 | Joseph | joseph410#mail.com | 21 |
| 4 | George | gge.48#mail.com | 28 |
| 6 | Kim | kimsd#mail.com | 32 |
| 7 | Bob | bob.s#mail.com | 38 |
| 10 | Jonathan | jonhan#mail.com | 22 |
+----+-----------+---------+------------+------+
And i use this code to try to get the second duplicated row :
User::withTrashed()->groupBy('name')->havingRaw('count("name") >= 2')->get();
The result is still same as the mentioned above :
+----+-----------+----------------------+------+
| ID | Name | Email | Age |
+----+-----------+----------------------+------+
| 1 | John | john.doe1#mail.com | 24 |
| 2 | Josh | josh99#mail.com | 29 |
| 3 | Joseph | joseph410#mail.com | 21 |
| 4 | George | gge.48#mail.com | 28 |
| 6 | Kim | kimsd#mail.com | 32 |
| 7 | Bob | bob.s#mail.com | 38 |
| 10 | Jonathan | jonhan#mail.com | 22 |
+----+-----------+---------+------------+------+
I want the result is to get record that have the id "5" with name "Joseph" like this :
+----+-----------+----------------------+------+
| ID | Name | Email | Age |
+----+-----------+----------------------+------+
| 1 | John | john.doe1#mail.com | 24 |
| 2 | Josh | josh99#mail.com | 29 |
| 4 | George | gge.48#mail.com | 28 |
| 5 | Joseph | jh.city89#mail.com | 24 |
| 6 | Kim | kimsd#mail.com | 32 |
| 7 | Bob | bob.s#mail.com | 38 |
| 10 | Jonathan | jonhan#mail.com | 22 |
+----+-----------+---------+------------+------+
But it seems only the first duplicate row is retrieved and i can't get the second duplicated row, can anybody give me suggestion ?
Let's start from your query
User::withTrashed()->groupBy('name')->havingRaw('count("name") >= 1')->get();
This will show all groups of rows whose count equals to 1 ore more. and this is the description of DISTINCT.
If you want to get only duplicate records you should get groups whose count is LARGER than 1.
The other thing to notice here is that a non-aggrigated column will be chosen randomly. because when you get a name and it's count, for example if you select name,count(name), email (email is not in the group by clause - not aggregated), and 4 rows have the same name. so you'll see:
+--------+-------------+-------+
| Name | Count(Name) | Email |
+--------+-------------+-------+
| Joseph | 4 | X |
+--------+-------------+-------+
what do you expect instead of X? which one of the 4 emails? actually, in SQLServer it's forbidden to select a non-aggrigated column and other databases will just give you a random one of the counted 3.
see this answer for more details it's explained very well: Do all columns in a SELECT list have to appear in a GROUP BY clause
So, we'll use having count(name) > 1 and select only the aggregated column name
DB::from('users')->select('name')->groupBy('name')->havingRaw('count("name") > 1')->get();
This should give you (didn't test it) this:
+--------+-------------+
| name | Count(name) |
+--------+-------------+
| Joseph | 4 |
+--------+-------------+
This will give you all names who have 2 or more instances. you can determine the number of duplicates in the having clause. for example having count(name) = 3 will give you all names which have exactly 3 duplicates.
So how to get the second duplicate? I have a question for that:
What is the first (original) duplicate? is it the one with the oldest created_at or the oldest updated_at ? or maybe some other condition?. because of that you should make another query with order by clause to give you the duplicates in the order most convenient to you. for example:
select * from `users` where `name` in (select `name` from users group by `name` having count(`name`) > 1) order by `id` asc
which will give:
+----+-----------+----------------------+------+
| ID | Name | Email | Age |
+----+-----------+----------------------+------+
| 3 | Joseph | joseph410#mail.com | 21 |
| 5 | Joseph | jh.city89#mail.com | 24 |
| 8 | Joseph | psa.jos#mail.com | 34 |
| 9 | Joseph | joseph.la#mail.com | 28 |
+----+-----------+---------+------------+------+

Where and group conflict?

I have a table like this:
mysql> select * from studentscore;
+------------+-----------+-------+
| student_id | cource_id | score |
+------------+-----------+-------+
| 1 | 1 | 80 |
| 1 | 2 | 90 |
| 1 | 3 | 85 |
| 1 | 4 | 78 |
| 2 | 2 | 53 |
| 2 | 3 | 77 |
| 2 | 5 | 80 |
| 3 | 1 | 71 |
| 3 | 2 | 70 |
| 3 | 4 | 80 |
| 3 | 5 | 65 |
| 3 | 6 | 75 |
| 4 | 2 | 90 |
| 4 | 3 | 80 |
| 4 | 4 | 70 |
| 4 | 6 | 95 |
| 5 | 1 | 60 |
| 5 | 2 | 70 |
| 5 | 5 | 80 |
| 5 | 6 | 69 |
| 6 | 1 | 76 |
| 6 | 2 | 88 |
| 6 | 3 | 87 |
| 7 | 4 | 80 |
| 8 | 2 | 71 |
| 8 | 3 | 58 |
| 8 | 5 | 68 |
| 9 | 2 | 88 |
| 10 | 1 | 77 |
| 10 | 2 | 76 |
| 10 | 3 | 80 |
| 10 | 4 | 85 |
| 10 | 5 | 83 |
| 11 | 3 | 80 |
| 12 | 4 | 99 |
| 13 | 5 | 74 |
+------------+-----------+-------+
I want to show student_id and students' average scores that are higher than 80.
The output I want is like this:
+------------+-------------------+
| student_id | Average |
+------------+-------------------+
| 1 | 83.25 |
| 4 | 83.75 |
| 6 | 83.66666666666667 | // and how can I make this result shorter like 83.67?
| 7 | 80 |
| 9 | 88 |
| 10 | 80.2 |
| 11 | 80 |
| 12 | 99 |
+------------+-------------------+
I've tried the following codes
mysql> select student_id, avg(score) as average_score
-> from studentscore
-> group by student_id
-> where avg(score) >= 80;
and it gave me an syntax error.
I know by rules the where clause should go before the group by clause but I can't because the where clause depends on the result from the group by clause, and if I switch their position it will give me another error("Invalid use of group function").
Can some one tell me how to get the table I want?
use "having" instead of "where"
use having instead of where.
Here's the difference:
with where you can write a predicate that will be applied to each row
with having you can write a predicate that will applied to each group
and in your case, the 2nd is the only solution that can work.
select student_id, avg(score) as average_score
from studentscore
group by student_id
having avg(score) >= 80;
where applies a filter to your data before grouping has taken place, whereas having applies a filter post-grouping. round(,2) will format as you also ask:
select student_id, round(avg(score), 2) as average_score
from studentscore
group by student_id
having average_score >= 80;

MySQL Query for averages

good morning. I have this table:
mysql> select * from Data;
+---------------------------+--------+-------+
| affyId | exptId | level |
+---------------------------+--------+-------+
| 31315_at | 3 | 250 |
| 31324_at | 3 | 91 |
| 31325_at | 1 | 191 |
| 31325_at | 2 | 101 |
| 31325_at | 4 | 51 |
| 31325_at | 5 | 71 |
| 31325_at | 6 | 31 |
| 31356_at | 3 | 91 |
| 31362_at | 3 | 260 |
| 31510_s_at | 3 | 257 |
| 5321_at | 4 | 90 |
| 5322_at | 4 | 90 |
| 5323_at | 4 | 90 |
| 5324_at | 3 | 57 |
| 5324_at | 4 | 90 |
| 5325_at | 4 | 90 |
| AFFX-BioB-3_at | 3 | 97 |
| AFFX-BioB-5_at | 3 | 20 |
| AFFX-BioB-M_at | 3 | 20 |
| AFFX-BioB-M_at | 5 | 214 |
| AFFX-BioB-M_at | 7 | 20 |
| AFFX-BioB-M_at | 8 | 40 |
| AFFX-BioB-M_at | 9 | 20 |
| AFFX-HSAC07/X00351_M_at | 3 | 86 |
| AFFX-HUMBAPDH/M33197_3_st | 3 | 277 |
| AFFX-HUMTFFR/M11507_at | 3 | 90 |
| AFFX-M27830_3_at | 3 | 271 |
| AFFX-MurIL10_at | 3 | 8 |
| AFFX-MurIL10_at | 5 | 8 |
| AFFX-MurIL10_at | 6 | 4 |
| AFFX-MurIL2_at | 3 | 20 |
| AFFX-MurIL4_at | 5 | 78 |
| AFFX-MurIL4_at | 6 | 20 |
| U95-32123_at | 1 | 128 |
| U95-32123_at | 2 | 128 |
| U98-40474_at | 1 | 57 |
| U98-40474_at | 2 | 57 |
+---------------------------+--------+-------+
37 rows in set (0.00 sec)
If I wanna look for the average expression level (level) of each array probe (affyId) across all experiments, I do SELECT affyId, AVG(level) AS average FROM Data GROUP BY affyId;
However, I can't figure out how to look for the average expression level of each array probe (affyId) for each experiment... It must be something similar to the last query, but I don't obtain good results... any help?
PD: someone told me I should give some reputation or click to some green button if somebody solves my question... Is it right? How do I do it? I'm pretty new on this website...
This shows the average for every affyId:
SELECT affyId, AVG(level) AS average FROM Data GROUP BY affyId
This the average for every exptId:
SELECT exptId, AVG(level) AS average FROM Data GROUP BY exptId
and this the average for every exptId in every affyId:
SELECT affyId, exptId, AVG(level) AS average FROM Data GROUP BY exptId, affyId
Just add that to the group by clause
SELECT affyId, exptId, AVG(level) AS average
FROM Data
GROUP BY affyId, exptId;