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'];
}
Related
I use lengths of code similar to this repeatedly in my scripting because I cannot find a shorter way to to compare the MYSQL columns
if ($them['srel1']=="Y" AND $me['Religion']=='Adventist'){$seek11pts=5;}
if ($them['srel2']=="Y" AND $me['Religion']=='Agnostic'){$seek11pts=5;}
if ($them['srel3']=="Y" AND $me['Religion']=='Atheist'){$seek11pts=5;}
if ($them['srel4']=="Y" AND $me['Religion']=='Baptist'){$seek11pts=5;}
if ($them['srel5']=="Y" AND $me['Religion']=='Buddhist'){$seek11pts=5;}
if ($them['srel6']=="Y" AND $me['Religion']=='Caodaism'){$seek11pts=5;}
if ($them['srel7']=="Y" AND $me['Religion']=='Catholic'){$seek11pts=5;}
if ($them['srel8']=="Y" AND $me['Religion']=='Christian'){$seek11pts=5;}
if ($them['srel9']=="Y" AND $me['Religion']=='Hindu'){$seek11pts=5;}
if ($them['srel10']=="Y" AND $me['Religion']=='Iskcon'){$seek11pts=5;}
if ($them['srel11']=="Y" AND $me['Religion']=='Jainism'){$seek11pts=5;}
if ($them['srel12']=="Y" AND $me['Religion']=='Jewish'){$seek11pts=5;}
if ($them['srel13']=="Y" AND $me['Religion']=='Methodist'){$seek11pts=5;}
if ($them['srel14']=="Y" AND $me['Religion']=='Mormon'){$seek11pts=5;}
if ($them['srel15']=="Y" AND $me['Religion']=='Moslem'){$seek11pts=5;}
if ($them['srel16']=="Y" AND $me['Religion']=='Orthodox'){$seek11pts=5;}
if ($them['srel17']=="Y" AND $me['Religion']=='Pentecostal'){$seek11pts=5;}
if ($them['srel18']=="Y" AND $me['Religion']=='Protestant'){$seek11pts=5;}
if ($them['srel19']=="Y" AND $me['Religion']=='Quaker'){$seek11pts=5;}
if ($them['srel20']=="Y" AND $me['Religion']=='Scientology'){$seek11pts=5;}
if ($them['srel21']=="Y" AND $me['Religion']=='Shinto'){$seek11pts=5;}
if ($them['srel22']=="Y" AND $me['Religion']=='Sikhism'){$seek11pts=5;}
if ($them['srel23']=="Y" AND $me['Religion']=='Spiritual'){$seek11pts=5;}
if ($them['srel24']=="Y" AND $me['Religion']=='Taoism'){$seek11pts=5;}
if ($them['srel25']=="Y" AND $me['Religion']=='Wiccan'){$seek11pts=5;}
if ($them['srel26']=="Y" AND $me['Religion']=='Other'){$seek11pts=5;}
EG: if ($them['srel1']=="Y" AND $me['Religion']=='Adventist'){$seek11pts=5;}
I check to seek if the MYSQL column srel1 has a value of Y. if it does then I check to see if the column Religion equals Adventist. If both are true then $seek11pts=5, if they are not both true then nothing happens.
There are 26 srel type columns with either a Y value or null. There are also 26 different values for Religion as you may see. This is but one section of my code. I have multiple HUGE code groupings like this and I'd love to be able to reduce it down to a few lines. I was thinking some kind of array for the religions and another for the numerical endings of the srel columns but I cant get it.
For this current code you can use this:
<?php
$religions = array(1 => 'Adventist','Agnostic','Atheist','Baptist','Buddhist','Caodaism','Catholic','Christian','Hindu','Iskcon','Jainism','Jewish','Methodist','Mormon','Moslem','Orthodox','Pentecostal','Protestant','Quaker','Scientology','Shinto','Sikhism','Spiritual','Taoism','Wiccan','Other');
$count = count($religions) + 1;
for ($i = 1; $i < $count; $i++) {
if ($them["srel$i"]=="Y" && $me['Religion']==$religions[$i]) {
$seek11pts=5;
break;
}
}
While there are ways to accomplish what you ask, you should instead seriously consider restructuring your data.
Better data structure
If your data had a structure more similar to the following:
db.person
+----+------+
| id | name |
+----+------+
| 1 | Nick |
| 2 | Bob |
| 3 | Tony |
+----+------+
PrimaryKey: id
db.religion
+----+---------+
| id | name |
+----+---------+
| 1 | Atheist |
| 2 | Jainism |
| 3 | FSM |
+----+---------+
PrimaryKey: id
db.person_religion
+--------+----------+
| person | religion |
+--------+----------+
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
+--------+----------+
UniqueIndex: (person,religion)
...everything you're trying to do could be done with simple queries.
SELECT me.id, me.name, meR.name as religion, count(them.id) as matches
FROM person me
LEFT INNER JOIN person_religion meRlookup
ON me.id = meRlookup.person
LEFT INNER JOIN religion meR
ON meRlookup.religion = meR.id
LEFT INNER JOIN person_religion themRlookup
ON meRlookup.religion = themRlookup.religion
LEFT INNER JOIN person them
ON themRlookup.person = them.id
GROUP BY meR.id
I would recommend using laravel or lumen, since these include a "queries generator" that just write a little code (NOTHING SQL) to make queries and that ..
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 3 tables in my DB: ‘workouts’, ‘exercises’ and ‘exercise_list’.
workouts: | id | datetime | misc1 | misc2 |
exercises: | id | ex_id | wo_id | weight | reps | wo_order |
exercise_list: | id | title |
So far I have generated a view which grabs details of a specific workout (myurl.com/workouts/view/<datetime>)
I have built a query that grabs the fields from ‘workouts’ and also it grabs any ‘exercises’ entries that correspond to that workout (by get_where using wo_id).
I build a view which lists the exercises for that workout, but I can only get as far as foreach’ing out the ‘id’ of the exercise. I need to somehow have a further query that grabs the ‘title’ of each exercise that is associated with that workout ‘id’.
So I currently have a table (html):
| Exercise | Weight | Reps |
| 1 | 50 | 8 | ...
I need ‘1’ to become the title of the exercise in ‘exercise_list’ with an ‘id’ of ‘1’.
My solution
May not be perfect but it works:
public function get_exercises($wo_id)
{
$this->db->select('exercises.wo_id,
exercises.weight,
exercises.reps,
exercise_list.title');
$this->db->from('exercises');
$this->db->join('exercise_list','exercises.ex_id= exercise_list.id');
$this->db->where('exercises.wo_id',$wo_id);
$q = $this->db->get();
$query = $q->result_array();
return $query;
}
Not sure about the bestway to do the last few lines. This is in my model, so I needed to return the array. I am going tobet there is a way to do it better than the last 3 lines.
You can use joins and select title from your exercise_list table
$this->db->select('w.*,el.title')
->from('workouts w')
->join('exercises e','w.id = e.wo_id')
->join('exercise_list el','el.id = e.ex_id')
->where('e.wo_id',$yourid)
->get()
->result();
I have one field in the backend, where I input IDs separated by comma - ID1, ID2, ID3....These are videos in fact. All ids are stored in the field product_videos in the database (as they are typed).
How can I echo these id's on the frontend so they all show for the product?
Storing comma separated data in one data field is a bad idea. It is a real pain to manipulate, so you should really consider revising your db structure.
You haven't shown your data structure, so I'll give a basic example and then explain how it can be improved. My example assumes product_videos is linked to particular users:
table: `users`
| user_id | user_name | product_videos |
|---------|-----------|----------------|
| 1 | User1 | 1,2,3,4,6,7 |
| 2 | User2 | 5 |
You would maybe run a query
SELECT `product_videos` FROM `users` WHERE `user_name` = 'User1'
This would give you one row, with a comma separate value - you would then need to use something like PHP's explode() to convert it into an array and then loop through that array. That is a very bad method (and it will only become harder as you try to do more advanced things).
Instead, it would be easier to use a link table. Imagine:
table: `users`
| user_id | user_name |
|---------|-----------|
| 1 | User1 |
| 2 | User2 |
table: `videos`
| video_id | user_id |
|-----------|---------|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
| 6 | 1 |
| 7 | 1 |
In this example, each video is a separate row in a db table (and each video is linked to an existing user). Each row is readily able to be handled independently. This makes it really easy to handle extra data for each video, such as a title, runtime length, date of uploading, etc.
You would then need to run a JOIN query. e.g.
SELECT `videos`.`video_id` FROM `videos`
INNER JOIN `users` ON `users`.`user_id` = `videos`.`user_id`
WHERE `users`.`user_name` = 'User1'
In PHP, you would do something like:
$q = mysql_query("SELECT `videos`.`video_id` FROM `videos` INNER JOIN `users` ON `users`.`user_id` = `videos`.`user_id` WHERE `users`.`user_name` = 'User1'");
while ($row = mysql_fetch_assoc($q)) {
echo "VIDEO ID = " . $row["video_id"] . "<br/>";
}