How to show summary table with other table data in a row? - php

I have following MySQL scheme :
Method table: PK id, name, comment
Okp table: PK id, FK method_id, comment
Tnved table: PK id, FK method_id, comment
Okp --many to one--> Method <-- many to one-- Tnved
Image representation:
I'm need to show HTML summary table from methods. But each method (each row) could have many data from other tables in fields and I'm need to show them all.
It looks like this:
Methods summary
+-----+-----------+--------------+---------------+-----+---------+
| id | name | All OKP data | All TNVED data| ... | Comment |
+-----+-----------+--------------+---------------+-----+---------+
| 1 | Cloth 1 | 841000 | 5007000000 | ... | Special |
| | | 842000 | 5111000000 | | |
| | | 843000 | 5112000000 | | |
| | | 844000 | ... much more | | |
| | | ...much more | | | |
+-----+-----------+--------------+---------------+-----+---------+
| 2 | Game 76 | 259000 | 6100000000 | ... | Nice |
| | | 816700 | 6200000000 | | |
| | | 880000 | 6400000000 | | |
| | | ...much more | ...much more | | |
+-----+-----------+--------------+---------------+-----+---------+
| ... | ... | ... | ... | | ... |
+-----+-----------+--------------+---------------+-----+---------+
| 999 | T-shirt 3 | 831701 | 6302600000 | ... | Bad |
+-----+-----------+--------------+---------------+-----+---------+
I'm tryed to use SQL JOIN but it looks monstrously with multiple redundancy. So i don't know how to use queries better.
I'm solved it with PHP by recieving related data for each row with separate queries, but this solution is too slow. (In fact i have 1000+ rows).
SO how to query and show such data?
I'm using following method to get information from DB:
//Model-file pseudo-code
$result = array();
$methods = $this->db
->select('*')
->from('method')
->get()
->result();
$i = 0;
foreach ($methods as $method){
$result[$i]['method_t'] = $method;
$result[$i]['okps'] = $this->db
->select('*')
->from('okp')
->where('method_id', $method['id]')
->get()
->result();
$result[$i]['tnveds'] = $this->db
->select('*')
->from('tnved')
->where('method_id', $method['id]')
->get()
->result();
//so on
$i++;
}
I'm using following method to show summary table:
//View-file pseudo-code
//Table header
foreach ($result as $method) {
echo '<tr>';
echo '<td>' . $method['method_t']['id'] . '</td>';
echo '<td>' . $method['method_t']['name'] . '</td>';
echo '<td><ul>';
foreach ($method['okps'] as $okp) {
echo '<li>' . $okp['id'] . '</li>';
//in fact much more data from $okp with separate template
}
echo '</ul></td>';
echo '<td><ul>';
foreach ($method['tnveds'] as $tnved) {
echo '<li>' . $tnved['id'] . '</li>';
}
//in fact much more data from $tnveds with separate template
echo '</ul></td>';
//so on
echo '</tr>';
}
echo '</table>';

Perhaps I'm missing something due to my lack of php skills, but I see no reason why you can't get all the records in one shot and then just use the php to show it how you want. Here's an example, but the php can only best be described as pseudo-code as I have little experience with it:
select
m.id as m_id,
m.name as m_name,
m.comment as m_comment,
o.id as o_id,
o.comment as o_comment,
t.id as t_id,
t.comment as t_comment
from
method m
inner join okp o
on m.id = o.method_id
inner join tnved t
on m.id = t.method_id
order by m.id, o.id, t.id;
For the php, something like the following. I omitted the tvned stuff as you can add that in by just copying the model of the okp part.
$is_first=true;
$m_id_last = 0;
$m_id = 0;
$o_id_last = 0;
$o_id = 0;
$o_str = "";
foreach ($result as $method) {
$m_id_last = $m_id;
$m_id = $method['m_id'];
if ((!is_first) && ($m_id_last != $m_id)) {
echo '<tr>';
echo '<td>' . $m_id . '</td>';
echo '<td>' . $method['name'] . '</td>';
echo '<td><ul>';
echo o_str;
echo '</ul></td>';
echo '</tr>';
$o_str = "";
}
$o_str .= '<li>' . $method['o_id'] . '</li>';
$is_first=false;
}

Why not to use a single SQL query, but be careful when using INNER JOIN.
Because you can have for method #1 related rows in okp table, but haven't any rows for method #1 in tnved table and vice versa.
Beware: in query and php code below I'm using name column instead of comment column for okp and tnved tables.
So, SQL should be smth like this:
SELECT m.id, o.id AS oid, o.name AS oname, t.id AS tid, t.name as tname, m.comment
FROM
(method AS m LEFT JOIN okp AS o ON m.id = o.method_id)
LEFT JOIN tnved AS t ON m.id = t.method_id
ORDER BY m.id
After execution of query above you'll have smth like this:
https://drive.google.com/a/thepaydock.com/file/d/0B3C0Ywfdde5Fa2lPc0FBdlZENG8/view?usp=drivesdk
Next iterate query results rows with PHP:
$output = array();
foreach($results AS $id => $method) {
$output[$id]['okps'] = array();
$output[$id]['tnveds'] = array();
if(!is_null($method['oid'])) {
$output[$id]['okps'][$method['oid']] = array(
'name' => $method['oname'];
);
};
if(!is_null($method['tid'])) {
$output[$id]['tnveds'][$method['tid']] = array(
'name' => $method['tname'];
);
}
}
Next render $output array where $output array key is method_id.
Also php code above can be refactored. I've provided basic example.

The basic issue is the semi-cartesian product. If there are five rows from "okp" (for given m_id) and six rows from "tnved", a join operation is going to match each row from "okp" with each row from "tnved", a total of thirty rows. Six copies of each "okp" row, and five copies of each "tnved" row.
If there are three, four, five tables involved in the join, and each has a dozen or more rows... the resulting conglomeration of duplication becomes unwieldy.
There are several approaches to taming the problem.
If I was going to run a query against "method", and process each row from in a loop, one "m_id" at a time...
For each row from "method", I would execute another single query to get back all of the rows from "okp", "tnved", et al. by combining the individual queries with a UNION ALL. This would eliminate the duplication of the rows. With an appropriate index defined e.g. "... on okp (m_id, id)", the queries should be pretty efficient.
For example:
SELECT okp.m_id AS m_id
, 'okp' AS src
, okp.id AS id
, okp.comment AS comment
FROM okp
WHERE okp.m_id = ?
ORDER BY okp.m_id, okp.id
UNION ALL
SELECT tnved.m_id AS m_id
, 'tnved' AS src
, tnved.id AS id
, tnved.comment AS comment
WHERE tnved.m_id = ?
ORDER BY tnved.m_id, tnved.id
We use a UNION ALL and not a UNION, so the results are just concatenated together, and avoid the overhead of a sort and removal of duplicate rows.
Note that we include a "src" discriminator column. Our code can use the value in this column to determine which query returned it. In this case, each query is from a single table, so we can just identify the table the values are from.
If one of the tables has more columns to return than the others, we add "dummy" expressions to the SELECT list of the other queries. That lets us satisfy the requirements of the UNION ALL where all the sets being combined must have the same number of columns, and the same datatypes.
This approach eliminates a lot of the duplication we get with a semi-cartesian product. This approach requires an "extra" query for each m_id that we process, but if we're only putting 20 m_id on a page, it's only 20 queries to run.
as far as the php for this... execute the query, supplying m_id value for each of the bind placeholders. fetch the results, and each row into the appropriate (emptied) array. rows from "okc" into an $okc[] array, the rows from "tnved" into a $tnved[] array.
Looks like that setup would work for the current "outputting" code.
As long as you're processing a limited number of m_id, and there are appropriate indexes available, this approach can be reasonably efficient.

Related

Output The Contents Of A Pivot / Linking Table In A Nested While Loop - MySQL / PHP PDO

I have a situation where I'm trying to output the contents of a MySQL pivot/linking table from a many-to-many relationship with PHP.
In the code below I have a series of boards that will contain images. The actual board previews are outputted with the first block of PHP, but inside this I need a nested while loop that outputs the images themselves.
The pivot/linking table is called boards_images has two columns board_id and image_id and these are both foreign keys to the boards table and images table. A representation of the tables is given below the main code below.
Because some boards will have an image that is already on other boards I obviously need some type of conditional logic that outputs the image when the related board is present.
Each board preview will only show four images so I will need need to add a LIMIT 4 clause to the MySQL
My Question
What is the best way to approach this, do I:
a) Need to do two database calls one in the parent while loop and one in the nested while loop, or do I need to get all of the info from the MySQL database in the parent while loop with a multiple JOIN?
b) How do I actually output the content of the pivot/linking table? I can't seem to get my head around how to do this.
<?php
// $db_id is a variable created from a user login $_SESSION value
$sql = "SELECT boards.board_id, boards.board_name, users.user_id
FROM boards
JOIN users ON boards.user_id = users.user_id
WHERE users.user_id = :user_id
ORDER BY boards.board_id DESC";
$stmt = $connection->prepare($sql);
$stmt -> execute([
':user_id' => $db_id
]);
// parent while loop that outputs the board name
while ($row = $stmt->fetch()) {
$dbBoardname = htmlspecialchars($row['board_name']);
?>
<div class="board-component">
<h2><?= $dbBoardname; ?></a></h2>
<?php
// --- NESTED INNER WHILE LOOP
$SQL2 = "SELECT boards_images.board_id, boards_images.image_id, images.filename
FROM boards_images
JOIN images ON boards_images.image_id = images.image_id
WHERE boards_images.board_id = :board_id
LIMIT 4";
$stmt2 = $connection->prepare($SQL2);
$stmt2 -> execute([
':board_id' => $dbBoardId
]);
while ($row2 = $stmt2->fetch()) {
$dbImageId = htmlspecialchars($row['image_id']);
$dbImageFilename = htmlspecialchars($row['filename']);
?>
<img src='<?= $wwwRoot . "/images-lib/{$dbImageFilename}" ?>' >
<?php } ?> <!-- end of nested while loop -->
</div>
<?php } ?> <!-- end of parent while loop -->
Representation of the Tables
// 'fk' stands for foreign key
// BOARDS_IMAGES LINKING / PIVOT TABLE
+----------------+----------------+
| board_id (fk) | image_id (fk) |
+----------------+----------------+
| 1 | 23 |
| 1 | 106 |
| 1 | 55 |
| 1 | 22 |
+----------------+----------------+
// BOARDS TABLE
+-------------+--------------+---------------+
| board_id | board_name | user_id (fk) |
----------------------------------------------
| 1 | London | 21 |
| 2 | France | 21 |
+-------------+--------------+---------------+
// IMAGES TABLE
+-------------+--------------------+---------------+
| image_id | filename | user_id (fk) |
---------------------------------------------------+
| 23 | BigBen.jpeg | 21 |
| 106 | TowerBridge.jpeg | 21 |
| 55 | TheMall.jpg | 21 |
| 22 | BuckPalace.jpg | 21 |
+-------------+--------------------+---------------+
// USERS TABLE
+-----------------+----------------+
| user_id | username |
+-----------------+----------------+
| 21 | johndoe |
+-----------------+----------------+
a) Need to do two database calls one in the parent while loop and one
in the nested while loop, or do I need to get all of the info from the
MySQL database in the parent while loop with a multiple JOIN?
You can do one query to get all the data necessary, we can limit the number of images displayed in php, and the join to the users table isn't needed because you have the FK in the boards table:
SELECT boards.board_name, images.filename
FROM boards
INNER JOIN boards_images on boards_images.board_id = boards.board_id
INNER JOIN images on boards_images.image_id = images.image_id
WHERE boards.user_id = :user_id
b) How do I actually output the content of the pivot/linking table? I
can't seem to get my head around how to do this.
With the output of the above query resulting in something like:
board_name | filename
-------------------------------
London | BigBen.jpeg
London | TowerBridge.jpeg
London | TheMall.jpg
London | BuckPalace.jpg
France | BigBen.jpeg
France | TowerBridge.jpeg
France | TheMall.jpg
France | BuckPalace.jpg
Your php loop could look something like this:
<?php
// $db_id is a variable created from a user login $_SESSION value
$sql = "SELECT boards.board_name, images.filename
FROM boards
INNER JOIN boards_images on boards_images.board_id = boards.board_id
INNER JOIN images on boards_images.image_id = images.image_id
WHERE boards.user_id = :user_id";
$stmt = $connection->prepare($sql);
$stmt -> execute([
':user_id' => $db_id
]);
$dbBoardname_last = '';
$imgcount = 0;
while ($row = $stmt->fetch()) {
$dbBoardname = htmlspecialchars($row['board_name']);
$dbImageFile = "$wwwRoot/images-lib/" . htmlspecialchars($row['filename']);
//if the boardname is the same as the previous row only add images.
if($dbBoardname != $dbBoardname_last)
{
//reset the image count for new boards
$imgcount = 0;
echo "<div class=\"board-component\"><h2>$dbBoardname</a></h2>";
}
//By counting the images within the loop we can avoid using multiple or nested queries to the DB
if($imgcount < 4) {
echo "<img src=\"$dbImageFile\">";
}
$imgcount++;
if($dbBoardname != $dbBoardname_last)
{
echo '</div>';
}
//record the last board_name to check if a new board element should be created
$dbBoardname_last = $dbBoardname;
}
?>
Note: hopefully this code works as you intended but long term I think best practice would be to parse the SQL output into a JSON object and iterate over that instead, the code might come out cleaner
The approach of running a query, looping the results and for each result, running another query is a suboptimal approach, but it works. You may want to try that out, because it's simple and it is very much possible that the performance issues you are worried about would never materialize, kindly read this: https://stackify.com/premature-optimization-evil/
The disadvantage, of course, is that, instead of running a single command, you run n + 1 command (assuming that the number of boards is n). Queries are costly, so, if you already encountered some performance issues related to this, or you want to write very performant code, then you can:
write a single query
the FORM clause's first table would be boards
LEFT JOIN boards_images and images, with the appropriate criteria
the resulting record would contain the boards and images data, defaulting to null if there are not enough matches
SELECT ...
FROM boards
JOIN users
ON boards.user_id = users.user_id
LEFT JOIN boards_images bi1
ON boards.board_id = bi1.board_id
LEFT JOIN images i1
ON bi1.image_id = i1.image_id
LEFT JOIN boards_images bi2
ON boards.board_id = bi2.board_id AND bi2.board_id NOT IN (bi1.board_id)
LEFT JOIN images i2
ON bi2.image_id = i2.image_id
LEFT JOIN boards_images bi3
ON boards.board_id = bi3.board_id AND bi3.board_id NOT IN (bi1.board_id, bi2.board_id)
LEFT JOIN images i3
ON bi3.image_id = i3.image_id
LEFT JOIN boards_images bi4
ON boards.board_id = bi4.board_id AND bi4.board_id NOT IN (bi1.board_id, bi2.board_id, bi3.board_id)
WHERE users.user_id = :user_id
You loop the records returned and use the returned values. I have left out the SELECT clause's content for you to fill it, you will need boards and images data there, with the images data columns ideally suffixed by the image index, so you can iterate the index and build the column names inside the loop.

What is the optimized/best way to retrieve data from two tables?

I have two tables:
post table:
|post_id | post_title |
+--------+------------+
| 1 | Post 1 |
| 2 | Post 2 |
| 3 | Post 3 |
post_creator table:
|post_id | creator |
+--------+---------+
| 1 | John |
| 1 | Smith |
| 1 | Mike |
| 2 | Bob |
| 3 | Peter |
| 3 | Brad |
When I join these tables it looks like this.
SELECT *
FROM post p
JOIN post_creator c ON p.post_id = c.post_id
|post_id | post_title | post_id | creator|
+----------------------------------------+
| 1 | Post 1 | 1 | John |
| 1 | Post 1 | 1 | Smith |
| 1 | Post 1 | 1 | Mike |
| 2 | Post 2 | 2 | Bob |
| 3 | Post 3 | 3 | Peter |
| 3 | Post 3 | 3 | Brad |
I want to grab each post with it's creators. But in this case my joined result has same post repeated again and again because of the creator.
What I did was first I fetched all data from post table. Then I looped that result and inside the loop I fetched all creators of each posts. But in this case it query again and again for each content to get the creators.
$sql = "SELECT * FROM post";
$stmt = $conn->prepare($sql);
$stmt->execute();
$res = $stmt->fetchAll(PDO::FETCH_OBJ);
$dataObj = new stdClass;
$dataArr = [];
foreach($res as $post){
$sql = "SELECT creator FROM post_creator WHERE post_id=$post->post_id";
$stmt = $conn->prepare($sql);
$stmt->execute();
$creators = $stmt->fetchAll(PDO::FETCH_OBJ);
$dataObj->post_id = $post->post_id
$dataObj->post_title = $post->title
$dataObj->creators = $creators;
array_push($dataArr, $dataObj);
}
So finally my dataArr has this kind of a structure.
[
{
post_id: 1,
post_title: Post 1,
creators:[John, Smith, Mike]
},
{
post_id: 2,
post_title: Post 2,
creators:[Bob]
},
{
post_id: 2,
post_title: Post 1,
creators:[Peter, Brad]
},
]
This is what I wanted. Now I can loop this and render to a view.
Are there any optimized/better way to get this result without looping and querying again and again?
I think you need to use group_concat to group your creators.
SELECT p.post_id, post_title, group_concat(creator)
FROM post p
JOIN post_creator using(post_id)
group by p.post_id
Additionally, this:
$sql = "SELECT creator FROM post_creator WHERE post_id=$post->post_id";
$stmt = $conn->prepare($sql);
$stmt->execute();
is improper usage of a prepared statement. It should be written as:
$sql = "SELECT creator FROM post_creator WHERE post_id=?";
$stmt = $conn->prepare($sql);
$stmt->execute(array($post->post_id));
if it were needed, but it is not. Always bind values, never put direct to SQL.
I'd say there are 3 different roads you could follow, all of whom have some benefit or another.
Option 1. Simple SELECT query with JOIN (and overlapping rows)
This is more or less what you've already tried, with the first query you listed; which resulted in duplicate rows.
It's fairly trivial to modify your application code to deal with the dupes, and simply fold the creators into the same array/object. The overhead is almost nil as well. From a relational database design point-of-view, this method is still the best practice.
SELECT p.post_id
, p.post_title
, c.creator
FROM post p
LEFT JOIN post_creator c
ON p.post_id = c.post_id
ORDER BY p.post_id ASC
.
/* $rows = ...query...; */
$posts = [];
foreach ($rows as $row) {
if (!isset($posts[( $row['post_id'] )])) {
// this is a new post_id
$post = [];
$post['id'] = $row['post_id'];
$post['creators'] = [];
$post['creators'][] = $row['creator'];
$posts[( $row['post_id'] )] = $post;
} else {
// this is just an additional creator
$posts[( $row['post_id'] )]['creators'][] = $row['creator'];
}
}
Option 2. Multivalue columns (arrays or json)
A slightly more pragmatic solution for non-purists can be to have your query produce output columns which contain more than one value. This generally means either a JSON or an ARRAY column. The exact details depend on your choice of database system.
In either case, you'd combine it with the SQL GROUP BY feature.
Let's assume you use MySQL and prefer the JSON type; you'd then go with a query such as:
SELECT p.post_id
, p.post_title
, JSON_ARRAYAGG(c.creator) AS creators
FROM post p
LEFT JOIN post_creator c
ON p.post_id = c.post_id
GROUP BY p.post_id
ORDER BY p.post_id ASC
This way, you'll only receive one record per post, and you'll get a value such as ['Mike', 'Paul', 'Susan'] which json_decode() can turn into a proper PHP array.
Option 3. Fullblown documents
Another alternative that kind of builds upon option #2 is to go entirely with JSON, and abandon the relational recordset altogether.
Most modern DBMS have plenty of JSON functionality and the format you yourself listed as dataArr, could be fully produced by the database in response to a single SELECT query.
This way, the query would always result in just 1 row with 1 single column, which holds the entire dataArr combining all those posts (which again, can be turned into a native PHP array or object tree with json_decode, just like before).
While the result of this method can be very neat (depending on the way your application is written), some may wonder why you're using an RDBMS and not something like MongoDB.
Overall i'd recommend Option 1.

Join tables horizontally and dynamically

I need some help from joining tables horizontally my tables are
+---------+---------+
| Candidates Table |
+---------+---------+
| can_id | Name |
+---------+---------+
| 1 | Liza |
| 2 | Sarah |
| 3 | Jane |
| | |
+---------+---------+
+---------+---------+
| Judges Table |
+---------+---------+
| id | Name |
+---------+---------+
| 1 | judge1 |
| 2 | judge2 |
| 3 | judge3 |
+-------------------+
+---------+---------------+--------+-------+
| Score Table |
+---------+-------+------------------------|
| sco_id | can_id| jud_id |crit_id |score|
+---------+--------+-----------------------+
| 1 | 1 | 2 | 1 | 87 |
| 2 | 1 | 3 | 1 | 89 |
| 3 | 1 | 1 | 1 | 80 |
+------------------------------------------+
I need an output of something like this one..
+---------+---------------+-------------+
| Score board |
+---------+---------+-------------------|
| Name | judge1 | judge2 | judge3 |
+---------+---------+-------------------|
| Liza | 80 | 87 | 89 |
|some data|some data|some data|some data|
|some data|some data|some data|some data|
+---------------------------------------+
notes: crit_id is criteria id from criteria table.
Normally I would use some joins and subqueries but my problems is I need the output dynamically where in if I add a new judges it will automatically generate a new column. I need at least 1 candidate data with all of the judges scores then just loop it with parameters on php to get the other candidates data something like
php loop start
<td>name</td>
<td>judge1 score</td>
<td>judge2 score</td>
php end loop
or if i could get the whole candidates table with judges score much better for me not to loop them per candidate
I've tried to research similar questions like
Concatenate more than two tables horizontally in SQL Server
I've tried to code myself but I got stuck with joining the judges..
SELECT s.sco_id,c.Name,c.Municipalities
FROM `tbl_scoring` s
LEFT JOIN tbl_candidates c ON c.`can_id` = s.`can_id`
WHERE s.can_id = 11
AND crit_id = 1
ORDER BY s.jud_id asc
I need a query that would generate dynamically depending on the number of judges either get candidate data with scores of judge then loop it on php or much way better if i get all the data without looping
Initialize the following arrays:
$judges = [];
$scores = [];
$candidates = [];
Then execute your query, and loop the results. Set those values for each iteration:
$judges[$row['jud_id']] = 1;
$candidates[$row['can_id']] = $row['Name'];
$scores[$row['can_id']][$row['jud_id']] = $row['score'];
Now you want to get the participant judges names, so let's run a SQL query:
$sql = 'SELECT Name FROM judges WHERE id IN (' . implode(',', array_keys($judges)) . ')';
And on every iteration set the judge's name in the $judges array:
$judges[$row['id']] = $row['Name'];
Then for the output:
echo '<tr>';
echo '<td>Name</td>';
ksort($judges);
foreach ($judges as $name) {
echo '<td>Judge: ' . $name . '</td>';
}
echo '</tr>';
foreach ($scores as $candidateId => $data) {
echo '<tr>';
echo "<td>$candidates[$candidateId]</td>";
ksort($data);
foreach ($data as $score) {
echo "<td>$score</td>";
}
echo '</tr>';
}
I used ksort on $judges and $data so the score will fit each judge.
first, we retrieve the judges' ids and name that exist on the score table.
$judges = [];
$query = "SELECT id, name FROM Judges WHERE id IN ( SELECT DISTINCT jud_id FROM Score )";
// execute the query and store the results in the $judges array.
we retrieve the candidates' ids and name that exist on the score table.
$candidates = [];
$query = "SELECT id FROM Candidate WHERE id IN ( SELECT DISTINCT can_id FROM Score )";
// execute the query and store the results in the $candidates array.
then, we join the candidate and score table.
$candidate_score = [];
$query = "SELECT Candidate.name, Candidate.id as candidate_id , Score.jud_id, Score.score FROM Candidate JOIN Score ON Score.can_id = Candidate.id";
// execute the query and store it in the $candidate_score array.
now, for each candidate, we fill its score on the $score_board array.
$score_board = [];
foreach ( $candidates as $candidat )
{
$score_board[$candidat] = [];
foreach ( $judges as $judge )
{
$judge_name = $judge['name'];
$judge_id = $judge['id'];
$score_board[$candidat][$judge_name] = get_judge_score($candidate_score,$candidat,$judge_id);
}
}
this is how the get_judge_score will work:
function get_judge_score ( $scores , $candidate , $judge )
{
$score_filtred = array_filter($scores, function ($score) use ($candidate,$judge) {
return $score['jud_id'] == $judge && $score['candidate_id'] = $candidate;
});
return count($score_filtred) > 0 ? $score_filtred[0]['score'] : 0;
}

How to find the minimum value in each table column and display a class in php

Hi I pulled a table from an external website using YQL. The table contains a list of times such as: But the number of columns can change to include more columns.
| Name | T1 | T2 | T3 |
|--------|--------|--------|--------|
| Name 1 | 23.234 | 45.234 | 16.456 |
| Name 2 | 23.389 | 44.322 | 15.222 |
| Name 5 | 22.890 | 44.221 | 15.345 |
What I'm trying to do is get the lowest value from each column. I've been able to get it by using this function.
<?php
$sectim = [];
for ($st=1; $st < count($allRows); $st++) {
$sectim[] = $phpObj->query->results->tbody->tr[1]->td[6]->content;
}
echo "<pre>"; print_r($sectim); echo "</pre>";
echo "<pre>"; print_r(min($sectim)); echo "</pre>";
?>
This gives me a list of all the times in that column and gives me the minimum. What I am struggling with is how to do it in a way that I can get the minimum from each table columns given the number of columns is not fixed and can either be 3 columns 4 or even 9 columns. I'm thinking of using either another for statements to loop through all the columns but I don't know whether that will work.
You can use this query
select min(T1) as column1,min(T2) as column2,min(T3) as column3 from tabel
This will give you minimum value of each column.
If you are looking For build dynamic query :
$array = array(
'column1' => 'T1',
'column2' => 'T2',
'column3' => 'T3'
);
$column_binder = array();
foreach ($array as $key=>$value){
$column_binder[] = " min($value) as $key ";
}
$query = "SELECT ".impode(",", $column_binder)." From tabel_name";

Efficient way to query SQL and format to CSV

I have a MySQL database that I am keeping temperature readings from several different sensors. I initially thought of using three different tables to store my data:
mysql> select * from sensor_info;
+----+------------------+------+--------+
| id | address | name | active |
+----+------------------+------+--------+
| 1 | 28D684CD02000057 | NULL | 1 |
| 2 | 28099B49030000D8 | NULL | 1 |
| 3 | 28339ACD0200004B | NULL | 1 |
+----+------------------+------+--------+
mysql> select * from data_period limit 4;
+----+---------------------+
| id | ts |
+----+---------------------+
| 1 | 2012-06-30 09:35:02 |
| 2 | 2012-06-30 09:36:22 |
| 3 | 2012-06-30 09:37:46 |
| 4 | 2012-06-30 09:40:36 |
+----+---------------------+
mysql> select * from data_points limit 4;
+----+-------------+-----------+-------+
| id | data_period | sensor_id | data |
+----+-------------+-----------+-------+
| 1 | 1 | 1 | 77.90 |
| 2 | 1 | 2 | 77.34 |
| 3 | 1 | 3 | 77.56 |
| 4 | 2 | 1 | 78.01 |
+----+-------------+-----------+-------+
What I'm trying to do is to take my stored data and put it into a CSV file so I can display it using dygraphs Javascript library. I need to get my data into a format like this:
date,temp1,temp2,temp3
2012-06-30 09:35:02,77.90,77.34,77.56
2012-06-30 09:36:22,78.01,77.36,77.59
....
Every way I start to do this (using PHP), I seem to make this overly complicated and have to put queries inside loops inside loops. Am I making this harder on myself than I need to?
Will most of the work be done using the queries or using PHP? Down the road, I will also want to add code that will place NULL in the CSV if a temperature reading is missing from a particular timestamp.
I'm not looking for a very specific answer, I just want to know what direction I should go. I don't even know how to start to format my data from the database or if I should try looking at a different format to store my info in the database.
I'd run a single select query to join the lot together. You can use an outer join where there might not be data.
SELECT data_period.ts AS date, dp1.data AS temp1, dp2.data AS temp2, dp3.data AS temp3
FROM data_period
LEFT OUTER JOIN data_points AS dp1 ON dp1.data_period=data_period.id AND dp1.sensor_id=1
LEFT OUTER JOIN data_points AS dp2 ON dp2.data_period=data_period.id AND dp2.sensor_id=2
LEFT OUTER JOIN data_points AS dp3 ON dp3.data_period=data_period.id AND dp3.sensor_id=3
(See: SQL Fiddle Example )
That should give you a single set of results that you can just loop through.
If you really want MySQL to do most of the work, you can change the first line to (I think)
SELECT data_period.ts +','+ IFNULL(dp1.data,'NULL') +','+ IFNULL(dp2.data,'NULL') +','+ IFNULL(dp3.data,'NULL')
To Synthesize the comment and the answer and to get this out to a file:
SELECT data_period.ts AS date, dp1.data AS temp1, dp2.data AS temp2, dp3.data AS temp3
INTO OUTFILE '/home/user/output.csv'
FIELDS TERMINATED BY ','
OPTIONALLY ENCLOSED BY '"'
ESCAPED BY '\\'
LINES TERMINATED BY '\n'
FROM data_period LEFT OUTER JOIN data_points AS dp1 ON dp1.data_period=data_period.id
AND dp1.sensor_id=1 LEFT OUTER JOIN data_points AS dp2 ON
dp2.data_period=data_period.id AND dp2.sensor_id=2 LEFT OUTER JOIN data_points AS dp3
ON dp3.data_period=data_period.id AND dp3.sensor_id=3;
To do this you can use two SQL statements giving an outer and an inner loop. The first statement
select ts from data_points as point
inner join data_period on data_period = data_period.id
group by ts
order by ts
will get a list of the dates that you have data for.
The second one on the inner loop will get a list of data values ordered by the sensor id - if you have missing values in the data_points then this list will be wrong and have gaps; so if you do have gaps you may need to do an extra step to get the list of sensors and then populate this list from the data coming back from the query.
The following example illustrates how you could proceed with this.
// get the outer list
$sql = "select ts from data_points as point
inner join data_period on data_period=data_period.id
group by ts
order by ts";
$result = mysql_query($sql);
$result || die(mysql_error());
while($row = mysql_fetch_row($result))
{
// get the date for the second query
$date = $row[0];
$sql = "select data from data_points as point
inner join data_period on data_period=data_period.id
inner join sensor_info on sensor_id = sensor_info .id
where ts = '$date'
order by sensor_id";
$result_1 = mysql_query($sql);
// now create an array from the values - we could use mysql_fetch_array to do this
// but this way allows us the possibility of extending the processing to
// take into account missing values.
$vals = array();
while($row_1 = mysql_fetch_row($result_1)) {
$vals[]=$row_1[0];
}
print "$date,".join(",",$vals)."\n";
}
I'd recommend just doing the csv formatting in php after the fact.
Here is a handy function I found online and really love using. And if you wrap your SQL calls in a class then you can just have this method available after you query. :)
Note I modified this to me MSSQL but its easy to replace it with just mysql.
public final function SQL2CSV() {
// set initial .csv file contents
$CSV = array();
$rowid = 0;
//This would be your query.
$res = $this->fetchQuery();
// get column captions and enclose them in doublequotes (") if $print_captions is not set to false
for ($i = 0; $i < mssql_num_fields($res); $i++) {
$fld = mssql_fetch_field($res, $i);
$colnames[] = $fld->name;
}
// insert column captions at the beginning of .csv file
$CSV[$rowid] = implode(",", $colnames);
// iterate through each row
// replace single double-quotes with double double-quotes
// and add values to .csv file contents
if (mssql_num_rows($res) > 0) {
while ($row = mssql_fetch_array($res, MSSQL_NUM)) {
$rowid++;
for ($i = 0; $i < sizeof($row); $i++)
//$row[$i] = '"'.str_replace('"', '""', $row[$i]).'"';
$row = str_replace (',', '', $row);
$CSV[$rowid] = "\n".implode(",", $row);
}
}
RETURN $CSV;
}

Categories