Using GROUP_CONCAT() usually invokes the group-by logic and creates temporary tables, which are usually a big negative for performance. Sometimes you can add the right index to avoid the temp table in a group-by query, but not in every case.
https://stackoverflow.com/a/26225148/9685125
After Reading this post, I realized that I was doing wrong, because many time I made complicated query using huge GROUP_CONCAT(). Such as
GROUP_CONCAT(DISTINCT exam.title) AS exam,
GROUP_CONCAT(subject.title, '<br/> Th - ', mark.th, ' | PR - ', mark.pr SEPARATOR ',') AS mark
But what can be alternative of GROUP_CONCAT in following situation without using subquery. I mean using only Mysql join,
For example, let see two relational database and and query to explain my problem
Student
id | Rid | name
========================
1 | 1 | john
Marks
id | std_id | th
======================
1 | 1 | 60
2 | 1 | 70
3 | 1 | 80
4 | 1 | 90
"SELECT
student.en_name, mark.th
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id;"
Column would be repeated if only use JOIN
John: 60, John: 70, John: 80, John: 90
So, I use GROUP BY. But if I assign GROUP BY to student.id, only first row is fetched
"SELECT
student.en_name, mark.th
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id
GROUP BY student.id;"
Result is
John: 60
So to get result, we have to assign that group.concat
"SELECT
student.en_name,
GROUP_CONCAT(mark.th) as mark
FROM student
JOIN mark ON student.id = mark.std_id
WHERE student.id=:id
GROUP BY student.id;"
And final and expected result using exploding array
$name=$row['en_name'];
echo "$name: <br/>";
$mrk_array = explode(',',$row['mark']);
foreach($mrk_array as $mark){
echo $mark.", ";
}
John:
60, 70, 80, 90,
Here, I don't see any alternative of GROUP_CONCAT to fetch all associated value of each Id and prevent duplicate, please help me how to replace GROUP_CONCAT from here.
Also, one friend told me
So why GROUP_CONCAT if you're "exploding" it. You might as well return a nice associative array and then deal with displaying it there.
But I can't understand, what he means ?
Too long for a comment...
With your original query, you are effectively returning an array of rows (associative arrays):
array(array('en_name' => 'John', 'mark' => 60),
array('en_name' => 'John', 'mark' => 70),
array('en_name' => 'John', 'mark' => 80),
array('en_name' => 'John', 'mark' => 90)
)
When you use GROUP BY and GROUP CONCAT, you are effectively imploding the 'mark' elements of that array to
array('en_name => 'John', 'mark' => '60,70,80,90')
and you then have to explode the array again to process the data.
If you stick with your original query, you can instead do the imploding in your application framework e.g.
$name = "";
while ($row = $result->fetch()) {
if ($row['en_name'] != $name) {
$name = $row['en_name'];
echo "$name: <br/>" . $row['mark'];
}
else {
echo ',' . $row['mark'];
}
}
Output:
John:
60,70,80,90
This will generally be a lot faster than using GROUP BY and GROUP_CONCAT in the database.
Related
I have the following dataset:
ToDo_Name List_ID
-------------------------------
Read book 1
Study English 2
Do excercises 2
Sleep 1
Eat 1
I need to group this data by List_ID creating the array like this (and send it as json to frontend):
$result = array(
array("listId" => 1, array('Read book', 'Sleep', 'Eat ')),
array("listId" => 2, array('Study English', 'Do excercises'))
);
I can't understand - should I do this using some SQL query or PHP array methods? Will be happy to hear any advices...
You could use group concat ..
select list_id, group_concat(ToDo_Name)
from mytable
group by list_id
I can't figure out how to get results from 2 tables, in 1 query result (can't simple JOIN)
I have these 2 tables in my MySQL database:
Table 1: sales
id
name
info
Table 2: users
sale_id
user_id
Now, every sale have different number of assigned users. Some sale have 2 users, some sale have 10 users.
In single row, I need to have columns from sale table, and all assigned users to it (connected with same Sale_id)
I need result, something like this:
enter image description here
Try this :
SELECT s.*,
(SELECT GROUP_CONCAT(u.user_id SEPARATOR ', ')
FROM users u
WHERE u.sale_id = s.id) AS users
FROM sales s
Some insight on your programming language would have been nice.
And yes, as suggested by wogsland and icoder, one typically use joins and loop through results to build en array. But the use of GROUP_CONCAT, as Yoleth pointed out, is what you need. I don’t know if it was the goal here, but it can reduce memory used in the result because there is no row repetition.
SELECT info FROM Sales AS s,
(
SELECT sale_id, GROUP_CONCAT(user_id) AS assigned_users
FROM Users
GROUP BY sale_id) AS u
WHERE s.id=u.sale_id;
In a single query, with a fancy JOIN:
SELECT s.info AS info, u.sale_id AS sale_id, GROUP_CONCAT(u.user_id) AS assigned_users
FROM Sales AS s LEFT JOIN Users AS u
ON s.id=u.sale_id
WHERE sale_id IS NOT NULL GROUP BY u.sale_id;
You can simply join two tables and get query result set like this:
saleID | saleName | userID | userName
1 | Oct Sale | 5 | Tim
1 | Oct Sale | 6 | Nik
2 | Nov Sale | 7 | Bill
Then you can walk each row and build associative array from that data:
$sales = array();
while( $row = mysqli_fetch_assoc($result)) {
if (!array_key_exists($row['saleID'], $sales)) {
$sales[$row['saleID']] = array(
'saleID' => $row['saleID'],
'saleName' => $row['saleName'],
'users' => array()
);
}
array_push($sales[$row['saleID']]['users'], array(
'userID' => $row['userID'],
'userName' => $row['userName']
));
}
Well, MySQL isn't going to return you a nice nested array like that. But you can create it by looping through the result. Assuming your MySQL connection is named $mysqli then try something like
$sales = array();
$result = $mysqli->query("SELECT sales.*, users.user_id FROM sales, users WHERE sales.id = users.sales_id");
while ($row = $result->fetch_assoc()) {
$sales[$row->id]['sales_id'] = $row->id;
$sales[$row->id]['name'] = $row->name;
$sales[$row->id]['info'] = $row->info;
$sales[$row->id]['assigned_users'][] = $row->user_id;
}
I have a database (mysql) table like
id | party | rights | m_id
---+---------+------------+---------
9 |abc | 3,5,6 | ["12,","15,"6"]
20 |xyz | 5,2 | ["6,","2,"9","12"]
21 |xyz 1 | 5,2 | ["6,","9,"12"]
Now I want to make my table in this way
search result for rights 5 is ["12,","15,"6"] ["6,","2,"12"] ["6,","9,"12"]
12 | abc , xyz,xyz1 |
15 | abc|
6 | abc , xyz,xyz1 |
9 | xyz,xyz1 |
Let's start with what I believe you already have. This is an sscce. If you adjust the mysql credentials it should run on your system, creating only a temporary MySQL table. It uses PDO to access the MySQL server. Which API you actually use is not important (i.e. as long as the other API is mysqli, because the mysql_* functions are depreacted ;-))
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8', 'localonly', 'localonly', array(
PDO::ATTR_EMULATE_PREPARES=>false,
PDO::MYSQL_ATTR_DIRECT_QUERY=>false,
PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION
));
setup($pdo);
$statement = $pdo->prepare('
SELECT
*
FROM
soFoo
WHERE
FIND_IN_SET(:right, rights)
');
$statement->execute( array(':right'=>5) );
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
echo $row['party'], ' ', $row['show_ids'], "\r\n";
}
function setup($pdo) {
$pdo->exec('
CREATE TEMPORARY TABLE soFoo
(`id` int, `party` varchar(9), `exc` varchar(13), `rights` varchar(5), `show_ids` varchar(27))
');
$pdo->exec( <<< eos
INSERT INTO soFoo
(`id`, `party`, `exc`, `rights`, `show_ids`)
VALUES
(9, 'Percept', 'Non-Exclusive', '3,5,6', '["12,","15,"6"]'),
(20, 'Tata Sky', 'Non-Exclusive', '5,4,', '["6,","9,"11"]'),
(21, 'Tata Sky', 'Exclusive', '5,4', '["6,","13","15,"2","4","9"]'),
(22, 'Simbiotic', 'Exclusive', '6,2', '["12,","15,"1","6","7","8"]')
eos
);
}
this prints
Percept ["12,","15,"6"]
Tata Sky ["6,","9,"11"]
Tata Sky ["6,","13","15,"2","4","9"]
and is (as I understand the question) as far as you've already got.
Now let's decode the JSON array and check whether it contains the element 9. If it does add inforamtion from the current row to an array called $parties
$parties = array();
/* in one way or another you have a loop where you fetch the records
having '5' in the `rights` field
*/
foreach( $statement as $row ) {
$ids = json_decode($row['show_ids'], true);
if ( in_array('9', $ids) ) {
$parties[$row['id']] = $row['party'];
}
}
var_export($parties);
prints
array (
20 => 'Tata Sky',
21 => 'Tata Sky',
)
But ... from a relational database point of view this is ....suboptimal.
The FIND_IN_SET clause hinders MySQL from using indices effectively; you're searching (compound) data within a single field. It's amazing what the database server implementations can do to improve performance; but it has limits.
And you're also transfering possibly unnecessary data from the MySQL server to the php instance (those records that have 5 in rights but not 9 in show_ids). If possible, this should be avoided. Networks/Network stacks are fast and can be optimized, RAM is cheap ...but again, there are limits.
So, I suggest you look into Database normalization on the one hand and/or document-oriented databases on the other hand.
I am going to give a example below so you can write in your way,
If you are having table below
id | name
1 | a,b,c
2 | b
and expected output like this
id | name
1 | a
1 | b
1 | c
2 | b
Then follow below solutions
If you can create a numbers table, that contains numbers from 1 to the maximum fields to split, you could use a solution like this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
numbers inner join tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
Please see fiddle here.
If you cannot create a table, then a solution can be this:
select
tablename.id,
SUBSTRING_INDEX(SUBSTRING_INDEX(tablename.name, ',', numbers.n), ',', -1) name
from
(select 1 n union all
select 2 union all select 3 union all
select 4 union all select 5) numbers INNER JOIN tablename
on CHAR_LENGTH(tablename.name)
-CHAR_LENGTH(REPLACE(tablename.name, ',', ''))>=numbers.n-1
order by
id, n
an example fiddle is here.
So I have two columns of text in a MySQL database, an example would be as follows:
Name Score
Henry Hodgens 4
Mary Hodgens 8
Jim Servan 2
Jane Servan 4
Hank Servan 6
Sarah Smith 10
Mary Smith 12
Henry Dobbins 2
Henry Jenkins 4
I need to run a query with PHP that can show the average of "Score", based on the most common occurrences of a single word in "Name". So, it would show that "Servan" averages 4, "Henry" averages 3.3, "Hodgens" averages 6, "Mary" averages 10, in the order of most occurrences of the word in "Name".
I hope this makes sense.
This is very difficult to do inside of MySQL. It's great at storing and retrieving relational values, not so great doing this sort of computation. If you don't mind doing this inside of PHP, you can use the code below to count the frequencies.
foreach ($rows as $row)
{
foreach (explode(" ", $row['Name']) as $token)
{
$total[$token] += (int)$row['Score'];
$occurances[$token]++;
}
}
// compute average by taking ($total[$token])/($occurances[$token])
You could do it like this:
SELECT
AVG(t.Score) AS ScorceAvg,
t.name
FROM
(
SELECT
SUBSTRING(Table1.Name,1,INSTR(Table1.Name, ' ')) AS name,
Table1.Score
FROM
Table1
UNION ALL
SELECT
SUBSTRING(Table1.Name,INSTR(Table1.Name, ' ')) AS name,
Score
FROM
Table1
) AS t
GROUP BY
t.name
Here is th perfect thing
SELECT distinct
SUBSTRING(NAME, LOCATE(' ',NAME)+1) as NameED,
(select
avg(score)
from avgscore
where NameED = SUBSTRING(NAME, LOCATE(' ',NAME)+1)) as Score
FROM avgscore
Try this
SELECT sum(Score)/count(Name) as average FROM tablename where Name regexp "Mary";
I have 2 joined tables, each one has a primary key column named id.
SELECT t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id
When I run the query above, both id fields are selected (t1.id and t2.id). My question is, how can I select the correct ID while I am looping through the result set? If I select $result->id, I will get the t2.id. Is there any way that I can get the t1.id also without explicitly selecting it in the query (i.e. t1.id as t1_id?) Also, please, let us know about some of your practices when it comes to naming the primary key columns.
Thanks!
SELECT t1.id as id1, t2.id as id2, t1.*, t2.* from t1 join t2 on t1.fk_id=t2.id
You are probably using mysqli_result::fetch_assoc to return each row of your result set as an associative array. MySQL will let you have two columns with the same name in a query, but these do not map to an associative array the way you want them to—even though the associative array is doing exactly as it should.
Assume two tables, book and author, linked by the junction table book_author. In MySQL, you can run the following query, which returns two id columns:
SELECT b.*, a.*
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+----+-----------------+----+--------------+
| id | title | id | name |
+----+-----------------+----+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+----+-----------------+----+--------------+
If you try to map one of these rows to an associative array, you end up with a single id element in your array:
$row = $result->fetch_assoc();
print_r($row);
Array
(
[id] => 1
[title] => Design Patterns
[name] => Erich Gamma
)
The last id column in the row will overwrite any that precede it. Here’s the second row from the result set:
Array
(
[id] => 2
[title] => Design Patterns
[name] => Richard Helm
)
This is just the same as modifying the value of an element in an associative array;
$row = array();
$row['id'] = 1;
print_r($row);
Array
(
[id] => 1
)
$row['id'] = 2;
print_r($row);
Array
(
[id] => 2
)
If you give each column a unique name in your query, either by doing so in the table itself, or giving it an alias in the query, the problem is avoided:
SELECT b.id AS book_id, b.title,
a.id AS author_id, a.name
FROM book AS b
JOIN book_author AS ba ON ba.book_id = b.id
JOIN author AS a ON a.id = ba.author_id
LIMIT 2;
+---------+-----------------+-----------+--------------+
| book_id | title | author_id | name |
+---------+-----------------+-----------+--------------+
| 1 | Design Patterns | 1 | Erich Gamma |
| 1 | Design Patterns | 2 | Richard Helm |
+---------+-----------------+-----------+--------------+
$row = $result->fetch_assoc();
print_r($row);
Array
(
[book_id] => 1
[title] => Design Patterns
[author_id] => 1
[name] => Erich Gamma
)
Alternatively, you could (and almost certainly should) use prepared statements instead. Although this can get round the problem of duplicate column names, using unique column names in your queries still makes things much easier to read and debug:
$sql = 'SELECT b.*, a.* ' .
'FROM book AS b ' .
'JOIN book_author AS ba ' .
'ON ba.book_id = b.id ' .
'JOIN author AS a ' .
'ON a.id = ba.author_id';
$stmt = $mysqli->prepare($sql);
$stmt->execute();
$stmt->bind_result($book_id, $book_title, $author_id, $author_name);
while ($stmt->fetch()) {
printf("%s, %s, %s, %s\n",
$book_id,
$book_title,
$author_id,
$author_name);
}
You'll often see the primary key for table XXX named xxx_id. This keeps the name of the same "information identifier" the same everywhere: for example in another table YYY, you'll have YYY.xxx_id with a foreign key constraint to XXX.xxx_id. This makes joins easier (you don't have to specify the "on" constraint at all in many databases) and it solves the problem you're running into as well.
I'm not saying you should prefix every column name to create a faux-namespace, but in the case of "id" it is actually useful and descriptive. It is, after all, not just any kind of ID, it's a user ID, site ID, game ID, contact ID, what have you.