Combining mySQL tables into one row? - php

I have four tables that are linked.
images Table
cid link
=======================
1 something.jpg
2 else.jpg
terms Table
cid term is_attr
================================
1 Location 0
2 Caption 1
3 Camera Lens 0
tags Table
cid Name term_id
==============================
1 somewhere 1
2 BFE 1
3 A word 2
linked_tags Table
cid photo_id tag_id
==========================
1 1 1
2 1 2
3 1 3
if a Term is_attr == 1 the image should only have ONE entry in the linked_tags table for that term.
If I were to query the image table and get the tags at the same time, how would I do that?
I'd like to have (something) this returned:
_____________________________________________________________________________
cid |link |attributes |tags |
=========|==================|==================|====================|
1 |something.jpg |__________________|____________________|
| ||term |value |||term |value ||
| ||========|=======|||========|=========||
| ||caption |A word |||Location|somewhere||
| || | |||Location|BFE ||
This is what I am looking for (PHP side):
//Row 1
array(
'link' => "something.jpg",
'attributes' => array('caption'=>"A word"),
'tags' => array('Location'=>array('somewhere','BFE'))
);
//Notice 'caption' points to a string and 'location' points to an array
//Row 2
array(
'link' => "else.jpg",
'attributes' => array(),
'tags' => array()
);
// OR
array(
'link' => "else.jpg"
);
// OR
array(
'link' => "else.jpg",
'attributes' => array('caption'=>""),
'tags' => array();
);

SELECT i.*, GROUP_CONCAT(tag.name SEPARATOR ', ')
FROM image i
JOIN tag
ON tag.image_id = i.id
GROUP BY
i.id
Update:
Tabular format assumes same number of columns in each record, all having the same type across the records.
Your task is best done in three queries:
SELECT cid, link
FROM image
WHERE cid = 1
(cid and link)
SELECT t.name, tag.name
FROM linked_tags lt
JOIN tags
ON tags.cid = lt.tag_id
JOIN terms t
ON t.cid = tags.term_id
WHERE lt.photo_id = 1
AND t.is_attr = 1
(attributes)
SELECT t.name, tag.name
FROM linked_tags lt
JOIN tags
ON tags.cid = lt.tag_id
JOIN terms t
ON t.cid = tags.term_id
WHERE lt.photo_id = 1
AND t.is_attr = 0
(tags)

Most likely you'd want to the GROUP_CONCAT function.
edit:
phooey. Quassnoi beat me to the punch. Just as a warning: GROUP_CONCAT's output is length limited (usually 1024 bytes by default), but you can override it with the group_concat_max_length run-time setting.

Am I missing something, or does this just need a few LEFT JOINs?
$query = 'SELECT images.cid, images.link, terms.term, tags.name, terms.is_attr FROM images LEFT JOIN linked_tags ON images.cid = linked_tags.photo_id LEFT JOIN tags ON linked_tags.tag_id = tags.cid LEFT JOIN terms ON tags.term_id = terms.cid WHERE 1 ORDER BY images.cid';
$result = mysql_query($query);
The result set will give you multiple rows for each image, and you can use PHP to parse the data into whatever format you need like this.
$image_list = array();
while ($line = mysql_fetch_assoc($result)) { // fetch mysql rows until there are no more
if (!isset($image_list[$line['cid']]) { // checks to see if we have created this image array row yet
// if not, create new image in image array using cid as the key
$image_list[$line['cid']] = array('link' => $line['link'], 'attributes' => array(), 'tags' => array());
}
if ($line['term'] != null) { // if there are no linked attributes or tags, move on to the next image leaving both arrays empty, otherwise...
if ($line['is_attr']) {
// if attribute row, add attribute using term as key
$image_list[$line['cid']]['attributes'][$line['term']] = $line['name'];
} else {
// if tag row, add tag using term as key
$image_list[$line['cid']]['tags'][$line['term']] = $line['name'];
}
}
}
If I am reading you correctly, this should output exactly what you are looking for.

Related

group by and group_concat alternative

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.

SELECT results from another table AS column array

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;
}

PHP/MySQL Sort Multi-dimensional array

I'm trying to sort an array in to a three-deep array. This is my current query:
SELECT * FROM question
INNER JOIN category ON question.category_id = category.id
INNER JOIN difficulty ON question.difficulty_id = difficulty.id
Expected result is something like:
array(
'1' => array( // category id 1
'1' => array( // difficulty id 1
'1' => array('...'), // question id 1
'2' => array('...') // question id 2
),
'2' => array(
'3' => array('...'),
'4' => array('...')
)
)
)
I did have the following:
foreach($categories as $category) {
foreach($difficulties as $difficulty) {
foreach($questions as $question) {
if ($question['category_id'] == $category['id'] && $question['difficulty_id'] == $difficulty['id']) {
$feed[$category['id']][$difficulty['id']][$question['id']] = $question;
}
}
}
}
But there will be 10,000+ questions and performance will be bad so is there a way I can do this with one query and fewer loops?
Basically you could just return your query and order by the ids like so:
Category_ID Difficulty_ID Question_ID
0 0 0
0 0 1
1 0 2
1 3 3
1 3 4
2 0 5
2 1 6
Then parse everything in a while:
each time the category_ID changes add a new category with empty difficulty and reset previous difficulty
each time the difficulty changes add new difficulty to category with empty question
each time add the question to current difficulty.
To store this structure performantly in local storage:
define a unique delimiter (note: IE doesn't support control characters, this also means you can't store binary data without encoding it before, e.g. base64)
load each row of each table like this:
key: unique table prefix + id
value: columns (delimited with the delimiter defined before)
The easiest way to return a whole table at once is to define a second delimiter and then have some slightly ugly query in the form of:
SELECT id||delimiter||col1||delimiter||...||colN FROM ...
And then put it all together with a list aggregation using the second delimiter (group_concat() in mysql).
Sometimes you need maps (for N to M relations or also if you want to search questions by difficulty or category), but because each question only has one category and difficulty you are already done.
Alternative
If the data is not too big and doesn't change after login, then you can just use the application cache and echo your stuff in script tags.

Join 1 Row to Multiple Rows in PDO

Here is my scenario:
Database Name: Children
+-------------+---------+---------+
| child_id | name | user_id |
+-------------+---------+---------+
1 Beyonce 33
2 Cher 33
3 Madonna 33
4 Eminem 33
Database Name: Parents
+-------------+---------+---------+
| parent_id | child_id | parent_name |
+-------------+---------+---------+
1 1 Obama
2 1 Michelle
3 4 50cents
4 4 Gaga
Desired Output:
+-------------+---------+---------+
| child_id | name | parent Name |
+-------------+---------+---------+
1 Beyonce Obama (Row 1) Michelle (Row 2)
PHP SQL Query in PDO:
$sql = "SELECT Children.child_id, Children.name, Parents.parent_name
FROM Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = ?
";
$stmt = $db_PDO->prepare($sql);
if($stmt->execute(array($userId))) // $userId defined earlier
{
// Loop through the returned results
$i = 0;
foreach ($stmt as $row) {
$fetchArray[$i] = array (
'childId' => $row['child_id'],
'childName' => $row['name'],
'parentName' => $row['parent_name'],
// How do I save the multiple parents from other rows here ????
);
$i++;
}
}
How can I run a query that Joins 1 row to multiple rows in second table in PDO? I have read other topics here but I am unsure. Is it easier to add a second query that gets the linked parents for each child_id separately in a loop? I am worried that will be too much query. Can someone help me solve this?
Well, took me some fiddling to test it all out but here you go.
Unfortunately one cannot easely pivot tables in mysql but there are alternatives.
http://sqlfiddle.com/#!9/1228f/26
SELECT GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) FROM Parents where Parents.child_id=1
;
SELECT
Children.child_id,
Children.name,
GROUP_CONCAT(
CONCAT_WS(':', Parents.parent_id,Parents.parent_name) ) as parents
FROM
Children
LEFT JOIN Parents
ON Children.child_id = Parents.child_id
WHERE Children.user_id = 33
Group by Children.child_id
This query uses the group concat to concatenate all resulsts we want into a colon seperated string with the values we want, and comma's between the individual fields.
We could do some tricky magic to make them individual fields but that would break our php because we wouldnt know how much fields each query would return(adopted, orphan, no known parents, etc...)
In php you could feed them into an object
$parents = array();
$loop1 = explode(',',$row['parents']);
foreach($loop1 as $parentset) {
$parentdetail = explode(":",$parentset);// decide yourself how much detail you want in here... I jsut went with name and id.
$parent = new stdClass();
$parent->id = $parentdetail[0];
$parent->name = $parentdetail[1];
array_push($parents,$parent);
}
var_dump($parents);
Execute the below query . You will get the output as required, i just used the group by which will group the records as per the selected column
select a.child_id, name ,group_concat(parent_name) from children a, parents b where a.child_id =b.child_id group by a.child_id
HI this query works only if you are passing child id ,
select a.child_id, name ,group_concat(parent_name ) parent_name from children a, parents b where a.child_id =b.child_id and a.child_id=1
here i am using a function called group_concat which is used for concatinating the rows.It automatically takes the rows whose count is greater than 1.So no need of the extra code again

Mysql query with multiple ID columns and php

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.

Categories