mysql group concat improper ordering - mysql

I have a query which returns records in following order from db:
app_id app_name transaction_id mobile_no logtime_stamp navigation_type entered_code display_text User_Response
111 Unicef 1133 919552516853 10/5/2012 12:52:37 PM 0 Maharashtra Student Attendance 2
111 Unicef 1133 919552516853 10/5/2012 12:52:37 PM 0 Pune Student Attendance 2
111 Unicef 1133 919552516853 10/5/2012 12:52:37 PM 0 Baramati Student Attendance 2
111 Unicef 1133 919552516853 10/5/2012 12:52:37 PM 0 Ravi School Student Attendance 2
The query that returns the above records is an inner query.
I am doing a group_concat on these records to get a single row.
I am getting the following records:
app_name transaction_id mobile_no entered_code display_text User_Response
Unicef 1133 919552516853 Baramati,Ravi School,Maharashtra,Pune Student Attendance 2
Now I dont understand on what basis is the group_concat function ordering the string - Baramati,Ravi School,Maharashtra,Pune!?
I want the ordering to be exactly the same as I am getting in the first set:
Maharashtra,Pune,Baramati,Ravi School
As far as I know, a string in group_concat is sorted alphabetically by default. But the above result defies that.
Also I tried a sample query where in I am reading records from a table and group_concating that without assigning any specific order. The result had string sorted as per the insertion order. That is, the record that was inserted first came up first in the concat string and the last record as the last in the string.
So can I arrange/order my result set in the group_concat funcation based on the read order of the inner query?

Just looked at the docs: http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
Found this line:
To sort values in the result, use the ORDER BY clause.
as well as this, as an example:
mysql> SELECT student_name,
-> GROUP_CONCAT(DISTINCT test_score
-> ORDER BY test_score DESC SEPARATOR ' ')
-> FROM student
-> GROUP BY student_name;

group_concat(entered_code order by entered_code)

Without seeing the full query and table data prior to the results you gave, it is difficult to tell of you can add an ORDER BY to you GROUP_CONCAT() however, it appears to be working fine in this demo:
select app_name,
transaction_id,
mobile_no,
group_concat(entered_code),
display_text,
User_Response
from yourtable
See SQL Fiddle with Demo.

As for documentation you have to be able to order group concat result based on some field or expression. http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
You should not rely on an "insertion order" at all since there is no such feature in MySQL. In most case you see data in this order, but it is not being ordered actually and later you go less relevant this order will become.

Related

Get rows which are related to the searched row, by specific column

I am trying to implement a sql query to below scenario,
user_id
nic_number
reg_number
full_name
code
B123
12345
1212
John
123
B124
12346
1213
Peter
124
B125
12347
1214
Darln
125
B123
12345
1212
John
126
B123
12345
1212
John
127
In the subscribers table there can be rows with same user_id , nic_number , reg_number , full_name. But the code is different.
First -> get the user who have same code i have typed in the query ( i have implemented a query for that and it is working fine)
Second -> Then in that data i need to find the related rows (check by nic_number, and reg_number) and display only those related rows. That means in the below query I have got the data for code = 123. Which will show the first row of the table.
But I need to display only the rest of the rows which have the same nic_number or reg_number for the searched code only once.
That means the last 2 rows of the table.
select code,
GROUP_CONCAT(distinct trim(nic_number)) as nic_number,
GROUP_CONCAT(distinct trim(reg_number)) as reg_number,
GROUP_CONCAT(distinct trim(full_name)) as full_name from subscribers
where code like lower(concat('123')) group by code;
I need to implement sql query for this scenario by changing the above query.(Only one query, without joins or triggers).
I have tried this for a long time and unable to get the result. If anyone of you help me to get the result it will be very helpful.
You can combine nic and reg numbers in a unique key to get your records.
EDITED
to extract only related rows and not the one searched by code,
by the way, code seems not to be unique in subscribers table.
select
code,
trim(nic_number) as nic_number,
trim(reg_number) as reg_number,
trim(full_name) as full_name,
trim(code) as code
from
subscribers s1
where
code <> lower(trim('123'))
and trim(nic_number) + '|' + trim(reg_number) IN (
select trim(nic_number) + '|' + trim(reg_number)
from subscribers
where code = lower(trim('123'))
)
I'm not sure why you have specified "without joins" - I get that you may not want to have triggers on a table (which you don't need to achieve this anyway), but a JOIN is standard SQL syntax that will help you achieve the result you are after.
Try:
SELECT
s1.code, s1.nic_number, s1.reg_number, s1.full_name
FROM subscribers s1
INNER JOIN
(
SELECT nic_number, reg_number
FROM subscribers
WHERE code = '123'
) s2
ON s1.nic_number = s2.nic_number
AND s1.reg_number = s2.reg_number
WHERE s1.code <> '123';
Or, if you really need to achieve it with no JOINs at all, then you're just doubling-up the sub-query that you need to include:
SELECT
s1.code, s1.nic_number, s1.reg_number, s1.full_name
FROM subscribers s1
WHERE s1.nic_number IN
(
SELECT nic_number FROM subscribers
WHERE code = '123'
)
AND s1.reg_number IN
(
SELECT reg_number FROM subscribers
WHERE code = '123'
)
AND s1.code <> '123';
The latter query is not necessarily ideal, but it still achieves the desired result.

Retrieve data in a single row using group keyword in sql

Let's say I have a table tbl_marks with columns and data as below.
name id section1 section2 section3 section4 year
cherry 1 100 101 102 103 2016
cherry 1 200 201 202 203 2015
cherry 1 300 301 302 303 2014
Expected Output Format :
cherry 1 100101102103 200201202203 300301302303
I would like to have scores of all sections of one year to be concatenated and then followed by scores of another year separated by space.
So I need 5 columns in single row. (name, id, scores of year1, scores of year2, scores of year3)
Please let me know how should i update the query below. Thank you.
Query : select name, id, ??? from tbl_marks group by id;
Use GROUP_CONCAT:
SELECT name,
id,
GROUP_CONCAT(CONCAT(section1, section2, section3, section4)
ORDER BY section1 SEPARATOR ' ')
FROM yourTable
GROUP BY name, id
This answer assumes that you want three columns in your result set, the three columns being the name, id, and a CSV list of marks for that name/id group (123,456,789 in this case).
SQLFiddle (courtesy of #Luke)
The process you are looking for is called 'pivoting' and is not dynamically possible in basic SQL as it is a set-based language.
Just retrieve the rows in your business logic code and turn it into columns there.
All you need to use is group_concat() and concat() functions of MySQL, e.g.
select name, group_concat(concat(value1, value2))
from table
group by name
Here is the sample SQL Fiddle.
One way to solve this is self joins.
SELECT a.id,a.name,CONCAT(a.section1, a.section2, a.section3, a.section4) as val2015
,CONCAT(b.section1, b.section2, b.section3, b.section4) as val2016
,CONCAT(c.section1, c.section2, c.section3, c.section4) as val2017
FROM test a
LEFT JOIN test b
ON b.id=a.id
LEFT JOIN test c
ON c.id=a.id
WHERE a.year=2015 AND b.year=2016 AND c.year=2017
SQL Fiddle
Edit: Changed the query according to comments.

Complex SQL Select query with inner join

My SQL query needs to return a list of values alongside the date, but with my limited knowledge I have only been able to get this far.
This is my SQL:
select lsu_students.student_grouping,lsu_attendance.class_date,
count(lsu_attendance.attendance_status) AS count
from lsu_attendance
inner join lsu_students
ON lsu_students.student_grouping="Central1A"
and lsu_students.student_id=lsu_attendance.student_id
where lsu_attendance.attendance_status="Present"
and lsu_attendance.class_date="2015-02-09";
This returns:
student_grouping class_date count
Central1A 2015-02-09 23
I want it to return:
student_grouping class_date count
Central1A 2015-02-09 23
Central1A 2015-02-10 11
Central1A 2015-02-11 21
Central1A 2015-02-12 25
This query gets the list of the dates according to the student grouping:
select distinct(class_date)from lsu_attendance,lsu_students
where lsu_students.student_grouping like "Central1A"
and lsu_students.student_id = lsu_attendance.student_id
order by class_date
I think you just want a group by:
select s.student_grouping, a.class_date, count(a.attendance_status) AS count
from lsu_attendance a inner join
lsu_students s
ON s.student_grouping = 'Central1A' and
s.student_id = a.student_id
where a.attendance_status = 'Present'
group by s.student_grouping, a.class_date;
Comments:
Using single quotes for string constants, unless you have a good reason.
If you want a range of class dates, then use a where with appropriate filtering logic.
Notice the table aliases. The query is easier to write and to read.
I added student grouping to the group by. This would be required by any SQL engine other than MySQL.
Just take out and lsu_attendance.class_date="2015-02-09" or change it to a range, and then add (at the end) GROUP BY lsu_students.student_grouping,lsu_attendance.class_date.
The group by clause is what you're looking for, to limit aggregates (e.g. the count function) to work within each group.
To get the number of students present in each group on each date, you would do something like this:
select student_grouping, class_date, count(*) as present_count
from lsu_students join lsu_attendance using (student_id)
where attendance_status = 'Present'
group by student_grouping, class_date
Note: for your example, using is simpler than on (if your SQL supports it), and putting the table name before each field name isn't necessary if the column name doesn't appear in more than one table (though it doesn't hurt).
If you want to limit which data rows get included, put your constraints get in the where clause (this constrains which rows are counted). If you want to constrain the aggregate values that are displayed, you have to use the having clause. For example, to see the count of Central1A students present each day, but only display those dates where more than 20 students showed up:
select student_grouping, class_date, count(*) as present_count
from lsu_students join lsu_attendance using (student_id)
where attendance_status = 'Present' and student_grouping = 'Central1A'
group by student_grouping, class_date
having count(*) > 20

MySQL ORDER BY Column = value AND distinct?

I'm getting grey hair by now...
I have a table like this.
ID - Place - Person
1 - London - Anna
2 - Stockholm - Johan
3 - Gothenburg - Anna
4 - London - Nils
And I want to get the result where all the different persons are included, but I want to choose which Place to order by.
For example. I want to get a list where they are ordered by LONDON and the rest will follow, but distinct on PERSON.
Output like this:
ID - Place - Person
1 - London - Anna
4 - London - Nils
2 - Stockholm - Johan
Tried this:
SELECT ID, Person
FROM users
ORDER BY FIELD(Place,'London'), Person ASC "
But it gives me:
ID - Place - Person
1 - London - Anna
4 - London - Nils
3 - Gothenburg - Anna
2 - Stockholm - Johan
And I really dont want Anna, or any person, to be in the result more then once.
This is one way to get the specified output, but this uses MySQL specific behavior which is not guaranteed:
SELECT q.ID
, q.Place
, q.Person
FROM ( SELECT IF(p.Person<=>#prev_person,0,1) AS r
, #prev_person := p.Person AS person
, p.Place
, p.ID
FROM users p
CROSS
JOIN (SELECT #prev_person := NULL) i
ORDER BY p.Person, !(p.Place<=>'London'), p.ID
) q
WHERE q.r = 1
ORDER BY !(q.Place<=>'London'), q.Person
This query uses an inline view to return all the rows in a particular order, by Person, so that all of the 'Anna' rows are together, followed by all the 'Johan' rows, etc. The set of rows for each person is ordered by, Place='London' first, then by ID.
The "trick" is to use a MySQL user variable to compare the values from the current row with values from the previous row. In this example, we're checking if the 'Person' on the current row is the same as the 'Person' on the previous row. Based on that check, we return a 1 if this is the "first" row we're processing for a a person, otherwise we return a 0.
The outermost query processes the rows from the inline view, and excludes all but the "first" row for each Person (the 0 or 1 we returned from the inline view.)
(This isn't the only way to get the resultset. But this is one way of emulating analytic functions which are available in other RDBMS.)
For comparison, in databases other than MySQL, we could use SQL something like this:
SELECT ROW_NUMBER() OVER (PARTITION BY t.Person ORDER BY
CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.ID) AS rn
, t.ID
, t.Place
, t.Person
FROM users t
WHERE rn=1
ORDER BY CASE WHEN t.Place='London' THEN 0 ELSE 1 END, t.Person
Followup
At the beginning of the answer, I referred to MySQL behavior that was not guaranteed. I was referring to the usage of MySQL User-Defined variables within a SQL statement.
Excerpts from MySQL 5.5 Reference Manual http://dev.mysql.com/doc/refman/5.5/en/user-variables.html
"As a general rule, other than in SET statements, you should never assign a value to a user variable and read the value within the same statement."
"For other statements, such as SELECT, you might get the results you expect, but this is not guaranteed."
"the order of evaluation for expressions involving user variables is undefined."
Try this:
SELECT ID, Place, Person
FROM users
GROUP BY Person
ORDER BY FIELD(Place,'London') DESC, Person ASC;
You want to use group by instead of distinct:
SELECT ID, Person
FROM users
GROUP BY ID, Person
ORDER BY MAX(FIELD(Place, 'London')), Person ASC;
The GROUP BY does the same thing as SELECT DISTINCT. But, you are allowed to mention other fields in clauses such as HAVING and ORDER BY.

mysql first record retrieval

While very easy to do in Perl or PHP, I cannot figure how to use mysql only to extract the first unique occurence of a record.
For example, given the following table:
Name Date Time Sale
John 2010-09-12 10:22:22 500
Bill 2010-08-12 09:22:37 2000
John 2010-09-13 10:22:22 500
Sue 2010-09-01 09:07:21 1000
Bill 2010-07-25 11:23:23 2000
Sue 2010-06-24 13:23:45 1000
I would like to extract the first record for each individual in asc time order.
After sorting the table is ascending time order, I need to extract the first unique record by name.
So the output would be :
Name Date Time Sale
John 2010-09-12 10:22:22 500
Bill 2010-07-25 11:23:23 2000
Sue 2010-06-24 13:23:45 1000
Is this doable in an easy fashion with mySQL?
I think that something along the lines of
select name, date, time, sale from mytable order by date, time group by name;
will get you what you're looking for
you need to perform a groupwise max or groupwise min
see below or http://pastie.org/973117 for an example
select
u.user_id,
u.username,
latest.comment_id
from
users u
left outer join
(
select
max(comment_id) as comment_id,
user_id
from
user_comment
group by
user_id
) latest on u.user_id = latest.user_id;
In databases, there really is no "first" or "last" record; think of each record as its own, non-positional entity in the table. The only positions they have are when you give them one, say, using ORDER BY.
This will give you what you want. It might not be efficient, but it works.
select Name, Date, Time, Sale from
(select Name, Date, Time, Sale from MyTable
order by Date asc, Time asc) MyTable_subquery_name
group by Name
Note: MyTable_subquery_name is just a dummy name for the subquery. MySQL will give the error ERROR 1248 (42000): Every derived table must have its own alias without it.
If only GROUP BY and ORDER BY were communicative operations, then this wouldn't have to be a subquery.