Category and Sub-Categories with NodeJS and SQL Result - sql

I am trying to build a JSON with NodeJS and SQL result.
The database is:
CREATE TABLE category (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
title varchar(255) NOT NULL,
parent_id int(10) unsigned DEFAULT NULL,
PRIMARY KEY (id),
FOREIGN KEY (parent_id) REFERENCES category (id)
ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE `items` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`cat_id` int unsigned DEFAULT NULL,
`parent_id` int unsigned DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `cat_id` (`cat_id`),
KEY `sub_id` (`parent_id`),
CONSTRAINT `cat_id` FOREIGN KEY (`cat_id`) REFERENCES `category` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `sub_id` FOREIGN KEY (`parent_id`) REFERENCES `category` (`parent_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
BEGIN;
INSERT INTO `category` VALUES (1, 'Colazione', NULL);
INSERT INTO `category` VALUES (2, 'Pranzo', NULL);
INSERT INTO `category` VALUES (3, 'Primi piatti', 2);
INSERT INTO `category` VALUES (4, 'Second dish', 2);
INSERT INTO `category` VALUES (5, 'Other things for lunch', 2);
COMMIT;
-- ----------------------------
-- Records of items
-- ----------------------------
BEGIN;
INSERT INTO `items` VALUES (1, 1, NULL, 'Cornetto');
INSERT INTO `items` VALUES (2, 3, 2, 'Pasta al sugo 1');
INSERT INTO `items` VALUES (3, 3, 2, 'Pasta al sugo 2');
INSERT INTO `items` VALUES (4, 3, 2, 'Pasta al sugo 3');
INSERT INTO `items` VALUES (5, 3, 2, 'Pasta al sugo 1 X');
INSERT INTO `items` VALUES (6, 3, 2, 'Pasta al sugo 2 X');
INSERT INTO `items` VALUES (7, 4, 2, 'Pasta al sugo 3 X');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
The expected JSON is:
Category:
Sub-Categories:
Items:
If category doesn't got any sub-categories it will print:
Category:
Items:
We got more than one categories and each categories can have more than one sub-categories.
Each sub-categories can have more than one item.
How can I build a JSON result with NodeJS with SQL Query?
Expected JSON:
{
"menu": {
"categories": [
{
"id_category": 1,
"category_title": "Colazione",
"items": [
{
"id_item": 1,
"title": "Cornetto"
}
]
},
{
"id_category": 2,
"category_title": "Pranzo",
"subcategories": [
{
"title_subcategories": "Primi piatti",
"items": [
{
"id_item": 1,
"title": "Pasta al sugo 1"
},
{
"id_item": 2,
"title": "Pasta al sugo 2"
}
]
},
{
"title_subcategories": "Secondi piatti",
"items": [
{
"id_item": 1,
"title": "Pasta al sugo 3"
}
]
}
]
}
]
}
}

Here's what I came up with, although I haven't tested it:
async function buildJson(categories, items) {
// `query` is some function that queries the DB
const categries = await query('SELECT id AS id_category, title AS category_title, parent_id FROM categories');
const items = await query('SELECT id AS id_item, title AS item_title, cat_id FROM items');
const data = {
menu: {
categories: [],
},
};
const subcategories = categories.filter(category => category.parent_id !== null);
categories.filter(category => category.id_parent === null)
.forEach(category => {
data.menu.categories.push({
id_category: category.id_category,
category_title: category.category_title,
});
data.menu.categories.forEach(_category => {
_category.items = items.filter(item => item.cat_id === _category.id_category)
.map(item => ({
id_item: item.id_item,
title: item.title,
}));
_category.subcategories = categories.filter(__category => __category.parent_id === _category.id);
_category.subcategories.forEach(subcategory => {
subcategory.items = items.filter(item => item.cat_id === subcategory.id_category)
.map(item => ({
id_item: item.id_item,
title: item.title,
}));
});
});
});
return data;
}

Related

Creating JSON from Database Table

I am saving some settings in a table because it changes frequently and is different for each user. Following is my table structure.
DECLARE #tem AS TABLE
(
id INT,
Person VARCHAR(100),
JLabel NVARCHAR(100),
JValue NVARCHAR(200),
Parent INT
);
INSERT #tem
(
id,
Person,
JLabel,
JValue,
Parent
)
VALUES
(1,'arun', 'Area', '250', NULL),
(2,'arun', 'brder', NULL, NULL),
(3,'arun', 'width', '5', 2)
(4,'arun', 'marker', NULL, NULL),
(5,'arun', 'dataLabel', NULL, 4),
(6,'arun', 'visible', '1', 5),
(7,'arun', 'position', 'Top', 5),
(8,'arun', 'font', NULL, 5),
(9,'arun', 'fontWeight', '600', 8),
(10,'arun', 'color', '#ffffff', 8);
--EXPECTED OUTPUT
{"Area":"250","brder":{"width":"5"},"marker": { "dataLabel": { "visible": "1", "position": "Top", "font": { "fontWeight": "600", "color": "#ffffff" } } }}
Here width is the child element of border because it's Parent is 2 (id of border).
How do I get this output?

update column value with a json

If I try this with a numbers col:
UPDATE TEST_TABLE SET META_ROW_NUM = 2
it works. However, when I try to update another field with a JSON (also tried without the quotation marks ""):
UPDATE TEST_TABLE
SET JSON_DATA = "{
"Business_Type": "载货",
"Collected_Article_Quantity": null,
"Consignee_Company_ContactPerson": null,
"Consignee_Company_Email": null,
}"
I get syntax errors like this:
SQL Error [1003] [42000]: SQL compilation error:
syntax error line 3 at position 1 unexpected 'Business_Type'.
The TYPEOF(JSON_DATA) is Object. When I hover over the col in Dbeaver, I see that the type is Variant:
The table looks like this:
Using ' and PARSE_JSON/TRY_PARSE_JSON:
UPDATE TEST_TABLE
SET JSON_DATA = PARSE_JSON('{
"Business_Type": "载货",
"Collected_Article_Quantity": null,
"Consignee_Company_ContactPerson": null,
"Consignee_Company_Email": null
}');
Full demo:
CREATE OR REPLACE TABLE t(JSON_DATA VARIANT);
INSERT INTO t VALUES(NULL);
UPDATE t
SET JSON_DATA = PARSE_JSON('{
"Business_Type": "载货",
"Collected_Article_Quantity": null,
"Consignee_Company_ContactPerson": null,
"Consignee_Company_Email": null
}');
SELECT * FROM t;
Output:
It seems like you might want to use OBJECT_CONSTRUCT to build you values, it has a nice side effect, that properties with a NULL value are not inserted
thus:
SELECT column1 as id,
OBJECT_construct(column2, column3, column4, column5, column6, column7, column8, column9) as json
FROM VALUES
(1, 'Type', 'xxi', 'Quantity', null, 'ContactPerson', null, 'Email', null),
(2, 'Type', 'xxi', 'Quantity', 'many', 'ContactPerson', null, 'Email', null),
(3, 'Type', 'xxi', 'Quantity', 'many', 'ContactPerson', 'Simeon', 'Email', null),
(4, 'Type', 'xxi', 'Quantity', 'many', 'ContactPerson', 'Simeon', 'Email', 'example#example.com')
;
gives:
ID
JSON
1
{ "Type": "xxi" }
2
{ "Quantity": "many", "Type": "xxi" }
3
{ "ContactPerson": "Simeon", "Quantity": "many", "Type": "xxi" }
4
{ "ContactPerson": "Simeon", "Email": "example#example.com", "Quantity": "many", "Type": "xxi" }
which makes "more" sense to set the JSON to values from the related data that is used to decide what to update.
but if you data you are build via is spare OBJECT_AGG works super nice:
,OBJECT_AGG(column2, column3) as json
FROM VALUES
(1, 'Type', 'xxi'),
(1, 'Quantity', null),
(1, 'ContactPerson', null),
(1, 'Email', null),
(2, 'Type', 'xxi'),
(2, 'Quantity', 'many'),
(2, 'ContactPerson', null),
(2, 'Email', null),
(3, 'Type', 'xxi', l),
(3, 'Quantity', 'many'),
(3, 'ContactPerson', 'Simeon'),
(3, 'Email', null),
(4, 'Type', 'xxi'),
(4, 'Quantity', 'many'),
(4, 'ContactPerson', 'Simeon'),
(4, 'Email', 'example#example.com')
GROUP BY 1
;
gives:
ID
JSON
1
{ "Type": "xxi" }
2
{ "Quantity": "many", "Type": "xxi" }
3
{ "ContactPerson": "Simeon", "Quantity": "many", "Type": "xxi" }
4
{ "ContactPerson": "Simeon", "Email": "example#example.com", "Quantity": "many", "Type": "xxi" }

JSON Hierarchy Problems

I don't have a recursive relationship here, but a problem of several child tables having the same parent, and having multiple records in each child table. Here's an example:
create table #Schedules (
ScheduleId int,
ScheduleName varchar(20)
);
create table #ScheduleThings (
ScheduleThingId int,
ScheduleId int,
Thing decimal(18,2));
create table #ScheduleOtherThings (
ScheduleOtherThingId int,
ScheduleId int,
OtherThing varchar(50));
I insert some typical sample data:
insert into #Schedules (
ScheduleId,
ScheduleName )
values
(1, 'A'),
(2, 'B');
insert into #ScheduleThings (
ScheduleThingId,
ScheduleId,
Thing )
values
(1, 1, 10.22),
(2, 1, 11.02),
(3, 1, 11.89),
(4, 2, 19.23),
(5, 2, 20.04),
(6, 2, 20.76),
(7, 2, 21.37);
insert into #ScheduleOtherThings (
ScheduleOtherThingId,
ScheduleId,
OtherThing )
values
(1, 1, 'Always'),
(2, 1, 'Sometimes'),
(3, 2, 'Seldom'),
(4, 2, 'Always'),
(5, 2, 'Never');
declare #results table (result xml);
I've then tried 2 similar approaches (3 or 4 actually), but here is one:
insert into #Results (
result )
select fr.result from (
select
s.ScheduleId as [schedules.schedule_id],
s.ScheduleName as [schedules.schedule_name],
st.ScheduleThingId as [schedules.schedule_things.schedule_thing_id],
st.Thing as [schedules.schedule_things.thing],
sot.ScheduleOtherThingId as [schedules.schedule_other_things.schedule_other_thing_id],
sot.OtherThing as [schedules.schedule_other_things.other_thing]
from #Schedules s
join #ScheduleThings st
on st.ScheduleId = s.ScheduleId
join #ScheduleOtherThings sot
on sot.ScheduleId = s.ScheduleId
where s.ScheduleId = 1
and st.ScheduleThingId < 3
for json path, root('schedules') ) fr(result) ;
select * from #Results;
This attempt gives me:
{
"schedules": [
{
"schedules": {
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 1,
"other_thing": "Always"
}
}
},
{
"schedules": {
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
}
},
and removing 'schedules' from the dot notation entirely has no significant impact:
{
"schedules": [
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 1,
"other_thing": "Always"
}
},
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": {
"schedule_thing_id": 1,
"thing": 10.22
},
"schedule_other_things": {
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
},
What I need (and what I think is the proper JSON structure) is like:
{
"schedules": [
{
"schedule_id": 1,
"schedule_name": "A",
"schedule_things": [
{
"schedule_thing_id": 1,
"thing": 10.22
},
{
"schedule_thing_id": 2,
"thing": 11.02
},
]
"schedule_other_things": [
{
"schedule_other_thing_id": 1,
"other_thing": "Always"
},
{
"schedule_other_thing_id": 2,
"other_thing": "Sometimes"
}
]
}
]
}
In other words, the attributes of the parent 'Schedule' record appear one time, an object of ScheduleThings follows, including all child ScheduleThings, followed by an object of ScheduleOtherThings, etc.
I don't understand yet why my dot specifications don't make it clear which attributes belong to the root object, and therefore that I don't need those attributes repeated. But, I especially don't understand why the entire dataset is flattened--even when I think I've used the dot notation to make the parent-child relationships very explicit.
You could try nesting the calls to for json
Such as...
select
fr.result
from
(
select
s.ScheduleId as [schedules.schedule_id],
s.ScheduleName as [schedules.schedule_name],
(
SELECT ScheduleThingId, Thing
FROM #ScheduleThings
WHERE ScheduleId = s.ScheduleId
AND ScheduleThingId < 3
FOR JSON PATH
)
AS [schedules.schedule_things],
(
SELECT ScheduleOtherThingId, OtherThing
FROM #ScheduleOtherThings
WHERE ScheduleId = s.ScheduleId
FOR JSON PATH
)
AS [schedules.schedule_other_things]
from
#Schedules s
where
s.ScheduleId = 1
for json path, root('schedules')
)
fr(result) ;
Demo : https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=e9a9c55b2daaac4e0f48d52a87bfede9

How to use Yii2 multilingual behavior

I’m having a problem using Yii2 multilingual behavior and I hope someone here can help me.
This is the my database
CREATE TABLE IF NOT EXISTS `post` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`enabled` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE IF NOT EXISTS `postLang` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) NOT NULL,
`language` varchar(6) NOT NULL,
`title` varchar(255) NOT NULL,
`content` TEXT NOT NULL,
PRIMARY KEY (`id`),
KEY `post_id` (`post_id`),
KEY `language` (`language`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
ALTER TABLE `postLang`
ADD CONSTRAINT `postlang_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `post` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
And this PostlangController
use omgdef\multilingual\MultilingualBehavior;
public function behaviors()
{
return [
'ml' => [
'class' => MultilingualBehavior::className(),
'languages' => [
'vi-VN' => 'Russian',
'en-US' => 'English',
],
//'languageField' => 'language',
//'localizedPrefix' => '',
//'requireTranslations' => false',
//'dynamicLangClass' => true',
'langClassName' => PostLang::className(), // or namespace/for/a/class/PostLang
'defaultLanguage' => 'vi-VN',
'langForeignKey' => 'post_id',
'tableName' => "{{%postLang}}",
'attributes' => [
'title', 'content',
]
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'delete' => ['post'],
],
],
];
}
and output orror
Call to undefined method backend\controllers\PostlangController::primaryKey()
don't add the behavior to the controller add it to your model
Attaching this behavior to the model (Post in the example).
https://github.com/OmgDef/yii2-multilingual-behavior

Laravel Eloquent ORM for Social Network

I have the following database setup for my status posts. For each post, users can like the post, comment on the post or can even be tagged in the original post by the author.
I'm trying to setup my Resourceful controller 'Post' to bring back all the data via JSON object but I can't properly find the comment, likes or tags usernames. I'm using Sentry 2 for auth if that makes a difference.
Here's the database setup:
CREATE TABLE Users (
id INT NOT NULL AUTO_INCREMENT,
first_name VARCHAR(30),
last_name VARCHAR(30),
many more...
);
CREATE TABLE Posts (
postID INT NOT NULL AUTO_INCREMENT,
caption VARCHAR(200),
description VARCHAR(200),
fromID INT(10) UNSIGNED NOT NULL,
toID INT(10) UNSIGNED NOT NULL,
icon VARCHAR(200),
link VARCHAR(200),
message TEXT,
storyType INT,
type ENUM ('LINK', 'PHOTO', 'STATUSUPDATE', 'VIDEO' ),
createdTime DATE,
PRIMARY KEY (postID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (toID) REFERENCES users (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Likes (
likeID INT NOT NULL AUTO_INCREMENT,
fromID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
createdDate DATE,
PRIMARY KEY (likeID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Comments (
commentID INT NOT NULL AUTO_INCREMENT,
fromID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
comment TEXT,
createdDate DATE,
PRIMARY KEY (commentID),
FOREIGN KEY (fromID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE Tags (
tagID INT NOT NULL AUTO_INCREMENT,
userID INT(10) UNSIGNED NOT NULL,
postID INT NOT NULL,
PRIMARY KEY (tagID),
FOREIGN KEY (userID) REFERENCES users (id),
FOREIGN KEY (postID) REFERENCES Posts (postID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
My Post Controller, I just have a simple page that spits out everything. I don't want to loop through anything in my view file, I just want to bring back the json full object.
class PostController extends BaseController {
public function show($id)
{
$post = Post::with(array('comments', 'from', 'tags', 'likes'))->find($id);
return View::make('samplepage')->with('data', $post);
}
}
My Post Model:
class Post extends Eloquent {
protected $table = 'Posts';
protected $primaryKey = 'postID';
public function comments()
{
return $this->hasMany('Comment','postID');
}
public function tags()
{
return $this->hasMany('Tag','postID');
}
public function likes()
{
return $this->hasMany('Like','postID');
}
public function from()
{
return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
public function users()
{
return $this->belongsTo('User', 'fromID');
}
}
Comment Model:
class Comment extends Eloquent {
protected $table = 'Comments';
protected $primaryKey = 'commentID';
public function post() {
return $this->belongsTo('Post','fromID');
}
public function user() {
return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
}
Tag Model:
class Tag extends Eloquent {
protected $table = 'Tags';
protected $primaryKey = 'tagID';
}
I even setup the following in my user model but it makes no difference.
User Model:
public function posts() {
return $this->hasMany('Post','id');
}
public function comments() {
return $this->hasMany('Comment','id');
}
Everything works great with this setup and when I hit posts/2 with this the following code, I get the below object back.
$post = Post::with(array('comments', 'from', 'tags', 'likes'))->find($id);
return View::make('samplepage')->with('data', $post);
{
postID: "2",
toID: "8",
comments: [
{
commentID: "2",
comment: "second comment",
fromID: "1",
postID: "2",
createdDate: "2014-02-15"
}
],
from: {
id: "4",
first_name: Paul,
last_name: Davis
},
tags: [
{
tagID: "1",
userID: "2",
postID: "2"
},
{
tagID: "2",
userID: "3",
postID: "2"
}
],
likes: [
{
likeID: "1",
fromID: "2",
postID: "2",
createdDate: "2013-01-04"
},
{
likeID: "2",
fromID: "3",
postID: "2",
createdDate: "2013-02-05"
}
]
}
But what I want is the following, where for each tag, like and comment to concatenate the first and last name and get them back with the object.
{
postID: "2",
toID: "4",
comments: [
{
commentID: "2",
comment: "second comment",
fromID: "1",
from: {
"name": "Jason Terry",
"id": "721286625"
},
postID: "2",
createdDate: "2014-02-15"
}
],
from: {
id: "4",
first_name: Paul,
last_name: Davis
},
tags: [
{
tagID: "1",
userID: "2",
from: {
"name": "David Lee",
"id": "721286625"
},
postID: "2"
},
{
tagID: "2",
userID: "3",
from: {
"name": "Paul Pierce",
"id": "721286625"
},
postID: "2"
}
],
likes: [
{
likeID: "1",
fromID: "2",
from: {
"name": "David Lee",
"id": "721286625"
},
postID: "2",
createdDate: "2013-01-04"
},
{
likeID: "2",
fromID: "3",
from: {
"name": "Al Davis",
"id": "721286625"
},
postID: "2",
createdDate: "2013-02-05"
}
]
}
I have searched Stackoverflow, countless Laravel blogs, the official documentation for 2 weeks now and I can't seem to solve this. Any help is wonderfully appreciate.
Update:
With Tony's answer below I added
$post = Post::with(array('comments.users', 'from', 'tags.users', 'likes.users'))->find($id);
Then I added
public function users()
{ return $this->belongsTo('User', 'fromID')->select(array('id', 'first_name', 'last_name'));
}
to the comments, tags, and likes model. And the object works great now.
But my debugger shows the following
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('1')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('4')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('2', '3')
select `id`, `first_name`, `last_name` from `users` where `users`.`id` in ('2', '3')
In short, it runs 4 queries on my users table. Isn't this redundant? Shouldn't it be doing 1 query to the users table instead of 1 query for the original post user, 1 query for the comments users, 1 query for the tags users, and 1 query for the likes users?
It looks like you want to use nested relationships.
$post = Post::with('comments.from', 'from', 'tags.from', 'likes.from')->find($id);
You would also need the "from" relationship coded into each of those models.
To get your concatenated name; you'd need the following in your User model
protected $appends = array('name');
protected $hidden = array('first_name', 'last_name'); //this is optional
public function getNameAttribute()
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
it is little old post but you can also do this,
public function from()
{
return $this->belongsTo('User', 'fromID')
->select(array('id',DB::raw("CONCAT(firstname,' ', lastname) as name")));
}