Convert MySQL Query into JSON using PHP - 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) );

Related

Perform query on existing SQL result? Find result from subset of SQL result

I have a script that goes through all order history. It takes several minutes to print the results, but I noticed I perform several SQL statements that are similar enough I wonder if you could do another query on an existing SQL result.
For example:
-- first SQL request
SELECT * FROM orders
WHERE status = 'shipped'
Then, in a foreach loop, I want to find information from this result. My naive approach is to perform these three queries. Note the similarity to the query above.
-- grabs customer's LTD sales
SELECT SUM(total) FROM orders
WHERE user = :user
AND status = 'shipped'
-- grabs number of orders customer has made
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total != 0
-- grabs number of giveaways user has won
SELECT COUNT(*) FROM orders
WHERE user = :user
AND status = 'shipped'
AND total = 0
I end up querying the same table several times when the results I seek are subsets of the first query. I'd like to get information from the first query without performing more SQL calls. Some pseudocode:
$stmt1 = $db->prepare("
SELECT * FROM orders
WHERE status = 'shipped'
");
$stmt1->execute();
foreach($stmt1 as $var) {
$username = $var['username'];
$stmt2 = $stmt1->workOn("
SELECT SUM(total) FROM this
WHERE user = :user
");
$stmt2->execute(array(
':user' => $username
));
$lifesales = $stmt2->fetchColumn();
$stmt3 = $stmt1->workOn("
SELECT COUNT(*) FROM this
WHERE user = :user
AND total != 0
");
$stmt3->execute(array(
':user' => $username
));
$totalorders = $stmt3->fetchColumn();
$stmt4 = $stmt1->workOn("
SELECT COUNT(*) FROM this
WHERE user = :user
AND total = 0
");
$stmt4->execute(array(
':user' => $username
));
$totalgaws = $stmt4->fetchColumn();
echo "Username: ".$username;
echo "<br/>Lifetime Sales: ".$lifesales;
echo "<br/>Total Orders: ".$totalorders;
echo "<br/>Total Giveaways: ".$totalgaws;
echo "<br/><br/>";
}
Is something like this possible? Is it faster? My existing method is slow and ugly, I'd like a quicker way to do this.
We could do one pass through the table to get all three aggregates for all users:
SELECT s.user
, SUM(s.total) AS `ltd_sales`
, SUM(s.total <> 0) AS `cnt_prior_sales`
, SUM(s.total = 0) AS `cnt_giveaways`
FROM orders s
WHERE s.status = 'shipped'
GROUP
BY s.user
That's going to be expensive on large sets. But if we are needing that for all orders, for all users, that's likely going to be faster than doing separate correlated subqueries.
An index with leading column of user is going to allow MySQL to use the index for the GROUP BY operation. Including the status and total columns in the index will allow the query to be satisfied entirely from the index. (With the equality predicate on status column, we could also try an index with status as the leading column, followed by user column, then followed by total.
If we only need this result for a small subset of users e.g. we are fetching only the first 10 rows from the first query, then running a separate query is likely going to be faster. We'd just incorporate the condition WHERE s.user = :user into the query, as in the original code. But run just the one query rather than three separate queries.
We can combine that with the first query by making it into an inline view, wrapping it in parens and putting into the FROM clause as a row source
SELECT o.*
, t.ltd_sales
, t.cnt_prior_sale
, t.cnt_giveaways
FROM orders o
JOIN (
SELECT s.user
, SUM(s.total) AS `ltd_sales`
, SUM(s.total <> 0) AS `cnt_prior_sales`
, SUM(s.total = 0) AS `cnt_giveaways`
FROM orders s
WHERE s.status = 'shipped'
GROUP
BY s.user
) t
ON t.user = o.user
WHERE o.status = 'shipped'
I'm not sure about that column named "prior" sales... this is returning all shipped orders, without regard to comparing any dates (order date, fulfillment date, shipment date), which we would typically associate with a concept of what "prior" means.
FOLLOWUP
noticing that the question is modified, removing the condition "status = 'shipped'" from the count of all orders by the user...
I will note that we can move conditions from the WHERE clause into the conditional aggregates.
Not that all these results are needed by OP, but as a demonstration...
SELECT s.user
, SUM(IF(s.status='shipped',s.total,0)) AS `ltd_sales_shipped`
, SUM(IF(s.status<>'shipped',s.total,0)) AS `ltd_sales_not_shipped`
, SUM(s.status='shipped' AND s.total <> 0) AS `cnt_shipped_orders`
, SUM(s.status='canceled') AS `cnt_canceled`
, SUM(s.status='shipped' AND s.total = 0) AS `cnt_shipped_giveaways`
FROM orders s
GROUP
BY s.user
Once the results are returned from the database, you can not run an SQL on top of them. However you can store them in a temporary table, to reuse them.
https://dev.mysql.com/doc/refman/8.0/en/create-temporary-table.html
https://dev.mysql.com/doc/refman/8.0/en/create-table-select.html
https://dev.mysql.com/doc/refman/8.0/en/insert-select.html
You need to create a temporary table, and insert all the data from the select statement, and then you can run queries on that table. Not sure if it would help much in your case.
For your particular case you can do something like:
select user, (total = 0) as is_total_zero, count(*), sum(total)
from orders
where status = 'shipped'
group by user, total = 0
However you would have to do some additional summing to get the results of the second query which gives you the sums per user, as they would be divided into two different groups with a different is_total_zero value.

Why do i double my display in json

So i fetch my data from two tables in my php and encode it in one json object. I got everything i needed except that it doubles the display. And my teamone is located in the matches tables. instead of starting from array 0, it starts after the schedules tables. Which is array 7. I dont know why this happen.
Here is my php.
$sql = "SELECT * from schedule, matches;";
$con = mysqli_connect($server_name,$mysql_user,$mysql_pass,$db_name);
$result = mysqli_query($con,$sql);
$response = array();
while($row=mysqli_fetch_array($result))
{
array_push($response, array("start"=>$row[4],"end"=>$row[5],"venue"=>$row[6], "teamone"=>$row[8], "teamtwo"=>$row[9],
"s_name"=>$row[17]));
}
echo json_encode (array("schedule_response"=>$response));
mysqli_close($con);
?>
Here is my display. As you can see there are four displays but in my database it only has 2. It doubles the display. How do i fix this? Thanks
{ "schedule_response":[
{"start":"2016-11-10 00:00:00","end":"2016-11-04 00:00:00","venue":"bbbb","teamone":"aaa","teamtwo":"bbb",
"s_name":"hehehe"},
{"start":"2016-11-23 00:00:00","end":"2016-11-24 00:00:00","venue":"bbbbbbbb","teamone":"aaa","teamtwo":"bbb",
"s_name":"hehehe"},
{"start":"2016-11-10 00:00:00","end":"2016-11-04 00:00:00","venue":"bbbb","teamone":"ehe","teamtwo":"aha",
"s_name":"aaa"},
{"start":"2016-11-23 00:00:00","end":"2016-11-24 00:00:00","venue":"bbbbbbbb","teamone":"ehe","teamtwo":"aha",
"s_name":"aaa"}]}
I need to get the teamone, teamtwo and s_name values from the matches while i need the start, end and the venue from the schedule table in one query.
Schedule table
Matches Table
Because of your SQL query, you should not forget to perform some form of a grouping (the way you select results from both table defines that):
$sql = "SELECT * from schedule s, matches m GROUP BY s.id"; //I assume that your table schedule has an `id`
Or, you can rework the query to be more readable:
$sql = "SELECT s.*, m.* FROM schedule s
INNER JOIN matches m ON m.schedule_id = s.id
GROUP BY s.id"; //I assume that you have such database design that you have defined foreign key on table `matches`.
Of course, the INNER JOIN above could be LEFT OUTER JOIN - it all depends on your database design.
I think it is the problem with mysqli_fetch_array().
Can you please change mysqli_fetch_array() to mysqli_fetch_assoc()

How to add a single column php/mysql

I'm trying to add a single column in a db query result. I've read about the SUM(col_name) as TOTAL, GROUP BY (col_name2).
But is there a way i can only SUM the column without any GROUPing? I a case whereby all col_name2 are all unique.
For example... I have a result with the following col headers:
course_code
course_title
course_unit
score
grade
Assuming this have 12 rows returned into an HTML table. Now i want to perform SUM() on all the values (12 rows) for the column course_unit, in other to implement a GPA school grading system.
How can i achieve this.
Thanks.
SELECT SUM(col_name) as 'total' FROM <table>
GROUP BY is required only if you want to sum subsets of the rows in the table.
You can find sum or any aggregate db functions (such as count, avg, etc) for most cases without using group clause. Your sql query may look something like this:
SELECT SUM(course_unit) as "Total" FROM <table_name>;
As comments below have already pointed out: SELECT SUM(course_unit) AS total FROM your_table;. Note that this is a separate query to the one with which you retrieve the table data.
This does it in php. I'm not sure how to do it with pure sql
$query = "SELECT * FROM table";
$result = mysql_query($query);
$sum = 0;
while($row = mysql_fetch_assoc($result))
{
$sum+= intval($row['course_unit']);
}
echo $sum;
SELECT
course_code,
course_title,
course_unit,
score, grade,
(select sum(course_unit) from TableA) total
from TableA;

getting rid of repeated customer id's in mysql query

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";
}
}

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