Why is my Query within a loop is extremely very slow - php

I don't have a enough experience in mysql with php, but am trying to develop a school report card, am trying to query the students, with their exams, subjects and marks. In this case I have three loops. the First loop is for the students I use it to get the student's ID, the second loop is for the exam, i use it to get the exam ID and the last is for subject I use it to get the subject ID, i need all this IDs in order to get the marks for each student per exam per subject. Bellow are the loops;
foreach ($students as $student_row){ //get each student's ID
$studentID = $student_row['studentID'];
foreach ($allExams as $allExam) { //get each exam's ID
$examID = $allExam['examID'];
foreach ($subjects as $subject){ //get each subject's ID
$subjectID = $subject['subjectID'];
$obtained_mark_query = $this->db->query("select * from mark
where mark.studentID = '$studentID' && mark.examID = '$examID' &&
mark.subjectID = '$subjectID'")->row()->mark; //to get mark for each student per exam per subject
}
}
}
I don't know what's really the problem but this loop takes really long, it takes 26 second to return all the students in a specific class, there are only 23 students in a class, four exams, and five subjects, so am wondering what it'll really be when there are more than those students. Please any idea why it's behaving like this, any idea i can use other than loops.

There are 2 main issues in your code's logic:
You are selecting every field with SELECT * FROM which is highly inefficient, try selecting only the fields you are going to use
You are querying in your 3rd nested loop, so instead of querying it once, you are doing 1 query per student, per exam, per subject (like 23 * 4 * 5 = 460 queries)
The solution is to refactor your code, your db, and fetch that information in a simple query, then iterate through the returned records and handle the information server side.

Communicating with the database is very slow. Imagine you're cooking a meal, and your database queries are trips to the grocery store. You wouldn't make a trip for every single ingredient - you'd make a list of all the ingredients you'll need so you can just make a single trip.
So you want to run a single query to fetch all the records you'll need on this page. To make this easier you can turn your "WHERE" clause into the "ORDER BY" of a single query:
SELECT * FROM mark ORDER BY studentID, subjectID, examID;
Then you can handle the output or any processing of each student's marks one after the other in a single loop over that result set.

Related

Understanding DB Design

I wanted to make a database to store examination marks. I was wondering if it is better to have
a table for each subject which will make a single record for a roll number
or
have a marks table which will have multiple records for a roll number for every subject
Since the number of subjects is not fixed, which approach would be better.
For the first method, As the tables would be created using php dynamically, so the table names will become a variable, so that will mean extra sanitisation work,
For the second method, the roll number will have many duplicates
Since I am new to DBMS, so any insight is appreciated.
Here's one good reason to choose the second method over the first. Let's say you want to get all marks for a specific roll number. (Using 1 for example.) With the second method, it's just:
SELECT * FROM marks WHERE roll_number = 1;
With the first method (unless someone else knows a better way to do it), it's:
SELECT * FROM subject_a WHERE roll_number = 1
UNION
SELECT * FROM subject_b WHERE roll_number = 1
UNION
SELECT * FROM subject_c WHERE roll_number = 1
...
and so on, for each one of your unknown number of subject tables.

handling a lot of data with mysql and php in search

I'm making a car part system, to store all the parts inside mysql and then search for them.
Part adding goes like this:
you select up to 280 parts and add all the car info, then all the parts are serialized and put into mysql along with all the car info in a single row.
(for this example I'll say that my current database has 1000 cars and all of those cars have 280 parts selected)
The problem is that when I have 1000 cars with each of them having 280 parts, php and mysql starts getting slow and takes a lot of time to load the data, because the number of parts is 1000*280=280 000.
I use foreach on all of the cars and then put each part into another array.
The final array has 280 000 items and then I filter it by the selected parts in the search, so out of 28 000 parts it may have only have to print like 12 500 parts (if someone is searching for 50 different parts at the same time and 250 cars have that part).
Example database: http://pastebin.com/aXrpgeBP
$q=mysql_query("SELECT `id`,`brand`,`model`,`specification`,`year`,`fueltype`,`capacity`,`parts`,`parts_num` FROM `warehouse`");
while($r=mysql_fetch_assoc($q)){
$partai=unserialize($r['parts']);
unset($r['parts']); //unsetting unserialized parts so the whole car parts won't be passed into the final parts-only array
foreach($partai as $part){
$r['part']=$parttree[$part]; //$parttree is an array with all the part names and $part is the part id - so this returns the part name by it's id.
$r['part_id']=$part; // saves the part id for later filtering selected by the search
$final[]=$r;
}
}
$selectedparts=explode('|', substr($_GET['selected'], 0,strlen($_GET['selected'])-1)); //exploding selected part ids from data sent by jquery into an array
foreach($final as $f){
if(in_array($f['part_id'], $selectedparts)){
$show[]=$f; //filtering only the parts that need to be shown
}
}
echo json_encode($show);
This is the code I use to all the cars parts into arrays and the send it as json to the browser.
I'm not working on the pagination at the moment, but I'll be adding it later to show only 10 parts.
Could solution be to index all the parts into a different table once 24h(because new parts will be added daily) and then just stressing mysql more than php? Because php is doing all the hard work now.
Or using something like memcached to store the final unfiltered array once 24h and then just filter the parts that need to be shown with php?
These are the options I considered, but I know there must be a better way to solve this.
Yes, you should definitely put more emphasis on MySQL. Don't serialize the parts for each car into a single row of a single column. That's terribly inefficient.
Instead, make yourself a parts table, with columns for the various data items that describe each part.
part_id an autoincrement item.
car_id which car is this a part of
partnumber the part's external part number (barcode number?)
etc
Then, use JOIN operations.
Also, why don't you use a WHERE clause in your SELECT statement, to retrieve just the car you want?
Edit
If you're looking for a part, you definitely want a separate parts table. Then you can do a SQL search something like this.
SELECT w.id, w.model, w.specification, w.year, w.fueltype,
p.partnumber
FROM warehouse w
JOIN parts p ON (w.id = p.car_id)
WHERE p.partnumber = 'whatever-part-number-you-want'
This will take milliseconds, even if you have 100K cars in your system, if you index it right.
Your query should be something like:
<?php
$selectedparts=explode('|', substr($_GET['selected'], 0,strlen($_GET['selected'])-1)); //exploding selected part ids from data sent by jquery into an array
$where = ' id < 0 ';
foreach ($selectedparts AS $a){
$where .= " OR `parts` like '%".$a."%'";
}
$query = "SELECT * FROM `warehouse` WHERE ".$where." ORDER BY `id` ASC";//this is your query
//.... rest of your code
?>
Yes, look into has many relationships a car has many parts.
http://net.tutsplus.com/tutorials/databases/sql-for-beginners-part-3-database-relationships/
Then you can use an inner join to get the specified parts. You can do a where clause to match the specific partIds to filter out unwanted parts or cars.

Ordering-Sorting on runtime in PHP

My DB schema is like Course_Table {course_name,marks,std_ID}
I am getting only 1 subject marks for a particluar student,Therefore my flow of Logic and presnetation is
$std_id=Select std_id From student_Table
foreach(Iterate all students)
select marks from course_table where std_ID=student_ID
{
Print Student marks //here i need sorted List,,,
}
My problem is not actually the Code,I am looking for logic
I want to Print the student marks in ascending Order.
What I thought is to use Order By clause,but it is of no use as I am getting only 1 record per student .
may Be I need to create a sepreate class,Fed all the data inside this class and then Sort it.. (a very hectic approach would be my last option)
I am looking for an alternative approach or logic that could solve my query.
I'd write one query to give me all the data needed and then loop through...
something like...
Select student_ID, marks from course_Table order by student_ID, marks //this way the select is already orderd by student and marks. all we have to do is display it. Since you're not using name or anything getting records just from course_Table is fine.
for each record in resultset
if oldstudent=resultset.student then
isnewStudent=false
displaymark(resultset.mark, isNewStudent)
else
oldstudent = resultset.student
isNewStudent=True
displaymark(resultset.mark, isNewstudent)
end if
next record

Should I be using multiple SQL queries or multiple loops in my client code?

I'm building a small internal phone number extension list.
In the database I have two tables, a people table, and a numbers table. The relationship is a one-to-many respectively.
I want to display the results in a single HTML table with one person per row but if they have multiple numbers it shows those in a single column with a rowspan on the person row to compensate.
Now, to get the results from the database to work with, I can either do:
(pseudocode)
SELECT id, name
FROM people
foreach result as row {
SELECT number
FROM numbers
WHERE numbers.person_id = row['id']
}
This would mean that I'm doing one query to get all users, but then if I have 100 users, I'm performing 100 additional queries to get the numbers for each user.
Instead I could do it like this:
(pseudocode)
SELECT number, person_id
FROM numbers
SELECT id, name
FROM people
foreach people as person {
echo name
foreach numbers as number {
if (number.id = person.id) {
echo number
}
}
}
So, essentially it is doing the exact same thing except instead I do two queries to get all the results into arrays and then loop through the arrays to format my tables.
Which method should I be using or is there a better way to do this?
The common way is to do a regular JOIN:
SELECT id, name, number
FROM people, numbers
WHERE people.id = numbers.person_id;
You can either add an ORDER BY to get the numbers in order, or you could create an array with a single pass over the resultset, and then loop through that array.
You can also consider a GROUP_CONCAT to concatenate all the numbers for the same person:
SELECT id, name, GROUP_CONCAT(number SEPARATOR ', ')
FROM people, numbers
WHERE people.id = numbers.person_id
GROUP BY people.id;
Since you are even asking this question: I cannot stress the fact that you should pick up an introductory book on database design. It helped me wonders to learn the theories behind relational databases.
You probably want to execute just one query. Something like
select people.id, people.name, group_concat(numbers.number)
from people
inner join numbers on numbers.id = people.id
group by people.id, people.name
order by people.name
Then you can loop over the result set with simple php code.
It depends, and you may have to time it to find out. Doing multiple queries is a lot of network turns if your database is on a different computer than your web server, so often this takes longer. However, if your database server is on the same computer as your web server, this might not be an issue.
Also consider the time it will take to look up the number in the array. As an array you are doing a linear order O(N) search. If you can put it in a hash, the lookup will be much faster, and the two query approach may be faster, but not if you spend a lot of time looking up the answer in your array.
Using a join to get it into one query, may be fastest, as the numbers will be associated with the people, depending on your container structure you are storing the data into to be accessed in your foreach loop.
Use a stored procedure or function to retrive the data, don't wite the sql in your programm
You should do neither. You can do one query (join) over the tables:
select name, number
from people p, numbers n
where p.id = n.person_id
order by name, number;
and then just one loop:
$link = mysqli_connect(...);
$query = 'select name, number from people p, numbers n where p.id = n.person_id order by name, number';
if ($result = mysqli_query($link, $query)) {
$person = '';
while ($row = mysqli_fetch_array($result, MYSQLI_ASSOC)) {
if ($row['name'] !== $person) {
$person = $row['name'];
echo $person;
}
echo $row['number'];
}
}

PHP/MySQL looping through to get totals

I have three db tables: species, locations, records.
I have nested "while" statements that first loop through the locations to provide table headers. Further nesting accomplishes going through the records page to pull out each record and provide a total count of each species at each location.
I need to add an additional row underneath the last species to provide a count that totals each location.
This cannot be hard-coded since the number of species and locations can vary. Code is already setup to expand the table in both rows and columns. All I need to figure out is how to do the simple calculation of each location's (column) total.
I was thinking I need to do this in some type of an array to load it but I cannot find any real documentation on how I want to do this (at least that I can understand).
you have to use a UNION on this table.
you can create a temporary table first to do this then make a select from it.
roughly:
your select.....
UNION
select 'Total',sum(location1),sum(location2),sum(location3) from
(your select) as b
Total granny way to do it, but you said you needed something you understand, and I'll bet joins and unions are a little out of your scope. This may not be syntactically correct, but it should get you closer.
Removed MySQL | No longer applicable
or you can do it in php:
<?php
$Results = mysql_fetch_assoc("Select * FROM table");
$locArr = new Array();
foreach($R in $Results){
foreach($prop in $R){
$locArr[$prop] += $R[$prop];
}
}
foreach($prop in $locArr){
echo $prop."Total: ".$locArr[$prop]."<br/>";
}
?>

Categories