how to insert data while looping and inside array as well - php

This is my table order:
+-------+-------+
| month | count |
+-------+-------+
| 6 | 11 |
| 11 | 27 |
| 12 | 9 |
+-------+-------+
I want to create a graph using fusioncharts. Let's say that the year is 2017. How do I put the missing month into the array of which fusioncharts I'm using? I'm stuck on the condition.
This is my code:
$strQuery2 = "SELECT DATE_FORMAT(order_date, '%c') as month, COUNT(*) AS cnt FROM orders where YEAR(order_date)='2017' GROUP BY month ORDER BY `month` DESC";
$result2 = $dbhandle->query($strQuery2);
// Push the data into the array
while($row2 = $result2->fetch_assoc()) {
$q = $row2["month"];
$w = $row2["cnt"];
$e = 0;
$x = 1;
if ($x != $q){
echo "true";
array_push($arrData2["data"],
array(
"label" => $row2[$x],
"value" => $row2[$e]
)
);
}
else{
echo "false";
array_push($arrData2["data"],
array(
"label" => $row2["month"],
"value" => $row2["cnt"]
)
);
}
$x++;
echo $row2["month"] . "<br />";
echo $row2["cnt"] . "<br />";
}

You never get a month from the database that isn't there. If you push data into an empty array:
array_push($arrData2["data"]
you still end up with an array that only contains the months returned from the database.
What you can do is first create an array with all 12 months:
$arrData2["data"]=range(1,12,false);
You have now an array that contains 12 elements (months) from 1 - 12, all with values of false. (You could use any value, an array or false if you need).
Now inside your resultloop, just replace the array elements you have values for:
$arrData2["data"][$row2["month"]] = array(
'label' => $row2["month"],
'value' => $row2["cnt"]
);
$arrData2 now will look like:
array(
1 => false,
2 => false,
3 => false,
4 => false,
5 => false,
6 => array('label'=> 6,'value'=> 11),
7 => false,
8 => false,
9 => false,
10 => false,
11 => array('label'=> 11,'value'=> 27),
12 => array('label'=> 12,'value'=> 9)
)
Now you can replace all the false values for arrays with value 0
So your whole code could be schortened to:
//set empty array
$arrData2['data'] = range(1, 12, false);
//fill elements we know
while($row2 = $result2->fetch_assoc()) {
$arrData2["data"][$row2["month"]] = array(
'label' => $row2["month"],
'value' => $row2["cnt"]
);
}
//replace the rest with 0
foreach($arrData2["data"] as $key => $value){
// skip what we already know
if($values !== false) continue;
// replace the rest
$arrData2["data"][$key]=array(
'label'=>$key,
'value'=> 0
);
}
echo print_r($arrData2,true);

There are basically two ways to approach it. One to fill missing months on PHP side and one on SQL. I will give you SQL version:
SELECT
m.`month`,
IFNULL(x.cnt, 0) AS cnt
FROM
(SELECT 1 AS `month` UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 10 UNION SELECT 11 UNION SELECT 12) AS m
LEFT JOIN (SELECT DATE_FORMAT(order_date, '%c') as `month`, COUNT(*) AS cnt FROM orders where YEAR(order_date)='2017' GROUP BY `month`) AS x ON m.`month` = x.`month`
ORDER BY m.`month` DESC
It basically generates list of all months from 1 to 12 and if such month exists in your query than it is filled with real value. Otherwise it's zero.

Related

PHP/SQL Recursive Function - how to handle limited selects on mysqli while loop with sub selects

I have a table with products.
Sub-products can be assigned to a main product.
products
________________________________________________________________________
id | product_title | main_id | owner_id
1 | Volvo | 0 | 1
2 | Mercedes | 0 | 2
3 | Chevrolet | 0 | 1
4 | Rear lights | 1 | 1
5 | Glasses | 1 | 1
6 | Seats | 1 | 1
7 | Heater | 1 | 1
8 | Radio | 6 | 1
12 | Tyres | 6 | 1
13 | Rearview mirror | 8 | 1
14 | Door | 8 | 1
15 | Engine | 14 | 1
15 | Door | 3 | 1
I use function get_the_list(id = 0, owner_id = 1);
function get_the_list(id = 0, owner_id = 1) {
$query = "SELECT * FROM products WHERE main_id = $id AND owner_id = $owner_id";
while ($row = mysqli_fetch_array($result, MYSQLI_BOTH)) {
$list .= $row[product_title];
// select sub products from main_id
$list .= get_the_list($row[main_id], 1);
}
}
echo get_the_list(id = 0, owner_id = 1);
On this way I get the whole product list. Works well, no problems.
(1) i: 1 --- loop: 1, 1 - Volvo
i: 1 --- loop: 2, 2 - Rear lights
i: 2 --- loop: 3, 2 - Glasses
i: 3 --- loop: 4, 2 - Seats
i: 1 --- loop: 5, 3 - Radio
i: 1 --- loop: 6, 4 - Rear-view mirror
i: 2 --- loop: 7, 4 - Door
i: 1 --- loop: 8, 5 - Engine
i: 2 --- loop: 7, 3 - Tyres
i: 4 --- loop: 6, 2 - Heater
____________________________
(2) i: 2 --- loop: 1, 1 - Chevrolet
i: 1 --- loop: 2, 2 - Door
____________________________
(3) i: 3 --- loop: 1, 1 - Mercedes
____________________________
(4) i: 4 --- loop: 1, 1 - XX
____________________________
First (number) is main_id.
Second number is running $i++ in while.
Third number should be continuous/ongoing counter. BUT this breaks after level 5.
fourth number after comma is level.
I have to limit a select statement to only 8 products (incl. sub products).
So I will end, for example, with rear-view mirror on this image example.
It works.
But it works not after more than 8, because the counter breaks.
How can I limit the number of products that can be retrieved, what select statement should I choose? OR WHAT php workaround?
enter image description here
First of all consider moving to PDO instead of mysqli. Then set main_id nullable and add a foreign key for it as products.main_id -> products.id.
The modern way to handle this task is using CTE.
I'not using the owner_id here and I assume you only have two levels in your products tree:
WITH root_products AS (
SELECT
products.id,
products.main_id,
products.product_title,
products.id as sequence_route
FROM products WHERE main_id IS NULL OR main_id = 0
LIMIT 0, 20 -- this is where limit is set
)
SELECT * FROM (
SELECT * FROM root_products
UNION
SELECT
id,
main_id,
product_title,
CONCAT( main_id, '.', id ) sequence_route
FROM products
WHERE
main_id IN ( SELECT id FROM root_products )
) result
ORDER BY sequence_route
Note that this solution is using CTE. Check if your DB server version supports those. Tested with MySQL 8+
If you need some simplier queries, you will need two of those:
$query = "SELECT * FROM products WHERE main_id = 0 OR main_id IS NULL LIMIT 0, 20";
This will fetch 20 top-level products. And:
$query = "SELECT * FROM products WHERE main_id IN( " . implode(',', $root_ids_you_take_from_the_prev_query ) . " )";
will get the subproducts.
Upd:
Here's the query for unlimited depth. Its is still tied to a root product with id = 1, you might want to leave this in place for performance reasons or rework the WHERE clause to main_id IS NULL at the first SELECT to grab all the root entries. This query implements 'way #2'. It is still all about an ORDER BY. If you need to roll back to a layer-by-layer 'way #1', just drop the ORDER BY clause and corresponding rank/recursion_level columns from CTE.
-- This CTE will take the given root and
-- recursively grab all its descendants,
-- layer after layer
WITH RECURSIVE deeper_products AS (
SELECT
0 as recursion_level -- This is required for ranking
, CAST(id AS CHAR) as `rank` -- This should be 'CAST(id AS FLOAT)' but i'm 2 lazy to update my mysql installation
-- Server makes conversions internally just fine anyways
, CAST(id as CHAR) as sequence_route -- Just a visual helper, does nothing
, id
, main_id
, product_title
FROM products
WHERE id = 1 -- the root product id
UNION
SELECT
prev_layer.recursion_level + 1 as recursion_level
, prev_layer.rank + products.id / POWER( 100, prev_layer.recursion_level + 1 ) as `rank` -- I assume there'r less than 100 children there
, CONCAT( prev_layer.sequence_route, '.', products.id ) sequence_route
, products.id
, products.main_id
, products.product_title
FROM products
INNER JOIN deeper_products prev_layer
ON products.main_id = prev_layer.id
)
SELECT * FROM deeper_products
ORDER BY `rank` -- Remove this and the rank/recursion_level fields from CTE if you need a level-by-level ordering
LIMIT 0, 8 -- Your limit
Upd2:
Here's a PHP solution. I'm using PDO here (messing with mysqli is a pain). No recursion needed, so the limit is calculated pretty simple:
<?php
$pdo = new PDO("mysql:dbname=test;host=localhost", 'test', 'SECRET_PASSWORD');
function get_list( $dbh, $pid = null ){
$out = [];
$limit = 8;
// The regular query - items never have NULL as their main_id
$regular_query = "SELECT * FROM products WHERE main_id = ? LIMIT 0, $limit";
// The root query - items might have NULL as main_id
$root_query = (
is_null( $pid )
? "SELECT * FROM products WHERE main_id IS NULL LIMIT 0, $limit"
: $regular_query
);
// Results stack contains statements
// Every deeper level will add a new statement the top
$root_sth = $dbh->prepare( $root_query );
$root_sth->execute( [ $pid ] );
$results_stack = [ $root_sth ];
// Fill in the final result, loop untill the limit reached
// or while we still have records in DB
while( count( $out ) < $limit and count( $results_stack ) ){
// Take the topmost statement..
$sth = $results_stack[ count( $results_stack ) - 1 ];
// .. and grap the row
$row = $sth->fetch(PDO::FETCH_BOTH);
// The row is there, so we..
if( $row ){
// ..save it in the final result..
$out[] = $row;
// ..and add a new query returning children of a
// current item on the top of a stack
$regular_sth = $dbh->prepare( $regular_query );
$regular_sth->execute( [ $row['id'] ] );
$results_stack[] = $regular_sth;
}
// No row - we'r out of data with this query, remove the it from the stack
else{
array_pop( $results_stack );
}
}
return $out;
}
print_r( get_list( $pdo ) );
Upd3:
This builds the UL structure. I assume you've added the depth key for the items:
<?
$data = [
[ 'title' => 'Item 1', 'depth' => 1 ],
[ 'title' => 'Item 2', 'depth' => 1 ],
[ 'title' => 'Item 2.1', 'depth' => 2 ],
[ 'title' => 'Item 2.2', 'depth' => 2],
[ 'title' => 'Item 2.2.1', 'depth' => 3 ],
[ 'title' => 'Item 2.2.2', 'depth' => 3 ],
// [ 'title' => 'Item 3', 'depth' => 1 ],
];
?>
<ul>
<?
foreach( $data as $i => $row ){
?><li><?=$row['title']?><?
// If we even have a next level
if( !empty( $data[$i + 1]) ){
// If next item is on a level higher - open ul tag for it
if( $data[$i + 1]['depth'] > $row['depth'] ){?>
<ul>
<?}
// If next item is a level lower - close li/ul tags between the levels and close higher li
elseif( $data[$i + 1]['depth'] < $row['depth'] ){?>
<?=str_repeat('</li></ul>', $row['depth'] - $data[$i + 1]['depth'] )?>
</li>
<?}
// Next item is on the same level, just close self li and go on
else{?>
</li>
<?}
}
// This is the last item, close li/ul to the end
else{
print str_repeat("</li></ul>", $row['depth'] );
}
}?>

How to get individual data from an array that is outside the loop (while) to insert that data into another table?

I have done the following to be able to get the results outside the loop (while):
$date_day = date('Y-m-d');
$stmt = $con->prepare("
SELECT MAX(l.id_logtrama) AS id_logtrama
, MAX(l.fechaHora) AS fechaHora
, l.idCliente
, l.idEquipo
, MAX(l.statusGlobal) AS statusGlobal
, COUNT(*) AS total
FROM logtrama l
JOIN equipo e
ON l.idEquipo = e.idEquipo
AND l.idCliente = e.idCliente
WHERE DATE(l.fechaHora)=?
GROUP
BY l.idCliente
, e.idEquipo
");
$stmt->bind_param("s", $date_day);
$stmt->execute();
$stmt->store_result();
$DataArray = [];
$stmt->bind_result(
$DataArray['id_logtrama'],
$DataArray['fechaHora'],
$DataArray['l.idCliente'],
$DataArray['l.idEquipo'],
$DataArray['statusGlobal'],
$DataArray['total']
);
while ($stmt->fetch()) {
$row = [];
foreach ($DataArray as $key => $val) {
$row[$key] = $val;
}
$array[] = $row;
}
print_r($array);
As a result, I get the following:
Array ( [0] =>
Array ( [id_logtrama] => 4
[fechaHora] => 2021-04-19 22:01:09.059800
[l.idCliente] => 20
[l.idEquipo] => 1
[statusGlobal] => 2
[total] => 1 )
[1] => Array (
[id_logtrama] => 3
[fechaHora] => 2021-04-19 22:01:05.520600
[l.idCliente] => 20
[l.idEquipo] => 8
[statusGlobal] => 2
[total] => 3 )
)
I need to be able to insert that data into another table for example alert_notify:
id_notify id_logtrama idCliente idEquipo alert_type count_notify notify_date
1 4 20 1 2 1 2021-04-19 22:01:09.059800
2 3 20 8 2 3 2021-04-19 22:01:05.520600
How can I achieve that goal?
I suggest you to make a single statement for SELECT and INSERT.
Doing something like this:
INSERT INTO alert_notify AS SELECT * FROM (
SELECT
MAX(l.id_logtrama) AS id_logtrama,
MAX(l.fechaHora) AS fechaHora,
l.idCliente,
l.idEquipo,
MAX(l.statusGlobal) AS statusGlobal,
COUNT(*) AS total
FROM logtrama l
JOIN equipo e
ON l.idEquipo=e.idEquipo
AND l.idCliente=e.idCliente
WHERE DATE(l.fechaHora)=?
GROUP BY l.idCliente, e.idEquipo
) AS t
WHERE NOT EXISTS (
SELECT 'x'
FROM alert_notify AS an
WHERE an.idCliente = t.idCliente
AND an.idEquipo = t.idEquipo
AND an.notify_date = t.fetchaHora
)
In this way only records without a corrispondence for idCliente, idEquipo, notify_date on alert_notify will be inserted.
PS: Explicit the line INTO alert_notify AS SELECT * with the fields list in the correct order, if the order of the defined field in alert_notify differs from the order of the fields returned by the SELECT

Use array as table in MySql JOIN

I would like to use data from an array to add a column and make a join on a MySql table.
Let's say, on one hand, we have an array ($relevance):
$relevance = array(
array('product_id' => 1, 'relevance' => 2),
array('product_id' => 2, 'relevance' => 5),
array('product_id' => 3, 'relevance' => 1),
);
And on the other hand, we have this table (products):
product_id | product_name
--------------------------
1 | Product 1
2 | Product 2
3 | Product 3
Now, I want to select data from the products table and joining them with $relevance based on their product_id in order to get something like this:
product_id | product_name | relevance
---------------------------------------
1 | Product 1 | 2
2 | Product 2 | 5
3 | Product 3 | 1
In other words, how can I make a SELECT with LEFT JOIN using data from both the MySql database and an array which would "mean" something like this:
SELECT `p`.*, `{{$relevance}}`.* FROM `products` AS `p`
LEFT JOIN `{{$relevance}}`
ON p.product_id = {{$relevance}}.product_id
pure sql solution, not efficient though for big recordsets:
$relevances = array()
foreach ($relevance as $v){
$relevances[] = "SELECT {$v['product_id']} as product_id, {$v['relevance']} as relevance"
}
$sql = implode(' UNION ', $relevances);
$sql = "SELECT p.product_id, p.product_name, r.relevance
FROM products p
JOIN ($sql) r ON p.product_id=r.product_id";
Well, you can make another table relevance and then you could just use JOIN. Or you can use loop to get those data. Something like
$relevance = array(
array(1, 'relevance' => 2),
array(2, 'relevance' => 5),
array(3, 'relevance' => 1),
);
$q = mysql_query("SELECT * FROM products")
while($r = mysql_fetch_assoc($q))
{
$productRelevance = $relevance[$r['prod_id']-1];
}
Hoewever this code may fail if you delete some product and those ids wouldn' be in order, e.g.: 1,2,5,6,7,10. I recommend you to use another table.

mysql query calculate rating sum

I have following table
id item_id rating
1 10 1
2 10 2
3 10 2
4 10 3
5 10 3
6 10 3
7 10 4
How can I write a query so that I can get result as follows:
$ratings = array(
1 => 1,
2 => 2,
3 => 3,
4 => 1,
5 => 0
);
I need to use this query to write a php function to calculate average ratings by this function:
$total = 0;
$count = 0;
foreach($ratings as $number=>$frequency) {
$total += $number * $frequency;
$count += $frequency;
}
return $total / $count;
The basic COUNTing of records on the table won't produce 5 because there is no value 5 on the rating. But generating subquery which contains values from 1-5 and joining them using LEFT JOIN will do your desired result.
SELECT a.rating, COUNT(b.rating) totalCount
FROM
(
SELECT 1 rating UNION SELECT 2 UNION
SELECT 3 UNION SELECT 4 UNION
SELECT 5
) a
LEFT JOIN tableName b
ON a.rating = b.rating
GROUP BY a.rating
SQLFiddle Demo
SELECT
rating,
COUNT(rating) as Count
FROM rating
GROUP BY rating

multiple sorting in php or/and mysql doesn't take effect

EDIT
solved ! it was my fault, the mysql query and the php method, both are working perfectly. my very bad example cunfused myself. aktually they should be sorting products by favorite ( true / false ) and then by date.
i'm trying to sort a multidimensional array by two "cols" but it just won't work for me :O
I've serched and copyed all possible ways ( mysql - order by, php - array_multisort() )
but it still won't work for me....
here is the method i've tryed first:
// MySQL Query
SELECT * FROM `test` ORDER BY `name` ASC, `age` DESC;
// or
SELECT * FROM `test` ORDER BY `name`, `age`;
// or
SELECT * FROM `test` ORDER BY name, age;
// or
SELECT * FROM `test` ORDER BY name, age DECT;
// `name` is VARCHAR and `age` is INT
so , i've tryed all possible syntax, but the query wont give me the wanted result.
the original table :
ID NAME AGE
---------------------
1 adam 23
2 bernd 30
3 cris 22
4 dora 21
5 anton 18
6 brad 36
7 sam 41
8 ali 13
what i want to get from MySQP Query :
ID NAME AGE
--------------------- // first sort by name...
8 ali 13 // then sort by age
5 anton 18 // ...
1 adam 23 // for each "similar" name or names with "a", "b", ...
2 bernd 30 // and so on...
6 brad 36
3 cris 22
4 dora 21
7 sam 41
but it actually gives my either sorted by NAME ( ORDER BY name, age ) or AGE ( ORDER BY age, name;
as i get frustrated, i've decidet to just get that query and sort it in PHP...
well, i've also tryed some codes but they all don't work either.
here is the one i aktually did now:
// this array is just exported from mysql to debug without mysql query...
$test = array(
array('id' => '1','name' => 'adam','age' => '23'),
array('id' => '2','name' => 'bernd','age' => '30'),
array('id' => '3','name' => 'cris','age' => '22'),
array('id' => '4','name' => 'dora','age' => '21'),
array('id' => '5','name' => 'anton','age' => '18'),
array('id' => '6','name' => 'brad','age' => '36'),
array('id' => '7','name' => 'sam','age' => '50'),
array('id' => '8','name' => 'ali','age' => '13')
);
// ...print out the original array
foreach( $test as $key => $value ) {
echo $value['name'] . " - " . $value['age'] . "<br>";
}
// here is the part where i am sorting...
$sort = array();
foreach ($test as $key => $value ) {
$sort['name'][$key] = $value['name'];
$sort['age'][$key] = $value['age'];
}
array_multisort($sort['age'], SORT_ASC, $sort['name'], SORT_DESC, $test);
// ...till here.
// reorder array and print the new sorted array
$sorted = array();
for( $i = 0; $i < count( $sort['name'] ); $i++ ) {
array_push( $sorted, array( $sort['name'][$i], $sort['age'][$i] ) );
echo $sort['name'][$i] . " - " . $sort['age'][$i] . "<br>";
}
but as you'll see if you test this, the sorting will just affect the AGE...
i dont know what i am doing wrong, prease tell me guys T_T
It appears to me that you only want to sort on the first char of the name:
You should try:
SELECT * FROM `test` ORDER BY SUBSTR(name,0,1), age;
What you encountered is absolutely expected behaviour. Why? If you sort first by name, you get results sorted for the full strings in the name column, where
'adam' < 'ali' < 'anton'
If there were more than one rows with name='adam', but different ages, like this:
ID NAME AGE
---------------------
1 adam 23
5 anton 18
8 ali 13
9 adam 52
You would get this result for SELECT * FROM test ORDER BY name, age;
ID NAME AGE
---------------------
1 adam 23
9 adam 52
8 ali 13
5 anton 18
As it is first sorted by name column, where the two adam values are the same, and then sorts based on the age, where 23 is smaller than 52...
But now, sorting on first character of name, and age SELECT * FROM test ORDER BY SUBSTR(name,0,1), age;:
ID NAME AGE
---------------------
8 ali 13
5 anton 18
1 adam 23
9 adam 52
If I unserstand you correctly, what you want to do is to sort just on the first letter of the name, then on the age. - What you have gotten from mysql is just what I would expect from your query and data set. If you choose several columns for sort, the secound kicks in only if you have equal values in the first one, eg if you had two Adams, one 18 and one 20 years old, the two would be sorted by age. and if you had Adam and Beth, both 18, Beth would come after Adam in the "sort by age,name"

Categories