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/>";
}
?>
Related
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.
I have been trying to figure out how to check if any of one column's items present in another column in Codeigniter....
I have Bike,Car,Bus in a column(Vehicles) in a table1...
I also have Bus,Helicopter,Ship,Car in a column(Interested) in table2..
How to check if any of table2 interested column's items present in table1 vehicles column...
I tried like this...
$query = $this->db->get('table2');
foreach($query->result() as $row)
{
$a = explode(',', $row->interested);
$this->db->where_in('Vehicles', $a);
$query = $this->db->get('table1');
foreach($query->result() as $row2)
{
echo $row2->ID;
}
}
Could it be done ?? Please Help me... Thanks in advance....
It is very hard to help you without seeing the tables and how you are relating one to the other (such as user_id).
What you should do is two queries. One query to get the data from table 1 of the vehicles you are interested in. Then run through the result set and create a simple array of those vehicles in php. Then do a second query using where_in to select all the rows that are in the chosen vehicles array.
Your method is bad because the number of db queries depends on the size of the result set of your first query. You might find your code trying to do hundreds of database queries.
If you show a diagram of your tables, I could write some example code for you.
Here are the docs for where_in: https://www.codeigniter.com/user_guide/database/query_builder.html#CI_DB_query_builder::where_in
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'];
}
}
I want to return the row number of a particular row (so how many rows before a given row).
Now the problem is the PRIMARY_KEYs are not sequential, so there are 'gaps' inside them, because sometimes I have to DELETE rows.
id = 1
id = 2
id = 5
id = 9
id = 10
So the only option to get the row number is to use a COUNT(*):
SELECT COUNT(*) FROM table WHERE id < selected_row_id;
But for a given page I have to perform this operation multiple times.. so one solution is to use a foreach loop, like:
foreach($foo as $item){
mysql_query("SELECT COUNT(*) FROM table WHERE id < $item['id']");
//...
}
But I think it's not optimal...if one have thousands of rows and 80-100 iterations for the above foreach.
Another solution would be to rebuild the entire id column after DELETING a row.. but because foreign constraints / references I think this isn't a good step, too.
So if COUNT(*) in a foreach is not viable, then is there anyone who faced this type of problem, what would be the optimal solution?
Thanks for your time, and sry for my bad english.
I recently had a similar issue where, for a given set of results, I wanted to know the position # of a specific result in that set.
There's an elegant solution which will give you sequential row numbers for a resultset, based on internal incrementation of a variable
See http://craftycodeblog.com/2010/09/13/rownum-simulation-with-mysql
Hope that helps you out
Instead of rebuilding the id column after each delete or insert, how about just adding a new column to the table to store the data you need?
I think you want to try this:
SELECT parent.Id, count( DISTINCT (child.Id) ) AS previous
FROM table AS parent, table AS child
WHERE child.Id < parent.Id
I don't think it is efficient from a database perspective, but should be better than iterating from code. Caveat is that you need to build the required php code since I am not proficient at that, but should be easy.
There are a couple ways to improve this
You can sort $items by ID, then keep track of the number of rows above the last item, and add to this the number of rows between the current item and the last item: $last_id <= id AND id < $item['id']
Context switching is expensive. It is better to grab all the IDs in a single query, then process the information in PHP.
OK - I'll get straight to the point - here's the PHP code in question:
<h2>Highest Rated:</h2>
<?php
// Our query base
$query = $this->db->query("SELECT * FROM code ORDER BY rating DESC");
foreach($query->result() as $row) {
?>
<h3><?php echo $row->title." ID: ";echo $row->id; ?></h3>
<p class="author"><?php $query2 = $this->db->query("SELECT email FROM users WHERE id = ".$row->author);
echo $query2->row('email');?></p>
<?php echo ($this->bbcode->Parse($row->code)); ?>
<?php } ?>
Sorry it's a bit messy, it's still a draft. Anyway, I researched ways to use a Ratings system - previously I had a single 'rating' field as you can see by SELECT * FROM code ORDER BY rating DESC. However I quickly realised calculating averages like that wasn't feasible, so I created five new columns - rating1, rating2, rating3, rating4, rating5. So when 5 users rating something 4 stars, rating4 says 5... does that make sense? Each ratingx column counts the number of times the rating was given.
So anyway: I have this SQL statement:
SELECT id, (ifnull(rating1,0) + ifnull(rating2,0) + ifnull(rating3,0) + ifnull(rating4,0) + ifnull(rating5,0)) /
((rating1 IS NOT NULL) + (rating2 IS NOT NULL) + (rating3 IS NOT NULL) + (rating4 IS NOT NULL) + (rating5 IS NOT NULL)) AS average FROM code
Again messy, but hey. Now what I need to know is how can I incorporate that SQL statement into my script? Ideally you'd think the overall query would be 'SELECT * FROM code ORDER BY (that really long query i just stated) DESC' but I can't quite see that working... how do I do it? Query, store the result in a variable, something like that?
If that makes no sense sorry! But I really appreciate the help :)
Jack
You should go back to the drawing board completely.
<?php
$query = $this->db->query("SELECT * FROM code ORDER BY rating DESC");
foreach($query->result() as $row) {
$this->db->query("SELECT email FROM users WHERE id = ".$row->author;
}
Anytime you see this in your code, stop what you're doing immediately. This is what JOINs are for. You almost never want to loop over the results of a query and issue multiple queries from within that loop.
SELECT code.*, users.email
FROM code
JOIN users ON users.id = code.author
ORDER BY rating DESC
This query will grab all that data in a single resultset, removing the N+1 query problem.
I'm not addressing the rest of your question until you clean up your question some and clarify what you're trying to do.
if you would like to change your tables again, here is my suggestion:
why don't you store two columns: RatingTotal and RatingCount, each user that rates it will increment RatingCount by one, and whatever they vote (5,4,4.2, etc) is added to RatingTotal. You could then just ORDER BY RatingTotal/RatingCount
also, I hope you store which users rated each item, so they don't vote multiple times! and swing the average their way.
First, I'd decide whether your application is write-heavy or read-heavy. If there are a lot more reads than writes, then you want to minimize the amount of work you do on reads (like this script, for example). On the assumption that it's read-heavy, since most webapps are, I'd suggest maintaining the combined average in a separate column and recalculating it whenever a user adds a new rating.
Other options are:
Try ordering by the calculated column name 'average'. SQL Server supports this. . not sure about mysql.
Use a view. You can create a view on your base table that does the average calculation for you and you can query against that.
Also, unrelated to your question, don't do a separate query for each user in your loop. Join the users table to the code table in the original query.
You should include it in the SELECT part:
SELECT *, (if ....) AS average FROM ... ORDER BY average
Edit: assuming that your ifnull statement actually works...
You might also want to look into joins to avoid querying the database again for every user; you can do everything in 1 select statement.
Apart from that I would also say that you only need one average and the number of total votes, that should give you all the information you need.
Some excellent ideas, but I think the best way (as sidereal said that it's more read heavy that write heavy) would be to have columns rating and times_rated, and just do something like this:
new_rating = ((times_rated * rating) + current_rating) / (times_rated + 1)
current_rating being the rating being applied when the person clicks the little stars. This simply weights the current user's rating in an average with the current rating.