getting rid of repeated customer id's in mysql query - php

I originally started by selecting customers from a group of customers and then for each customer querying the records for the past few days and presenting them in a table row.
All working fine but I think I might have got too ambitious as I tried to pull in all the records at once having heard that mutiple queries are a big no no.
here is the mysqlquery i came up with to pull in all the records at once
SELECT morning, afternoon, date, date2, fname, lname, customers.customerid
FROM customers
LEFT OUTER JOIN attend ON ( customers.customerid = attend.customerid )
RIGHT OUTER JOIN noattend ON ( noattend.date2 = attend.date )
WHERE noattend.date2
BETWEEN '$date2'
AND '$date3'
AND DayOfWeek( date2 ) %7 >1
AND group ={$_GET['group']}
ORDER BY lname ASC , fname ASC , date2 DESC
tables are customer->customerid,fname,lname
attend->customerid,morning,afternoon,date
noattend->date2 (a table of all the days to fill in the blanks)
Now the problem I have is how to start a new row in the table when the customer id changes
My query above pulls in
customer 1 morning 2
customer 1 morning 1
customer 2 morning 2
customer 2 morning 1
whereas I'm trying to get
customer1 morning2 morning1
customer2 morning2 morning1
I dont know whether this is possible in the sql or more likely in the php

I finally worked out what I was missing.
In order to address the element of the array I needed to use, I needed to use a double bracket ie $customer_array[0][lname], $customer_array[1][lname]. I realise this is probably obvious to most but it was completely eluding me. The key to my understanding this was
print_r(customer_array) which I'd seen a lot but never got working properly.
Then it was just a case of pulling out all the database rows with:
$customer_array =array();
while($row1=mysql_fetch_assoc($extract1)){
$customer_array[] = $row1;
}
and then to loop through as I have a fixed number of records:
for ($x=0;$x<=900;)
{
echo $customer_array[$x][fname];
echo$customer_array[$x][lname];
for($y=0;$y<=30;$y++)
{
echo $customer_array[$x][morning];
echo $customer_array[$x][afternoon];
$x++;
}
}
Hope this helps someone else.

If I'm joining together related tables for one row (which is definitely a best practice as opposed to nested queries and should be done the majority of the time when you can), I tend to do the formatting into neat tables through code.
(pseudocode provided as i don't remember PHP):
// query database
while !EOF {
currentCustomerId = $database["CustomerId"]
// do opening table row stuff; customer name, etc.
while !EOF && currentCustomerId == $database["CustomerId"] {
// do the relational columns from the join
// move to next record
}
// do closing table row stuff
}
The outer loop iterates over each customer, and the inner loop iterates through the relational data for that customer.

Can you achieve that with SQL? Maybe, but I doubt it'd look nice.
Here is the easy PHP solution.
$mornings_by_customer = array();
foreach ($result as $r) {
$mornings_by_customer[$r['customerid']][] = $r['morning'];
}
An example of your result data structure and an example of what you'd rather have - in PHP's array notation - would allow me to give you a more exact answer. This, however, should give you the general idea.

Based also on this near identical problem I'm trying to help you solve, I know you're uncomfortable with arrays. But you're going to have to learn them if you're going to be coding PHP, especially if you need to deal with multidimensional ones as you seem to want to do here.
$sql = "SELECT morning, afternoon, date, date2, fname, lname, customers.customerid
FROM customers
LEFT OUTER JOIN attend ON ( customers.customerid = attend.customerid )
RIGHT OUTER JOIN noattend ON ( noattend.date2 = attend.date )
WHERE noattend.date2
BETWEEN '$date2'
AND '$date3'
AND DayOfWeek( date2 ) %7 >1
AND group ={$_GET['group']}
ORDER BY lname ASC , fname ASC , date2 DESC ";
$results = mysql_fetch_result($sql);
$customer_array = array()
// Load up an array with each customer id as the key and array full of mornings as the value
while($row = mysql_fetch_array($results)) {
array_push($customer_array[$row['customerid']], $row['morning']);
}
// For each customer ID, get the array of mornings
foreach ($customer_array as $customerID=>$morningArray) {
echo $customerID;
// For each morning array item, echo it out
forreach ($morningArray as $key=>$value) {
echo " $value";
}
}

Related

Convert MySQL Query into JSON using PHP

I have a question about using a MySQL Query to convert my data into a JSON Object. The Query I have is converting to a JSON Object, but it is not working the way I would like.
I have multiple tables in my database that I would like to graph on a chart using the date as the X axis and the values as the Y axis. I am currently joining the tables by date. However, some tables may have multiple submissions per day while others may not have any. Currently, the Query I have is only showing results for dates that data was submitted to all 4 tables.
I would also like to graph the information on a scale of 0-10. Three of the 4 tables only have values from 0-10 so I am taking the average of each value per day. The nutrition table, which holds nf_sugars and nf_total_carbohydrates has larger numbers that I will be using normalization to convert them into a 0-10 scale. For now, I am just attempting to get the SUM per day and will complete the rest of the calculation after this part is working. However, the query I am currently running is giving me results that are much higher than the SUM of the actual numbers in my database.
Any help would be greatly appreciated! Here is the PHP I am currently using to create the JSON Object. As a side note, I did successfully connect to my database, I just did not include that here.
$myquery = "SELECT track_ticseverity.date,
AVG(track_ticseverity.ticnum) as average_ticnum,
track_fatigue.date,
AVG(track_fatigue.fatiguenum) as average_fatiguenum,
track_stress.date,
AVG(track_stress.stressnum) as average_stressnum,
track_nutrition.date,
((SUM(track_nutrition.nf_sugars) ) ) as sum_nf_sugars,
((SUM(track_nutrition.nf_total_carbohydrate) ) ) as sum_nf_total_carbohydrate
FROM track_ticseverity
INNER JOIN track_fatigue
ON track_ticseverity.date=track_fatigue.date
INNER JOIN track_stress
ON track_fatigue.date=track_stress.date
INNER JOIN track_nutrition
ON track_stress.date=track_nutrition.date
WHERE track_ticseverity.user_id=1
AND track_fatigue.user_id=1
AND track_stress.user_id=1
AND track_nutrition.user_id=1
GROUP BY track_ticseverity.date";
$query = mysqli_query($conn, $myquery);
if ( ! $query ) {
echo mysqli_error(s);
die;
}
$data = array();
for ($x = 0; $x < mysqli_num_rows($query); $x++) {
$data[] = mysqli_fetch_assoc($query);
}
echo json_encode($data);
mysqli_close($conn);
EDIT - The Query is successfully returning a JSON object. My issue is that the query I wrote does not output the data in the correct way. I need the query to select information from multiple tables, some with multiple submission per day and others with only one or no submissions.
EDIT2 - I am thinking another way to handle this is to combine multiple SELECT statements into a single JSON Object, but I am not sure how to do this.
The sum is larger than expected because of the joins. Imagine that a certain date
occurs in one track_nutrition record and two track_fatigue records, then the join
will make that the data from the first table is once combined with the first track_fatigue
record, and then again with the second record. Thus the same nf_sugars
value will be counted twice in the sum. This behaviour will also affect the averages.
You should therefore first perform the aggregations, and only then perform the joins.
Secondly, to ensure you catch all data, even if for a certain date not all tables have
values, you should use full outer joins. This will guarantee that each record in each table
will find its way in the result. Now, MySQL does not support such full outer joins, so
I use an extra sub-select to select all different dates from the 4 tables and then
"left join" them with the other aggregated data:
SELECT dates.date,
IFNULL(average_ticnum_n, 0) as average_ticnum
IFNULL(average_fatiguenum_n, 0) as average_fatiguenum
IFNULL(average_stressnum_n, 0) as average_stressnum
IFNULL(sum_nf_sugars_n, 0) as sum_nf_sugars
IFNULL(sum_nf_total_carbohydrate_n, 0) as sum_nf_total_carbohydrate
FROM (
SELECT DISTINCT user_id,
date
FROM (
SELECT user_id,
date
FROM track_ticseverity
UNION
SELECT user_id,
date
FROM track_fatigue
UNION
SELECT user_id,
date
FROM track_stress
UNION
SELECT user_id,
date
FROM track_nutrition
) as combined
) as dates
LEFT JOIN (
SELECT user_id,
date,
AVG(ticnum) as average_ticnum_n
FROM track_ticseverity
GROUP BY user_id,
date) as grp_ticseverity
ON dates.date = grp_ticseverity.date
AND dates.user_id = grp_ticseverity.user_id
LEFT JOIN (
SELECT user_id,
date,
AVG(fatiguenum) as average_fatiguenum_n
FROM track_fatigue
GROUP BY user_id,
date) as grp_fatigue
ON dates.date = grp_fatigue.date
AND dates.user_id = grp_fatigue.user_id
LEFT JOIN (
SELECT user_id,
date,
AVG(stressnum) as average_stressnum_n
FROM track_stress
GROUP BY user_id,
date) as grp_stress
ON dates.date = grp_stress.date
AND dates.user_id = grp_stress.user_id
LEFT JOIN (
SELECT user_id,
date,
SUM(nf_sugars) as sum_nf_sugars_n,
SUM(nf_total_carbohydrate) as sum_nf_total_carbohydrate_n
FROM track_nutrition
GROUP BY user_id,
date) as grp_nutrition
ON dates.date = grp_nutrition.date
AND dates.user_id = grp_nutrition.user_id
WHERE dates.user_id = 1
ORDER BY dates.date;
Note that you will get 0 values in some of the columns when there is no data for that
particular date. If you prefer to get NULL instead, remove the Nvl() from those columns
in the query above.
Then, to normalize all data on a 0 - 10 scale, you could look at the maximum
found for each type of value and use that for a conversion, or if you know beforehand
what the ranges are per type, then it is probably better to use that information, and
maybe code that in the SQL as well.
However, it always looks a bit odd to have values combined in a graph that actually use
different scales. One might easily jump to wrong conclusions with such graphs.
I will prefer to use this way (assuming all other things are working fine e.g query is working fine)
$query = mysqli_query($conn, $myquery);
if ( ! $query ) {
echo mysqli_error($conn); //You need to put $conn here to display error.
die;
}
$data = array();
while($row = mysqli_fetch_assoc($query)) {
$data[] = $row;
}
echo json_encode($data);
mysqli_close($conn);
if you are using PDO (PHP Data Objects) for database operations; then following code can be used to create the JSON from PDO.
$array = $pdo->query("SELECT * FROM employee")->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($array);
For Multiple Select we need to try it like that way :
$array = $pdo->query("SELECT 1; SELECT 2;");
$array->nextRowset();
var_dump( $array->fetchAll(PDO::FETCH_ASSOC) );

Computing an average from different tables

I am sorry for a verlo long question, just trying to explain in details. My formatting is not very good, sorry for that as well. I had a PHP/ MySQL App that essentially was not truly relational as I had one large table for all student scores. Among other things, I was able to calculate the average score for each subject, such that the average appeared alongside a student's score. Now I have since split the table up, to have a number of tables which I am successfully querying and creating School Report Cards as before. The hardship is that I can no longer calculate the avaerages for any subject.
Since I had one table with 5 subjects and each of the subjects had 2 tests, I queried for data and calculated the average as follows:
The one table (Columns):
id date name exam_no term term year eng_mid eng_end mat_mid mat_end phy_mid phy_end bio_mid bio_end che_mid che_end
The one query:
$query = "SELECT * FROM pupils_records2
WHERE grade='$grade' && class='$class' && year = '$year' && term ='$term'";
$result = mysqli_query($dbc, $query);
if (mysqli_num_rows($result) > 0) {
$num_rows=mysqli_num_rows($result);
while($row = mysqli_fetch_array($result)){
//English
$eng_pupils1{$row['fname']} = $row['eng_mid'];
$eng_pupils2{$row['fname']} = $row['eng_end'];
$mid=(array_values($eng_pupils1));
$end=(array_values($eng_pupils2));
$add = function($a, $b) { return $a + $b;};
$eng_total = array_map($add, $mid, $end);
foreach ($eng_total as $key => $value){
if ($value==''){
unset ($eng_total[$key]);
}
}
$eng_no=count($eng_total);
$eng_ave=array_sum($eng_total)/$eng_no;
$eng_ave=round($eng_ave,1);
//Mathematics
$mat_pupils1{$row['fname']} = $row['mat_mid'];
$mat_pupils2{$row['fname']} = $row['mat_end'];
$mid=(array_values($mat_pupils1));
$end=(array_values($mat_pupils2));
$add = function($a, $b) { return $a + $b;};
$mat_total = array_map($add, $mid, $end);
foreach ($mat_total as $key => $value){
if ($value==''){
unset ($mat_total[$key]);
}
}
print_r($mat_total);
$mat_no=count($mat_total);
echo '<br />';
print_r($mat_no);
$mat_ave=array_sum($mat_total)/$mat_no;
$mat_ave=round($mat_ave,1);
}
}
//Biology
etc
I split the table into separate tables and have names in a separate table, not needed for calculating avaerages, so I will not show it here. Each subject table tajkes the following form:
id date exam_no term year grade class test*
*Test would be eng_mid or eng_end or mat_mid etc.
Because I had only one query which returned 10 rows (5 subjects each with two tests: e.g. eng_mid (English Mit exam), eng_end (english end of term test), I was able to capture all rows in one call and pack each subject into an array, and then work out the class average, with the help of array_map. It may not be elegant, but it worked very well. Now, I have each test in it's own table.
I was trying to write a joint so as to get a signle resultset but the query fails. The columns as like:
I know that the database design is not anything to be proud off, but coming from a huge single table, this is a massive step (worthy a pat on the shoulder).
What I wish to do is to be able to query all my data and calculate class averages (about 30 students in each class). I tried to use separate queries but I ran into a wall, in that previously I would use the WHILE conditional as shown after the query for it to pull all rows and create an array from which I could get desired results. Now several queries just makes me confused as to how I can archieve the same results since a join is not working. Also I am having a separate $row variable, and that throws me further off balance!
Is it even possible to do averages as I did on my infamous one table (from the dark side) or is my table design so messed up, what I want just isn't humanly possible?
Please any help will be deeply appreciated.
Try using union. It would be something like
select grade, test from math
union all
select grade, test from english
union all
....
Also, in my opinion, better design would be to have table exams something like that (warning, pseudo-DML):
id int primary key,
student_id int foreign key students
subject_id int foreign key subjects
exam_type_id int foreign key exam_types
grade int(????)
exam_types table would be just midterm and final, but you'll be able to easily support more types in future, if required.
subjects table will store all kinds of subjects you have (at this time there will be only five of them: math, eng, phy, etc.
The averaging query would be as simple as (yes, you can actually do aggregation in the query itself)
select student_id, avg(grade)
from exams
group by student_id

Whats quicker - Multiple PDO queries or Array Query

I am just wondering which way to go with a database query. I have started using PDO recently with mysql. I am writing a small script that checks for manufacturers and then it checks items against each manufacturer. I am stuck whether it would be quicker to place the items in an array and (only use 1 query) then as i loop for the manufacturer array use an array_count_values to get the item quantities or do a seperate query in the loop to count the items.
I have about 400 manufacturers and 70000 items at present.
my current code using array is :
$itemquery = $conn->query("SELECT manufacturer FROM item_info_short");
$itemquery->execute();
$itemrow = $itemquery->fetchall(PDO::FETCH_ASSOC);
foreach ($itemrow as $itkey => $itvalue) {
$items[] = $itvalue[manufacturer];
}
$it_qty = array_count_values($items);
and then for my loop :
$manu_query = $conn->query("SELECT manufacturer FROM manufacturers ORDER BY manufacturer");
while($rowsx = $manu_query->fetch(PDO::FETCH_ASSOC)){
$rowid = $rowsx[manufacturer];
$count = $it_qty[$rowid];
if($count == '') $count = 0;
echo "<option value=\"$rowsx[manufacturer]\">$rowsx[manufacturer] $count Items</option>";
}
As you can see i use 2 PDO queries altogether.
The other method would use 401 queries.
I am trying to see which method is best practise and/or quicker.
Thanks in Advance for any advice.
Your question is irrelevant to PDO
You're doing it extremely inefficient way, but it's irrelevant to the question you've asked.
The question you have to ask have to be not "which is faster" but "which is proper way".
To get count of manufacturers with cout of their goods, you have to make SQL to count them for you
SELECT manufacturer, count(*) cnt FROM item_info_short GROP BY manufacturer
will return all manufacturers with their goods count
if you want to get manufacturer details along - join this query with manufacturers table
if you need to list all manufacturers with their goods - use LEFT JOIN
something like this
SELECT m.manufacturer, count(i.manufacturer) cnt
FROM manufacturers m LEFT JOIN item_info_short i
ON i.manufacturer = m.manufacturer GROUP BY m.manufacturer
Thanks 'Your Common Sense' for your assistance but it still did nt show me 0 results against manufacturers that were not in the 'item_info_short' table.
SELECT m.manufacturer,
(SELECT COUNT(i.manufacturer) FROM item_info_short i
WHERE m.manufacturer = i.manufacturer) cnt
FROM manufacturers m ORDER BY m.manufacturer ASC;
This was the final mysql statement I actually have used which gives me a full list of manufacturers and their quantities from item_info_short including 0 values. In answer to my own question this method is alot quicker than putting into an array first, and i believe this to be the correct way.

Order MySQL results by number of related rows in foreign table

Alright, so I have a table outputting data from a MySQL table in a while loop. Well one of the columns it outputs isn't stored statically in the table, instead it's the sum of how many times it appears in a different MySQL table.
Sorry I'm not sure this is easy to understand. Here's my code:
$query="SELECT * FROM list WHERE added='$addedby' ORDER BY time DESC";
$result=mysql_query($query);
while($row=mysql_fetch_array($result, MYSQL_ASSOC)){
$loghwid = $row['hwid'];
$sql="SELECT * FROM logs WHERE hwid='$loghwid' AND time < now() + interval 1 hour";
$query = mysql_query($sql) OR DIE(mysql_error());
$boots = mysql_num_rows($query);
//Display the table
}
The above is the code displaying the table.
As you can see it's grabbing data from two different MySQL tables. However I want to be able to ORDER BY $boots DESC. But as its a counting of a completely different table, I have no idea of how to go about doing that.
There is a JOIN operation that is intended to... well... join two different table together.
SELECT list.hwid, COUNT(log.hwid) AS boots
FROM list WHERE added='$addedby'
LEFT JOIN log ON list.hwid=log.hwid
GROUP BY list.hwid
ORDER BY boots
I'm not sure if ORDER BY boots in the last line will work like this in MySQL. If it doesn't, just put all but the last line in a subquery.
But the result of the query into an array indexed by $boots.
AKA:
while(..){
$boot = mysql_num_rows($query);
$results[$boot][] = $result_array;
}
ksort($results);
foreach($results as $array)
{
foreach($array as ....)
{
// display table
}
}
You can switch between ksort and krsort to switch the orders, but basically you are making an array that is keyed by the number in $boot, sorting that array by that number, and then traversing each group of records that have a specific $boot value.

Looping through data from multiple tables PHP/Mysql

HI all,
I am trying to figure out how to put this into words even, but I am wanting to know how to format the output from each table separately in a "multiple table" mysql query. The output from the table1 "wall" is formatted within a while loop, but the content from table2 "actions" is already formatted(as 1 line of text with links) before it is inserted into the table(column action_body), so inside the loop I would only be outputting the action_date and action_body columns from the actions table.
I am probably not using the correct sql method(if Im doing anything right at all, that is) for the results I need, so feel free to correct my novice example, or suggest a new way to approach this.
Query:
$query = "SELECT wall.wall_id, wall.wall_owner_id, wall.wall_user_id,
wall.wall_post_date, wall.wall_post_content, actions.action_id,
actions.action_date, actions.action_user_id, actions.action_title,
actions.action_body FROM wall, actions
ORDER BY wall.wall_post_date, actions.action_date DESC";
$result = mysql_query($query);
while( $rows = mysql_fetch_assoc($result) {
// What to put here
}
Any help is appreciated, thanks, Lea
Update after comments
SELECT w.* FROM (
(SELECT
'w' as type,
wall_id as id,
wall_owner_id as owner_id,
wall_user_id as user_id,
wall_post_date as post_date,
NULL as title,
wall_post_content as content
FROM wall
WHERE wall_owner_id = x # user id of owner
)
UNION
(SELECT
'a' as type,
action_id as id,
action_user_id as owner_id,
NULL as user_id,
action_post_date as post_date,
action_title as title,
action_body as content
FROM actions
WHERE action_user_id = x # user id of owner
)
) w
ORDER BY w.post_date DESC
Because you don't JOIN on a specific field, you're gonna get every row-row combination of the two tables, which is a whole lot more data than you probably want.
You'd be better of by doing 2 queries, one for each table. While looping through the result of each table, you can collect the data you want in one array, with the field you want to sort it by as array key.
Then you sort the array, and loop through it to print it out.

Categories