SQL bind two tables into one SQL statement in a specific way - php

I have two SQL tables with columns:
menu:
id [AI]
name
description
subcategories:
id [AI]
name
pid (subcategory parent id)
mid (menu id)
In the new version of my website there is no menu anymore and "subcategories" are now changed into "categories":
id
name
description
parent_id
The old database is still in use, so I'm making my migration script and here is the part with categories:
$new_db->query("TRUNCATE TABLE `categories`");
$ids = [];
$menu_list = $old_db->fetch("SELECT * FROM `menu` ORDER BY `id`");
foreach($menu_list as $menu)
{
$id = $new_db->insert("categories", [
"name" => $menu["name"],
"description" => $menu["description"],
"parent_id" => "0"
]);
$ids[$menu["id"]] = $id;
}
$subcategories = $old_db->fetch("SELECT * FROM `subcategories` ORDER BY `id`");
foreach($subcategories as $subcategory)
{
$pid = 0;
$desc = "";
if($subcategory["mid"] > 0)
{
$menu = $old_db->fetch_first("SELECT `id`, `description` FROM `menu` WHERE `id` = '".$subcategory["mid"]."' LIMIT 1");
$pid = $ids[$menu["id"]];
$desc = $menu["description"];
}
else
{
$pid = $subcategory["pid"];
}
$new_db->insert("categories", [
"name" => $subcategory["name"],
"description" => $desc,
"parent_id" => $pid
]);
}
It works but I'm pretty sure it could be done better with lesser cost.
Can I make a SQL statement which will bind menu and subcategories into one result list and then insert them all?

I think you just want a JOIN?
SELECT *
FROM `categories` c
JOIN `subcategiries` s
ON s.pid = c.id

Related

How to create a menu tree using php?

I am developing one e-commerce platform where admin can manage menu, category and sub-category. Now, I want display it in mega menu.
Menu1
Category1.1
SubCategory1
SubCategory2
Category1.2
SubCategory
Menu2
Category2.1
SubCategory1
This is a possible structure with I came up.
I have 2 db tables - Menu, Category. In category, there is a column called sub_category where I am storing sub category values with , separator.
Now, after running below query -
$data = DB::select('SELECT m.id AS mId, m.name AS menu,
m.meta_keywords AS menu_keywords, m.meta_description AS menu_description,
c.id AS cId, c.name AS category, c.sub_category, c.meta_keywords AS
category_keywords, c.meta_description AS category_description
FROM `menu` AS m LEFT JOIN `category` AS c ON c.menuId = m.id
WHERE m.is_publish = 1 AND c.is_publish = 1');
I am getting result.
Now, I want to build a tree where I can get a result something like this -
"id" => 1,
"name" => "Fashion"
"category" => [
1 => [
"cId" => 1,
"category" => "Men"
"sub_category" => "Shirt, T-shirt"
],
2 => [
"cId" => 2,
"category" => "Women"
"sub_category" => ""
],
]
Please help me out to find out solution. Thank you in advance.
As per your db structure and requirement, you can use below function -
public function menuTree($data){
$menuId = $menu = $sub = $op = array();
foreach($data as $d){
if(in_array($d->mId, $menuId)){
$sub['cId'] = $d->cId;
$sub['category'] = $d->category;
$sub['sub_category'] = $d->sub_category;
$sub['category_keywords'] = $d->category_keywords;
$sub['category_description'] = $d->category_description;
$menu[$d->mId]['category'][] = $sub;
} else {
$menuId[] = $d->mId;
$op['id'] = $d->mId;
$op['menu'] = $d->menu;
$op['menu_keywords'] = $d->menu_keywords;
$op['menu_description'] = $d->menu_description;
$menu[$d->mId] = $op;
if($d->cId != NULL){
$sub['cId'] = $d->cId;
$sub['category'] = $d->category;
$sub['sub_category'] = $d->sub_category;
$sub['category_keywords'] = $d->category_keywords;
$sub['category_description'] = $d->category_description;
$menu[$d->mId]['category'][] = $sub;
}
}
}
return $menu;
}
Still I believe there will be a better solution. Hope this works for you.

PDO - get from one to many structure

I have this db structure:
create table article(
id int AUTO_INCREMENT PRIMARY KEY,
title varchar(50),
text text
)
create table comments(
id int AUTO_INCREMENT PRIMARY KEY,
article int not null
username varchar(30) not null,
text text not null,
foreign key(article) references article(id) on delete cascade
)
I would like to get articles with comments and convert to json with this structure:
[
{
id: 1,
title: "article1",
text: "text1",
"comments": [
{
id: 1,
username: "user1",
text: "text"
}
]
}
]
This is my code:
$query = $pdo->query('select * from article as a join comments as c on c.article =a.id');
$query->execute();
var_dump(json_encode($query->fetchAll(PDO::FETCH_ASSOC)));
and result:
[{"id":"1","title":"artile1","text":"comment1","article":"1","username":"user1"}]
It is any way how to get article and comments as inner array? I could do it manually but, I will have a lot of tables with many columns.
Thanks for advices
It looks like it is not possible using PDO fetch modes. They are powerful, but unfortunately, I was not able to get the output you wanted.
You can achieve this outcome using a simple loop. The downside is that you have to create the array manually.
$stmt = $pdo->prepare('SELECT a.id AS aid, a.title, a.text AS atext, c.id AS cid, c.username, c.text AS ctext
FROM article AS a
JOIN comments AS c ON c.article =a.id ');
$stmt->execute();
$stmt->setFetchMode(PDO::FETCH_ASSOC);
$id = null;
$data = [];
foreach ($stmt as $row) {
$comment = [
'id' => $row['cid'],
'username' => $row['username'],
'text' => $row['ctext'],
];
if ($id == $row['aid']) {
// If parent ID still the same append only comment
$data[array_key_last($data)]['comments'][] = $comment;
} else {
// set new id and append a whole new row
$id = $row['aid'];
$data[] = [
'id' => $row['aid'],
'title' => $row['title'],
'text' => $row['atext'],
'comments' => [$comment]
];
}
}
PDO has plenty of fetch modes and you can mix them together, but it looks like none of them can cope with joins the way you would like them too. They are all described here in https://phpdelusions.net/pdo/fetch_modes

MySql: Refer parent query fetched array in subquery

I am trying to refer the parent query fetched array in sub query of same statement. I have a news table and I want to get a specific news by its title and 10 more news which have id lower than that specific news. I want in one statement of Sql and i am php to fetch array.
<?php
// $_GET['q'] is title
include('db.php');
$result = array();
$sel = "SELECT * FROM news WHERE title = '".$_GET['q']."' "; // AND 10 MORE NEWS WHICH HAVE ID LOWER THAN THIS $_GET['q'] ID .
$qry = #mysqli_query($conn , $sel);
$num = mysqli_num_rows($qry);
while($row = #mysqli_fetch_array($qry)) {
array_push($result, array('id' => $row['id'] , 'title' => $row['title'] , 'desc' => $row['about'] , 'image' => $row['image'] , 'time' => $time , 'htitle' => $row['Htitle'] , 'habout' => $row['Habout']));
}
echo json_encode(array('result' => $result));
?>
Your original query is
SELECT * FROM news WHERE title = :title.
If you really want to use a subquery use something along the lines of
SELECT
*
FROM news
WHERE id <
(SELECT
id
FROM news
WHERE title = :title
LIMIT 1)
ORDER BY id DESC
LIMIT 10
A final note: PLEASE use parameters in your query, because you are WIDE OPEN to SQL injection (think about when $_GET['q'] has a value of ; DROP TABLE news;--).

left join only fetches one row

This SQL only fetches one row from the table category when I'm using fetchAll(), I know there is atleast two rows with the same info. Any ideas?
Click here to see a more detailed explanation of what I thought the problem was
$query = " SELECT
category.*,
GROUP_CONCAT('category_hierarchy.category_id' SEPARATOR ',') AS subcategories
FROM category
LEFT JOIN category_hierarchy ON category.category_id = category_hierarchy.category_parent_id
WHERE category.type = '1'
ORDER BY category.sort_order ASC";
// Prepare.
$stmt = $dbh->prepare($query);
// Execute.
$stmt->execute();
// Fetch results.
$categories = $stmt->fetchAll();
$countedRows = count($categories);
foreach($categories as $category) {
$parent_arr = '';
if(!empty($category['subcategories'])) {
$parent_arr = array(display_children($category['subcategories']));
}
$arr[] = array(
'category_id' => $category['category_id'],
'title' => $category['title'],
'slug' => $category['slug'],
'url' => $category['url'],
'type' => $category['type'],
'sort_order' => $category['sort_order'],
'categories' => $parent_arr
);
}
You have a aggregate function group_concat and without group by clause it will always return one row, you may need to add a group by at the end.
SELECT
category.*,
GROUP_CONCAT('category_hierarchy.category_id' SEPARATOR ',') AS subcategories
FROM category
LEFT JOIN category_hierarchy ON category.category_id = category_hierarchy.category_parent_id
WHERE category.type = '1'
group by category.category_id
ORDER BY category.sort_order ASC

Most efficient way to query the newest 20 posts per category on the sme page

I am using PHP and mySQL. I have a table of photographs. in the photographs table I have: a link to the photo, a category_id, date.
What would be the best way to list all the categories on the page with the newest 20 photos under each?
Right now I am selecting all the photos and then sorting them out after in PHP. If there gets to be 500 photos in one category this would seem very inefficient. Any better ideas for the SQL end of it?
The only other way I thought of was to loop a 20 limit query for each category, but if there are 100 categories that seems even worse!
pseudo output
[category_list] => {
[0]=> {
'category_title' => 'photos at sunset',
'posts' => {
[0] => {
'photo_link' = '1.jpg',
}
[1] => {
'photo_link' = '2.jpg',
}
}
}
[1]=> {
'category_title' => 'photos at sunrise',
'posts' => {
[0] => {
'photo_link' = '1.jpg',
}
}
}
}
pseudo code
$query =
"
SELECT
photographs.category_id, photographs.photo_link, categories.title
FROM
photographs
INNER JOIN
categories
ON
category.id = photographs.categories.id
ORDER BY
category.id DESC
";
$result = $this->pdo->prepare($query);
$result->execute();
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
$post[] = $row;
}
$result = null;
$count = sizeof($post);
//get a list of the categories
for($i=0; $i < $count; $i++) {
$categories[$i] = $post[$i]['title'];
}
$categories = array_unique($categories);
//sort categories alphabetically
sort($categories);
//add the newest 20 photos to each category
$categories_count = count($categories);
$posts_count = count($post);
for($i=0; $i < $categories_count; $i++) {
$category_list[$i]['post_count'] = 0;
for($k=0; $k < $posts_count; $k++) {
if ($categories[$i] == $post[$k]['category_title']) {
if ($category_list[$i]['count'] == 19) {
break;
}
$category_list[$i]['category_title'] = $post[$k]['category_title'];
$category_list[$i]['post'][] = $post[$k];
$category_list[$i]['post_count']++;
}
}
}
It can be done in a single query.
Assuming this is the table schema:
CREATE TABLE `parkwhiz_demo`.`test` (
`photo_id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`category_id` INT UNSIGNED NOT NULL ,
`date` DATETIME NOT NULL
) ENGINE = MYISAM ;
You can get an ordered list of the 20 most recent photos per category with this query:
select photo_id, category_id, date
from test
where (
select count(*) from test as t
where t.category_id = test.category_id and t.date >= test.date
) <= 20
order by category_id, date desc;
The PHP loop to create something similar to your desired array structure is:
$output = Array();
$prevRow = false;
$i=-1;
while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
if (!$prevRow || $row['category_id'] != $prevRow['category_id']) {
$i++;
$output[$i]['category_id'] = $row['category_id'];
$output[$i]['posts'] = Array();
}
array_push($output[$i]['posts'], Array('image_id'=>$row['image_id']));
}
Just a suggestion, but how 'bout running one query for the list of categories and use the results to create a query for the items using a combination of LIMIT and UNION? That way you're only sending two queries; but, it's possible that that's not much more efficient than the second option you described depending on how much overhead each database call requires and how much optimization mySql will do when it sees the UNION (e.g. parallel processing the statement).
I don't know enough about it to recommend it, but it's something i would try.

Categories