PHP multi array (JSON) from SQL query on three tables - php

I hope someone can help me with this specific problem.
I'm new to PHP and MySQL but I'm trying as best as I can. Also, I know that probably similar questions have been asked, but unfortunately I tried every angle I could think of to modify those tutorials/answers to suit my needs, but unfortunately I've failed miserably..
So, here's my problem: I have 3 MySQL tables(contacts, phone numbers, and phone types) for simple phonebook, structured like this:
|ID |name | |ID |cID |tID |number| |ID |type |
-----------------------------------------------------------------------
1 John 1 1 2 123456 1 Home
2 Mary 2 2 1 234567 2 Mobile
3 Sue 3 1 3 213234 3 Work
4 2 2 444321
5 3 2 555543
The first table contains contact names, second holds the number details, and third is "static" table for referencing phone number types.
Now, I'm creating an api for simple crud app in PHP and I'm stuck at creating the array that will give me the result structured as I envisioned:
[
{"ContactID": 1,
"ContactName": "John",
"PhoneNumbers": {
"PhoneNumberID": 1,
"PhoneType": 2,
"PhoneNumber": 123456
}
},
{...},
{...}
]
The query I'm using is:
SELECT contacts.*, pt.type, pn.number, pn.id
FROM contacts
LEFT JOIN phonenumbers pn ON c.ID = pn.cID
LEFT JOIN phonetypes pt ON pn.tID = pt.ID
And now I'm stuck at PHP syntax for creating the array mentioned above. Can you help point me in the right direction please?
Also, as this is a small assignment demonstrating the CRUD functions, I'm not sure about my database, is the three table structure OK? Do I need to change it to something else?
Thanks in advance! Cheers!

If all the tables have ID columns, you need to use an alias in the SQL to distinguish phonenumbers.id from contacts.id. So change the query to:
SELECT contacts.*, pt.type, pn.number, pn.id AS phoneid
FROM contacts
LEFT JOIN phonenumbers pn ON c.ID = pn.cID
LEFT JOIN phonetypes pt ON pn.tID = pt.ID
Here's the code assuming you're using PDO; mysqli will be similar.
$result = array();
while ($row = $stmt->fetch(PDO::FETCH_ASSOC) {
if (!isset($result[$row['ContactID']]) {
// If this is the first row for this contact, create an entry in the results
$result[$row['ContactID']] = array(
'ContactID' => $row['ID'],
'ContactName' => $row['name'],
'PhoneNumbers' => array()
);
}
// Add this phone number to the `PhoneNumbers` array
$result[$row['ContactID']]['PhoneNumbers'][] = array(
'PhoneNumberID' => $row['phoneid'],
'PhoneType' => $row['type'],
'PhoneNumber' => $row['number']
);
}
$result = array_values($result); // Convert from associative to indexed array
// Convert to JSON
echo json_encode($result);
The resulting JSON will look like this:
[
{"ContactID": 1,
"ContactName": "John",
"PhoneNumbers": [
{
"PhoneNumberID": 1,
"PhoneType": "Mobile",
"PhoneNumber": "123456"
},
{
"PhoneNumberID": 3,
"PhoneType": "Work",
"PhoneNumber": "213234"
}
},
{...},
{...}
]
PhoneNumbers is an array of all the phone numbers, and PhoneType is the type name, not its ID. If you only want the type ID, you don't need to join with phonetypes.

Related

MySQL query to retrieve related data from another table

I'm not too experienced with SQL queries (most times I used query builder from frameworks, like CodeIgniter and Laravel). Now, I need to retrieve data from a relational DB which have two tables: one for entity entries and other for complemental data of entities. For example, see below:
tbl_posts
id
name
slug
1
Lorem ipsum
lorem-ipsum
2
Testing post
testing-post
3
Hello world
hello-world
tbl_posts_meta
id
post_id
key
value
1
1
first_name
John
2
1
last_name
Doe
3
1
rating
5
4
2
parent
1
5
2
rating
3
6
3
rating
4
In this example, I need to retrieve an array of objects in this format:
[
{
id: 1,
name: "Lorem ipsum",
slug: "lorem-ipsum",
data: {
first_name: "John",
last_name: "Doe",
rating: 5,
}
},
{
id: 2,
name: "Testing post",
slug: "testing-post",
data: {
parent: 1,
rating: 3,
}
},
{
id: 3,
name: "Hello World",
slug: "hello-world",
data: {
rating: 4,
}
},
]
I've tried using subquery to handle this, but I'm reveiving Cardinality violation: 1241 Operand should contain 1 column(s) error. My query looks like this:
SELECT *,(SELECT * FROM tbl_posts_meta WHERE "post_id" = "tbl_posts"."id") as data FROM tbl_posts;
I've already tried using JOIN, but the result looks more away from expected (I have one "key" property and one "value" property in result, containing the last found entry in tbl_posts_meta).
SELECT * FROM tbl_posts INNER JOIN tbl_posts_meta ON tbl_posts_meta.post_id = tbl_posts.id;
Is there someway I can retrieve desired results with one query? I don't want to do this by applogic (like first retrieving data from tbl_posts and appending another query on "data" property, that returns all data from tbl_posts_meta), since this way may cause database overload.
Thanks!
The code is not shown right in the comment so here it is for you to check, sorry I can't contribute more to the theoretical debate:
select
tp.id,
tp.name,
tp.slug,
(select group_concat(`key`, "=",value separator ';' ) from tbl_posts_meta where post_id= tp.id )as datas
from tbl_posts tp
You can follow these simple steps to get to your needed json:
join the two tables to have centralized data
generate a tbl_posts_meta JSON-like string by aggregating on your columns of interest (tbl_posts.id, tbl_posts.name, tbl_posts.slug) using a GROUP_CONCAT aggregation function. Since your keys can either be integers or strings, you need to do a quick check using the CAST(<col> AS UNSIGNED), which will return 0 if the string is not a number.
use the JSON_OBJECT function to transform each row into a JSON
use the JSON_ARRAYAGG function to merge all jsons into a single json.
WITH cte AS(
SELECT p.id,
p.name,
p.slug,
CONCAT('{',
GROUP_CONCAT(
CONCAT('"',
m.key_,
'": ',
IF(CAST(m.value_ AS UNSIGNED)=0,
CONCAT('"', m.value_, '"'),
CAST(m.value_ AS UNSIGNED)) )
ORDER BY m.id ),
'}') AS data_
FROM tbl_posts p
INNER JOIN tbl_posts_meta m
ON p.id = m.post_id
GROUP BY p.id,
p.name,
p.slug
)
SELECT JSON_ARRAYAGG(
JSON_OBJECT("id", id,
"name", name,
"slug", slug,
"data", CAST(data_ AS JSON)))
FROM cte
Check the demo here.
After some research, testing and reading the queries you posted here, I've finally reached the result I needed. The final query is structured this way:
SELECT *, (
SELECT JSON_ARRAYAGG(
JSON_OBJECT(
key, value
)
) FROM tbl_posts_meta WHERE post_id = post.id
) AS data
FROM tbl_posts;
Translating it to Laravel's Eloquent query builder, and using PHP's serialize/unserialize functions, the end result looks like this:
$posts = Post::query()
->select("*")
->selectRaw('(SELECT JSON_ARRAYAGG(JSON_OBJECT(key, value)) FROM tbl_posts_meta WHERE post_id = posts.id) as data');
return $posts->get()->map(function($post){
$meta = [];
foreach($post->data as $dataItem){
foreach($dataItem as $key => $value){
$meta[$key] = unserialize($value);
}
}
$post->data = $meta;
return $post;
});
Thank you for your support and attention! :)

How to group data from database by particular column?

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

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.

Convert Received Data from SQL and Translate

I am wanting to convert data that is queried from the database to another string. The best way I can explain what I'm trying to do is below:
I have a table called "users". Inside that table I have a column called "rank". Rank is a two-digit integer ranging from 1 to 21.
I want to convert that "1" or whichever number into more understandable text in my php file. So if your rank is set to "1", the output is "User" or what-have-you.
How do I go about doing this? I'm not sure what this process is called. I'm very new to php.
Thank you!
Create a rank table having 2 columns, id and rank:
id | rank
-------------
1 | user
2 | admin
3 | goof
Then JOIN the users table with the rank table:
SELECT u.name, r.rank
FROM users u
LEFT JOIN rank r
ON u.rank = r.id
You can store the ranks title in another table like mentioned by Jay Blanchard or you can define an array with the rank titles like this
$ranks = array(
1 => 'User',
2 => 'Administrator',
3 => 'Moderator'
);
and can echo like this
echo $ranks['rank_id_fetched_using_sql'];
rank_id_fetched_using_sql is supposed to be 1 to 21

Get Column of MySql-Query as headline only once in PHP (even if there are several results)

So, how the name already says or not says, because i did not really know how to name my issue, i am trying to achieve the following thing.
I got a MySQL-Query which delivers me a result like this:
id | name | position
1 Kevin CEO
2 Sarah Developer
3 Daniel Developer
4 Michael Marketing
5 Tom Marketing
Now i need to show the information in Frontend like this:
**CEO**
Kevin
**Developer**
Sarah
Daniel
**Marketing**
Michael
Tom
Which means i need the position-column only once as a headline, and under it i want to show the matching persons for this position.
It is no option for me to do a separate query for every position because in my table i have like 75 of it.
So how can i achieve it to, on one side, get the positions of my query result only once as a headline, and on the other side, get every person's name for this position listed under it.
I hope i was able to explain my problem good enough.
You could loop once through the result and make the position a key in a new array.
$newArray = [];
foreach($myQueryResult as $res){
$newArray[$res['position']][] = $res['name'];
}
This results in this output:
Array (
"CEO" => [ "kevin" ],
"Developer" => [ "Sarah", "Daniel" ],
"Marketing" => [ "Michael", "Tom" ]
)
Use this query:
SELECT `position`, GROUP_CONCAT(name SEPARATOR '<br>') as name FROM `YourTableName` GROUP BY `position`

Categories