My mysql table looks like this:
I need to make 3 level tree, without House_NR.
-Country
|--City 1
| |--Street 1
| |--Street 2
|
|--City 2
|--City 3
| |--Sreet 1
| |--Steet 2
In PHP I make this sql query:
SELECT
l.Country_ID,
c1.Name AS Country_Name,
l.City_ID,
c2.Name AS City_Name,
l.Street_ID,
s.Name AS Street_Name,
l.House_NR
FROM Location l
JOIN Country c1 ON c1.id = l.Country_ID
JOIN City c2 ON c2.id = l.City_ID
JOIN Street s ON s.id = l.Street_ID
And I have this function:
function getTree($data) {
$tree = prepareTree($data);
drawTree($tree);
}
function prepareTree($data) {
$nodeList = array();
$tree = array();
foreach ($data as $key) {
$tree[$key["Country_ID"]]["name"] = $key["Country_Name"];
$tree[$key["Country_ID"]]["id"] = $key["Country_ID"];
$tree[$key["Country_ID"]]["level"] = 1;
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["name"] = $key["City_Name"];
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["id"] = $key["City_ID"];
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["level"] = 2;
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["children"][$key["Street_ID"]]["name"] = $key["Street_Name"];
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["children"][$key["Street_ID"]]["id"] = $key["Street_ID"];
$tree[$key["Country_ID"]]["children"][$key["City_ID"]]["children"][$key["Street_ID"]]["level"] = 3;
}
return $tree;
}
function drawTree($data, $sub = false) {
echo ($sub) ? "<ul class=\"tree-listCat\">" : "<ul class=\"tree-list\">";
foreach($data as $key) {
if ($key["children"] != null) {
echo "<li class=\"tree-itemCat\"><span class=\"tree-itemSel\">" . $key["name"] . "</span></li>";
drawTree($key["children"], true);
} else
echo "<li class=\"tree-item\"><span class=\"tree-itemSel\">" . $key["name"] . "</span></li>";
}
echo "</ul>";
}
This function works fine, but there some problem in function where function draw a html version of tree now I have this: action=search&type=tree&level=" . $key["level"] . "&id=" . $key["id"], but I need to have something like this:
If only country without child then action=search&type=tree&country=id
If only country and city without child street, then action=search&type=tree&country=id&city=id
If all arguments country, city and steet, then action=search&type=tree&country=id&city=id&street_id=id
And also function prepare tree looks bad (In my mind). In few words I need make a tree from Location database.
Related
I have Articles with Categories in a mysql Database. I would like to print out all Articles with their corresponding Categories as JSON in PHP to fetch with my Vue-App.
I'm working with the following tables: Articles, Categories and Article_has_Category (junction table, many to many):
Articles
"ID" | "Title"
------------
1 | First
2 | Second
Categories
"ID" | "Category"
------------
1 | Lifestyle
2 | Webtech
Article_has_Categories
"ID" | "Article_ID" | "Category_ID"
--------------------------------------
1 | 1 | 1
2 | 1 | 2
The following PHP-Code selects and prints all Articles for my Frontend to fetch:
$stmt = $pdo->prepare("SELECT * FROM Articles;");
$stmt->bindParam(':param');
if ($stmt->execute()) {
$array = $stmt->fetchAll();
$jsonArray = json_encode($array);
print_r($jsonArray);
}
Printed JSON-Output:
[
{"ID":"1","Title":"First"},
{"ID":"2","Title":"Second"}
]
Is it somehow possible to insert all Categories as an array into that JSON-Output?
Desired JSON-Output:
[
{"ID":"1","Title":"First", "Categories": "[Lifestyle, Webtech]" },
{"ID":"2","Title":"Second", "Categories": "[]"}
]
Currently I'm building the desired object in my frontend first using "SELECT * FROM Articles;" to fetch all articles and then in a seperate call, fetching the corresponding categories by Article ID using the statement below:
SELECT c.Category
FROM article_has_category ac
INNER JOIN Categories c ON c.ID = ac.Category_ID
WHERE ac.Article_ID = :id;
Is there any solution combining the two statements and building the desired object directly in my PHP File?
Okay I solved this by assembling my own JSON in PHP, instead of using json_encode().
My code is not very pretty but I commented it a bit for you to understand:
<?php
$stmt = $pdo->prepare("
SELECT * FROM Articles;
");
$stmt_categories = $pdo->prepare("
SELECT c.Category
FROM article_has_category ac
INNER JOIN Categories c ON c.ID = ac.Category_ID
WHERE ac.Article_ID = :id;
");
if ($stmt->execute()) {
$result = $stmt -> fetchAll();
// count items in result, in order to determine later which is the last one
$numItems = count($result);
$i = 0;
// prepare the json-array to print out in the php file
$printArray = '[';
// for each article, run the second sql-statement using the article-ID
foreach( $result as $row ) {
$stmt_categories->bindParam(':id', $row['ID']);
// executing the second statement
if ($stmt_categories->execute()) {
$result_category = $stmt_categories -> fetchAll();
// save the fetched categories into a new array, using the function makeArray()
$categories = makeArray($result_category);
// build the json object by hand, no more need for json_encode()
$element = '{';
$element .= ' "ID": ';
$element .= '"' . $row['ID'] . '",';
$element .= $categories;
$element .= ' "Title": ';
$element .= '"' . $row['Title'] . '"';
$element .= '}';
if(++$i === $numItems) {
// if it's the last item, do nothing
} else {
// if not, add a comma
$element .= ',';
}
// add the built element to the printArray
$printArray .= $element;
}
}
$printArray .= ']';
// finally print the array
print_r($printArray);
}
function makeArray($categoryArray){
$category_element .= ' "Category": ';
$category_element .= "[";
$numCategories = count($categoryArray);
$n = 0;
foreach( $categoryArray as $row ) {
$category_element .= '"' . $row['Category'] . '"';
if(++$n === $numCategories) {
// if it's the last item, do nothing
} else {
// if not, add a comma
$category_element .= ',';
}
}
$category_element .= "],";
return $category_element;
}
I have this mysql table "categories":
id category parent
1 category1 NULL
2 subcategory1 1
3 category2 NULL
4 subcategory2 2
and I want to get this result:
category1
subcategory1
subcategory2
category2
to get this result I use this code, but is very slow:
foreach($db->query("SELECT * FROM categories WHERE parent IS NULL") as $parent)
{
$t=0;
categories($db,$t,$parent['id'],$parent['category']);
}
function categories($db,$t,$id,$category)
{
echo"<option value=".$id.">";//
for($i=0;$i<$t;$i++) {echo" "; $GLOBALS['cat'].=" ";}
echo $category."</option>";//" ".$id.
$GLOBALS['cat'].=$category."<br>";
$t++;
if($db->query("SELECT * FROM categories WHERE parent=".$id)->rowCount()>0)
{
foreach($db->query("SELECT * FROM categories WHERE parent=".$id) as $child)
categories($db,$t,$child['id'],$child['category']);
}
}
Do you have a faster solution?
Thanks
If you know the depth of your tree (maximum or desired), you can have it all in SQL, flattened to a row representing path:
SELECT c.category, c1.category AS sub1, c2.category AS sub2, c3.category AS sub3
FROM categories c
LEFT JOIN
(categories c1
LEFT JOIN
(categories c2
LEFT JOIN categories c3
ON c3.parent = c2.category)
ON c2.parent = c1.category)
ON c1.parent = c.category
WHERE c.parent IS NULL;
Having that, it's not a big deal to present it in UI accordingly.
$sql = "SELECT id, name, parent_id,
(SELECT COUNT(*) FROM categories WHERE parent_id = cat.id) as child_count
FROM categories cat";
$result = db_select_all($sql);
function build_menu($pid, $level = 1) {
global $result;
$filtered = array_filter($result, function($elem) use($pid) {
return $elem['parent_id'] == $pid;
});
if(empty($filtered)) return;
foreach($filtered as $item) {
extract($item);
echo "<div>" . str_repeat("---", $level) . $name;
build_menu($id, $level + 1);
echo "</div>" . PHP_EOL;
}
}
$menu_elements = array_filter($result, function($item) {
return $item['parent_id'] == '';
});
foreach($menu_elements as $menu) {
$name = $menu['name'];
$id = $menu['id'];
echo "<div>" . $name;
build_menu($id);
echo "</div>" . PHP_EOL;
}
Maybe a simple question, but after hours of reading i still didn't find an answer.
I'm trying to get the values of subrow next to those of parent row, without constantly repeating parent row:
For example:
Table 1: ParentId = 1, ParentName = ParentName
Table 2:
ChildId = 1, ParentId = 1, ChildName = ChildName1
ChildId = 2, ParentId = 1, ChildName = ChildName2
The result with JOIN currently is:
ParentId, ParentName, ChildId, ChildName1
ParentId, ParentName, ChildId, ChildName2
But i would like to display the data like:
<div>Div with parent name</div>
<div>Div with ChildName1</div>
<div>Div with ChildName2</div>
<div>Div with parent name2</div>
<div>Div with ChildName1</div>
<div>Div with ChildName2</div>
etc...
Can anyone tell me how to get to this?
Something like
echo$Parent1 foreach child as $child echo$ChildName?
You can sort by parent ID, then check if it has changed. If it has changed output the name of the parent. Something like this:
$query = "query selecting data with join... ORDER BY ParentId";
$result = $mysqli->query($query);
$lastParent = 0;
while ( $row = $result->fetch_assoc($result) )
{
if ( $row['ParentId'] != $lastParent )
{
echo '<div>' . $row['ParentName'] . '</div>';
}
$lastParent = $row['ParentId'];
echo '<div>' . $row['ChildName'] . '</div>';
}
Using that the parent name will only output when the ID changes, and as we're ordering by ID all the children will be grouped by their Parent and output together.
Issue fixed with following code
$result = mysqli_query($link, "SELECT * FROM TestParent a
LEFT JOIN TestChild b ON a.ParentId = b.ParentId
ORDER BY a.ParentId ASC, b.ChildName ASC");
$lastParent = 0;
while($row = mysqli_fetch_assoc($result))
{
if ( $row['ParentId'] != $lastParent ) {
echo '<div>' . $row['ParentName'] . '</div>';
}
$lastParent = $row['ParentId'];
echo '<div>' . $row['ChildName'] . '</div>';
}
I have a table "AA" below. Where the table structure created by this code
CREATE TABLE "AA" (
height character varying DEFAULT '-'::character varying,
class character varying NOT NULL,
gender character varying NOT NULL,
origin character varying NOT NULL
);
Then the data are
Height class gender origin
162 1 m a
169 1 f a
172 1 m b
169 2 f b
171 2 f a
Then I want to get query that contain combination of class (1, 3) and gender(m) and origin(a,b)
How can I achieve it if the result desired look like this
Height class gender origin
162 1 m a
172 1 m b
- 3 m a
- 3 m b
I've try
Select COALESCE(height), class, gender, origin
FROM AA
WHERE class in ('1','3') and gender in ('m') and origin in ('a','b')
but it return only two rows above of second table.
then I try this one
//in php
<?php
$class = array('1', '3');
$gender = array('m');
$origin = array('a', 'b');
for ($i = 0; $i < count($class); $i++) {
for ($j = 0; $j < count($gender); $j++) {
for ($k = 0; $k < count($origin); $k++) {
$query = 'select * from "AA" where ' .
" class = '" . $class[$i] .
"' and gender = '" . $gender[$j] .
"' and origin = '" . $origin[$k] . "' ";
$result = pg_query($query) or die('Query failed: ' . pg_last_error());
$row = pg_fetch_assoc($result);
if (!$row) {
$row['height'] = "-";
$row['class'] = $class[$i];
$row['gender'] = $gender[$j];
$row['origin'] = $origin[$k];
}
if (is_null($row['height'])) {
$row['height'] = '-';
}
print_r($row);
}
}
}
?>
a. Is there any effective technique to check zero-row-result and assign "-" value to specific coloumn?
b. Is there better do iteration in php or in sql and how to make the dynamic iteration if the field is more than 3 (scaling up)?
Regards.
1 Read all existing values from sql into an associative array indexed by the class, gender and association.
2 Iterate over all possible combinations and then use isset to detect & fill missing combinations.
$query = "Select height, class, gender, origin FROM AA WHERE class in ('1','3') and gender in ('m') and origin in ('a','b')";
$result = pg_query($query) or die(pg_last_error());
while($row = pg_fetch_assoc($result)) {
//read in all existing values and index by class, gender, origin combo
$key = $row['class'] . $row['gender'] . $row['origin'];
$rows[$key] = $row;
}
$classes = array('1', '3');
$genders = array('m');
$origins = array('a', 'b');
//iterate over all possible combinations
foreach($classes as $class) {
foreach($genders as $gender) {
foreach($origins as $origin) {
$key = $class . $gender . $origin;
if(!isset($rows[$key])) {
//we're missing a value for the given class/origin/gender combo, so fill it in
$rows[$key] = array(
'height' => '-',
'class' => $class,
'gender' => $gender,
'origin' => $origin,
);
}
}
}
}
print_r($rows);
The biggest part of this is getting all the lists correct. The second is getting the '-' in the right place. You can do the first part with a cross join and the second part by judicious use of cast() and left outer join:
select (case when t.height is null then '-' else cast(height as varchar(255)) end) as height,
c.theclass. g.gender, o.origin
from (select 1 as theclass union all select 2) c cross join
(select 'm' as gender) g cross join
(select 'a' as origin union all select 'b') o left outer join
table t
on t.class = c.theclass and t.gender = g.gender and t.origin = o.origin;
EDIT:
If class is a character, try:
select (case when t.height is null then '-' else cast(height as varchar(255)) end) as height,
c.class. g.gender, o.origin
from (select '1' as class union all select '2') c cross join
(select 'm' as gender) g cross join
(select 'a' as origin union all select 'b') o left outer join
table t
on t.class = c.class and t.gender = g.gender and t.origin = o.origin;
I'm trying to have an advanced sidebar that filters the results when someone uses the search bar. I have the regular search working and also made it display the count of items in a category on another sidebar (i.e. price range, brand, condition) but now i want the user to click a category and narrow down the results like what you see on newegg.com.
This is my search query:
function build_query($product_search) {
$search_query = "SELECT * FROM tbl_product p INNER JOIN tbl_brand b ON p.br_id = b.br_id";
$clean_search = str_replace(',', ' ', $product_search);
$search_word = explode(' ', $clean_search);
$final_search_words = array();
if (count($search_word) > 0) {
foreach ($search_word as $word) {
if (!empty($word)) {
$final_search_words[] = $word;
}
}
}
$where_list = array();
if (count($final_search_words) > 0) {
foreach($final_search_words as $word) {
$where_list[] = "p.pd_name LIKE '%$word%'";
}
}
$where_clause = implode(' OR ', $where_list);
if(!empty($where_clause)) {
$search_query .= " WHERE $where_clause";
}
Same thing for my sidebar that counts the categories:
function narrow_query($product_search) {
$search_query = "SELECT p.pd_name, p.pd_con, b.br_name AS name, c.cat_name AS cat,
COUNT(p.pd_con) AS con, COUNT(b.br_name) AS brand, COUNT(c.cat_name) AS cat_c,
COUNT(CASE WHEN p.pd_price >= '00.01' AND p.pd_price <= '99.99' THEN 1 END) AS cnt1,
COUNT(CASE WHEN p.pd_price >= '100.00' AND p.pd_price <= '199.99' THEN 1 END) AS cnt2,
COUNT(CASE WHEN p.pd_price >= '200.00' AND p.pd_price <= '299.99' THEN 1 END) AS cnt3,
And so on and so on...
FROM tbl_product p JOIN tbl_brand b ON b.br_id = p.br_id
JOIN tbl_category c ON c.cat_id = p.cat_id";
$clean_search = str_repl...
(Same word filter code as above)
Now i had something going that kinda worked by grabbing the $_GET:
"<a href=\"" . $_SERVER['PHP_SELF'] . "?productsearch=" . $product_search . "&narrow=" . $row['cat'] . "$link=1\">"
and using case in my functions:
function any_query($product_search, $link, $narrow) {
previous info from above...
if(!empty($link)) {
switch($link)
case 1:
$search_query .= " AND b.br_name = '$narrow'";
break;
default:
}
}
but it's not doing the job and I'm just lost at this point.
Is there a better way or a different method to achieve this? It doesn't have to use case or $_GET.
Thank you for your time.
The problem is you need to add a parentheses around your OR statements. I would do this:
$search_query .= ' WHERE 1';
if (!empty($where_clause)) {
$search_query .= " AND ($where_clause)";
}
if (!empty($narrow)) {
$search_query .= " AND $narrow";
}