The amount of columns in a table is based upon the amount of data in a table. The amount of rows in a table is based on the amount of rows in a different table. An example of these tables are below
environments patches
id | name id | name
____|______ ____|____________
1 | dev 1 | firstPatch
2 | test 2 | secondPatch
3 | prod 3 | thirdPatch
The end result of what I'm looking for would be the following <table>
_________________________________
| Patch Name | dev | test | prod |
|_____________|_____|______|______|
| thirdPatch | | | |
| secondPatch | x | | |
|_firstPatch__|_____|______|______|
I understand how I could use a while() loop to build the table headers. And I understand how I could use a while() to build the rows.
echo "<tr>";
while ($env = $listEnvs->fetch_assoc()){
echo "<th>".$env['name']."</th>"
}
echo "</tr>";
--AND--
while ($patch = $listPatches->fetch_assoc()){
echo "<tr><td>".$patch['name']."</td></tr>";
}
What I am struggling with is that the cells for each patch/environment pair are going to have data that is pulled from several other tables. So knowing the patch.id as well as the environment.id is important. Example the cell with the x is patch.id = 2, environment.id = 1 When constructing the table, how can I make sure each cell has this necessary information?
Try to create an array like $content[$patch.id][$environment.id].
Then you can add echo($content[$patch.id][$environment.id]) to your while of the $patch. You walk through the patches, so $patch.id is known. For $environment.id, you should make an array with all environment.id's and walk through it with $environment.id[i], where $i should be increased with each new column (and set to 0 when a new row is started).
In (bad) pseudo-code:
walkthrough environments {
save array with environments
echo table headers
}
walk through patches {
echo table 'row headers'
walk through environment array {
echo content[patch.id][environment.id]
}
}
Related
I am saving many waypointIDs, bearings, and distances in three columns in a single row of my database. Each column stores it's respective item separated by a comma.
WaypointIds: 510,511,512
Bearings: 65,50,32
Distances: 74,19,14
I think I might have coded myself into a corner! I now need to pull them from the database and run through adding them to a table to output on screen.
I'm using the following to put the corresponding columns back into an array but am a little stuck with where to go after that, or if that is even the way to go about it.
$waypointArrays = explode (",", $waypointsString);
$bearingArrays = explode (",", $bearingsString);
$waypointsString & $bearingsStrings are the variables set by my database call.
I think I need to add them all together so I can iterate through each one in turn.
For example, waypointId 510, will have bearing 065, and distance 0.74.
I think I need an associative array but not sure how to add them all together so I can run through each waypoint ID and pull out the rest of the entry.
e.g. for each waypointId give the corresponding bearing and waypoint.
I have checks in place to ensure that as we add waypoints/bearings/distances that they don't fall out of step with each other so there will always be the same number of entries in each column.
Don't continue with this design: your database is not normalised and therefore you are not getting any benefit from the power that a database can offer.
I don't think working around this problem by extracting the information in PHP using explode, ..etc is the right way, so I will clarify how your database should be normalised:
Currently you have a table like this (possibly with many more columns):
Main table: route
+----+---------+-------------+----------+-----------+
| id | Name | WaypointIds | Bearings | Distances |
+----+---------+-------------+----------+-----------+
| 1 | myroute | 510,511,512 | 65,50,32 | 74,19,14 |
| .. | .... | .... | .... | .... |
+----+---------+-------------+----------+-----------+
The comma-separated lists violate the first normal norm:
A relation is in first normal form if and only if the domain of each attribute contains only atomic (indivisible) values, and the value of each attribute contains only a single value from that domain.
You should resolve this by creating a separate table for each of these three columns, which will have one record for each atomic value
Main table: route
+----+---------+
| id | Name |
+----+---------+
| 1 | myroute |
| .. | .... |
+----+---------+
new table route_waypoint
+----------+-------------+------------+----------+
| route_id | waypoint_id | bearing_id | distance |
+----------+-------------+------------+----------+
| 1 | 510 | 65 | 74 |
| 1 | 511 | 50 | 19 |
| 1 | 512 | 32 | 14 |
| 2 | ... | .. | .. |
| .. | ... | .. | .. |
+----------+-------------+------------+----------+
The first column is a foreign key referencing the id of the main table.
To select the data you need, you could have an SQL like this:
select route.*, rw.waypoint_id, rw.bearing_id, rw.distance
from route
inner join route_waypoints rw on rw.route_id = route.id
order by route.id, rw.waypoint_id
Now PHP will receive the triplets (waypoint, bearing, distance) that belong together in the same record. You might need a nested loop while the route.id remains the same, but this is how it is done.
To answer your question, code below will work as long as waypointsIds are unique. That beign said, as other mentioned, fix your database layout. What you have here could really benefit from a separate table.
<?php
$waypointIds = [510, 511, 512];
$bearings = [65, 50, 32];
$distances = [74, 19, 14];
$output = [];
for ($i = 0; $i < count($waypointIds); $i++) {
$output[$waypointIds[$i]] = [
'bearing' => $bearings[$i],
'distance' => $distances[$i]
];
}
print_r($output);
$waypoints = array(510, 511, 512);
$bearings = array(65, 50, 32);
$distances = array(74, 19, 14);
for ($i = 0; $i < count($waypoints); $i++) {
$res[] = array(
'waypoint' => $waypoints[$i],
'bearing' => sprintf("%03d", $bearings[$i]),
'distance' => $distances[$i]/100
);
}
print_r($res);
imagine we have a table like :
item_id | item_name | item_group
--------|-----------|-----------
1 | foo | jgKleC
2 | bar | jgKleC
3 | hey | jgKleC
4 | bra | DskgvV
5 | car | DskgvV
What would be a convenient way to pull this data from the database using MySQL queries in PHP to achieve this result :
Array (2){
'jgKleC' = Array(3)[
Array(3)[1, foo, jgKleC],
Array(3)[2, bar, jgKleC],
Array(3)[3, hey, jgKleC]
],
'DskgvV' = Array(2)[
Array(3)[4, bra, DskgvV],
Array(3)[5, car, DskgvV]
]
}
I'm working in PHP and using arrays only would be a plus for me.
select item_group, Concat(item_id,',',item_name,',',item_group) ) as data from tableName
now you get this data in $result
$a='';
foreach($result as $result2){
$a[$result2['tableName]['item_group']] =array($result2['tableName]['data'])
}
now may be you can get the idea
I have 4 tables in a database that allow me to manage a kind of 'check list'. In a few words for each pathology, I have a big step (process) splited into a multiple of tasks. All of this is linked to a specific operation (progress.case_id) in a summary table.
database.pathology
+--------------+------------+
| id_pathology | name |
+--------------+------------+
| 1 | Pathology1 |
| 2 | Pathology2 |
| 3 | Pathology3 |
+--------------+------------+
database.process
+------------+----------+--------------+----------------+
| id_process | name | pathology_id | days_allocated |
+------------+----------+--------------+----------------+
| 1 | BigTask1 | 2 | 5 |
| 2 | BigTask2 | 2 | 3 |
| 3 | BigTask3 | 2 | 6 |
| ... | ... | ... | ... |
+------------+----------+--------------+----------------+
database.task
+---------+-------+------------+
| id_task | name | process_id |
+---------+-------+------------+
| 1 | Task1 | 1 |
| 2 | Task2 | 1 |
| 3 | Task3 | 1 |
| 4 | Task4 | 2 |
| ... | ... | ... |
+---------+-------+------------+
database.progress
+-------------+---------+---------+---------+------------+---------+
| id_progress | task_id | case_id | user_id | date | current |
+-------------+---------+---------+---------+------------+---------+
| 1 | 1 | 120 | 2 | 2015-11-02 | 1 |
| 2 | 2 | 120 | 2 | 2015-11-02 | 0 |
| 3 | 1 | 121 | 3 | 2015-11-02 | 1 |
+-------------+---------+---------+---------+------------+---------+
I have to display something like that
My question is : what is the most efficient way to proceed ?
Is is faster to query only one table (progress) to display the most and only then query the other to get the names of the differents process and days ?
Perhaps the joint function is more efficient ?
Or do you think my database structure is not the more appropriate ?
For each case we can have aproximaly 50 tasks, with a current field translated into a checkbox. A background script is also running. It analyzes the days provided based on the remaining days to determine if there could be a delay for this specific case.
For each case, the progress table is already filled with all the task related to the pathology of the case. And the current field is always '0' at the begining.
I already tried multiple things like
$result = $db->prepare("SELECT DISTINCT process_id,process.name FROM task, progress,process WHERE progress.task_id = task.id_task AND task.process_id = process.id_process AND progress.case_id = ?");
$result->execute(array($id));
foreach($result as $row)
{
echo "<b>".$row[1]."</b><br>";
$result = $db->prepare("SELECT name,id_task FROM task WHERE process_id = ?");
$result->execute(array($row[0]));
foreach($result as $row)
{
echo $row[0];
$result = $db->prepare("SELECT user_id, date, current FROM progress WHERE progress.task_id = ? AND case_id = ?");
$result->execute(array($row[1], $id));
foreach($result as $row)
{
if($row[2] == 0)
{echo "<input type='checkbox' />";}
else
{
echo "<input type='checkbox' checked/>";
echo "user : ".$row[0]." date : ".$row[1]."<br>";
}
}
}
But I am pretty sure that I am not doing it right. Should I change my database infrastructure ? Should I use a specific MySQL trick ? Or maybe just a more efficiant PHP processing ?
In terms of efficiency, a database query is one of the slowest operations that you can perform. Anything that you can do to reduce the number of queries that you make will go a long way towards making your application faster.
But more important than that, your application needs to work as designed, which means that the developers need to understand what's going on, data shouldn't be hanging around just waiting to be overwritten, and the junior developer who will be tasked to maintain this in 3 years won't want to tear their hair out.
Fast is better than slow.
Slow is better than broken.
To your specific problem, if possible, never have a query inside of a loop. Especially when that loop is controlled by data that you pull from that same database. This is a code smell that calls for proper use of JOINs.
A Google Image search for SQL Join Diagrams shows plenty of examples of Venn Diagrams that show the different types of data returned with each JOIN. When in doubt, you usually want LEFT JOINs.
So, let's identify your relationships:
Pathology
Unused in your results.
Find a way to incorporate it into your query, since "Pathology2" appears in your mockup.
Process
References Pathology in a one-to-many relationship. Each Process can have one Pathology, but each Pathology can have 0 or more Processes.
Task
References Task in a one-to-many relationship. Task contains children of Process.
Progress
References Task, as well as the not shown Case and User. Progress appears to be the details of a Task when referencing a specific Case and User.
I am assuming that there is a business constraint where task_id, case_id, and user_id must be unique... That is, user 1 can only have 1 Progress entry for task 1 and case 100.
Besides holding the details for a Task, also acts as a bridge between Task, Case, and User, giving many-to-many relationships to the three tables. Since Task is a direct child of Process, and Process is a direct child of Pathology, it gives a many-to-many relationship to Pathology.
Case
Inferred existence of this table.
Referenced by Task.
User
Inferred existence of this table.
Referenced by Task.
Based on this table structure, our main groupings will be Case, Pathology, and User.
That is, if you're a logged in user and you want to look at your progress by Case, you would want to see the following:
Case 110:
Pathology1:
BigTask1:
Task1: X
Task2: []
BigTask2:
Task3: X
Pathology2:
BigTask3:
Task4: []
Case 120:
Pathology1:
BigTask1:
Task1: []
We would want User ID == 1;
Our first sorting would be based on Case
Our second sorting would be based on Pathology
Our third sorting would be based on Process
And our last sorting would be on Task...
Thus, the data to get our results above would be:
+------+------------+----------+-------+----------+
| case | pathology | process | task | progress |
+------+------------+----------+-------+----------+
| 110 | Pathology1 | BigTask1 | Task1 | 1 |
| 110 | Pathology1 | BigTask1 | Task2 | 0 |
| 110 | Pathology1 | BigTask2 | Task3 | 1 |
| 110 | Pathology2 | BigTask3 | Task4 | 0 |
| 120 | Pathology1 | BigTask1 | Task1 | 0 |
+------+------------+----------+-------+----------+
Our 'ORDER BY' clause is from last to first... ORDER BY task, process, pathology, case... We could sort it in PHP, but the database is better at it than we are. If your indexes are set up properly, the database might not even have to sort things, it'll just fetch it in order.
The query to get the above data for a specific user is:
SELECT
prog.case_id AS case,
path.name AS pathology,
proc.name AS process,
task.name AS task,
prog.current AS progress
FROM
pathology path
LEFT JOIN process proc ON path.id_pathology = proc.pathology_id
LEFT JOIN task ON task.process_id = proc.id_process
LEFT JOIN progress prog ON task.id_task = prog.task_id
WHERE prog.user_id = :userid
ORDER BY task, process, pathology, case
Your PHP might then be along the lines of
<?php
$sql = <<<EOSQL
SELECT
prog.case_id AS case,
path.name AS pathology,
proc.name AS process,
task.name AS task,
prog.current AS progress
FROM
pathology path
LEFT JOIN process proc ON path.id_pathology = proc.pathology_id
LEFT JOIN task ON task.process_id = proc.id_process
LEFT JOIN progress prog ON task.id_task = prog.task_id
WHERE prog.user_id = :userid
ORDER BY task, process, pathology, case
EOSQL;
$result = $db->prepare($sql);
$result->execute(array(':userid' => $id));
$rows = $result->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
var_dump($row);
// array(5) {
// ["case"]=>
// int(110)
// ["pathology"]=>
// string(10) "Pathology1"
// ["process"]=>
// string(8) "BigTask1"
// ["task"]=>
// string(5) "Task1"
// ["progress"]=>
// int(1)
// }
}
I have stored the physical locations of specific files in my database with download counters to provide downloads via shorter urls like /Download/a4s. Each file has a categoryId assigned via foreign keys which just describes to which course/lecture it belongs for an easier overview. The table fileCategories basically looks like this
categoryId | categoryName
---------------------------
1 | Lecture 1
2 | Lecture 2
3 | Personal Stuff
Assume that I have a files table which looks like this with some other columns I did omit
fileId | categoryId | filePath | ...
----------------------------------------
1 | 1 | /Foo/Bar.pdf | ...
2 | 1 | /Foo/Baz.pdf | ...
3 | 2 | /Bar/Foo.pdf | ...
4 | 2 | /Baz/Foo.pdf | ...
5 | 3 | /Bar/Baz.pdf | ...
I have created a page which should display some data about those files and group them by their categories which produces a very simple html table which looks like this:
Id | Path | ...
-----------------------
Lecture 1
-----------------------
1 | /Foo/Bar.pdf | ...
2 | /Foo/Baz.pdf | ...
-----------------------
Lecture 2
-----------------------
3 | /Bar/Foo.pdf | ...
4 | /Baz/Foo.pdf | ...
-----------------------
Personal Stuff
-----------------------
5 | /Bar/Baz.pdf | ...
So far I am using multiple SQL queries to fetch and store all categories in PHP arrays and append file entries to those arrays when iterating over the files table. It is highly unlikely this is the best method even though the number of files is pretty small. I was wondering whether there is a query which will automatically sort those entries into temporary tables (just a spontaneous guess to use those) which I can output to drastically improve my current way to obtain the data.
You can not do this with just mysql but a combination of JOIN and some PHP.
SELECT * FROM files f LEFT JOIN fileCategories c USING (categoryId) ORDER BY c.categoryName ASC
Be sure to order by the category first (name or ID) and optionally order by other params after that to allow the following code example to work as expected.
in PHP then iterate over the result, remember the category id from each row and if it changes you can output the category delimiter. assumung the query result is stored in $dbRes
Example Code:
$lastCategoryId = null;
while ($row = $dbRes->fetchRow()) {
if ($lastCategoryId !== $row['categoryId']) {
echo "--------------------" . PHP_EOL;
echo $row['categoryName'] . PHP_EOL
echo "--------------------" . PHP_EOL;
}
echo $row['filePath'] . PHP_EOL;
$lastCategoryId = $row['categoryId'];
}
I have a table that look like this:
Name | date1 | date2 | date3 | etc..
per1 | status1 | | status2 | etc
per4 | status2 | status3 | | etc
The number of the dates columns is not fixed. Their values can either be a status or they can be empty.
I want to access the data of the dates columns for each row separately and process the data.
The output I want to achieve:
Name | field1 | status1 | status2 | etc..
per1 | value | #ofstat1 | #ofstat2 | etc
So for I got, accessing the table at the beginning of the question:
$confirmed ="Confirmed";
$accepted ="Accepted";
while ($row = mysql_fetch_array($result)) {
$confirmed_cnt =0;
$accepted_cnt =0;
foreach ($row as $value) {
if (strcmp($value, $confirmed)) $confirmed_cnt++;
else if (strcmp($value, $accepted)) $accepted_cnt++;
}
print("<tr>");
print("<td>$row["Name"]</td>"); // name
print("<td>$confirmed</td>"); // confirmed
print("<td>$accepted</td>"); // accepted
print("</tr>");
}
As far as I know this should work, but for some reason it goes trough each column 2 times in a row.
Try mysql_fetch_assoc() or mysql_fetch_row() instead.
mysql_fetch_array() returns every column two times: as [1] and as ['fieldName']
Also, you have another error, replace your following line:
print("<td>$row["Name"]</td>"); // name
for this one:
print("<td>$row['Name']</td>"); // name