SQL query with join to get nested array of objects - mysql

Summary: I'll start with a JSON schema to describe the expectation. Notice the roles with a nested array of objects and I'm looking for a "Smart query" that can fetch it one single query.
{
"id": 1,
"first": "John",
"roles": [ // Expectation -> array of objects
{
"id": 1,
"name": "admin"
},
{
"id": 2,
"name": "accounts"
}
]
}
user
+----+-------+
| id | first |
+----+-------+
| 1 | John |
| 2 | Jane |
+----+-------+
role
+----+----------+
| id | name |
+----+----------+
| 1 | admin |
| 2 | accounts |
| 3 | sales |
+----+----------+
user_role
+---------+---------+
| user_id | role_id |
+---------+---------+
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
+---------+---------+
Attempt 01
In a naive approach I'd run two sql queries in my nodejs code, with the help of multipleStatements:true in connection string. Info.
User.getUser = function(id) {
const sql = "SELECT id, first FROM user WHERE id = ?; \
SELECT role_id AS id, role.name from user_role \
INNER JOIN role ON user_role.role_id = role.id WHERE user_id = ?";
db.query(sql, [id, id], function(error, result){
const data = result[0][0]; // first query result
data.roles = result[1]; // second query result, join in code.
console.log(data);
});
};
Problem: Above code produces the expected JSON schema but it takes two queries, I was able to narrow it down in a smallest possible unit of code because of multiple statements but I don't have such luxury in other languages like Java or maybe C# for instance, there I've to create two functions and two sql queries. so I'm looking for a single query solution.
Attempt 02
In an earlier attempt With the help of SO community, I was able to get close to the following using single query but it can only help to produce the array of string (not array of objects).
User.getUser = function(id) {
const sql = "SELECT user.id, user.first, GROUP_CONCAT(role.name) AS roles FROM user \
INNER JOIN user_role ON user.id = user_role.user_id \
INNER JOIN role ON user_role.role_id = role.id \
WHERE user.id = ? \
GROUP BY user.id";
db.query(sql, id, function (error, result) {
const data = {
id: result[0].id, first: result[0].first,
roles: result[0].roles.split(",") // manual split to create array
};
console.log(data);
});
};
Attempt 02 Result
{
"id": 1,
"first": "John",
"roles": [ // array of string
"admin",
"accounts"
]
}
it's such a common requirement to produce array of objects so wondering there must be something in SQL that I'm not aware of. Is there a way to better achieve this with the help of an optimum query.
Or let me know that there's no such solution, this is it and this is how it's done in production code out there with two queries.
Attempt 03
use role.id instead of role.name in GROUP_CONCAT(role.id), that way you can get hold of some id's and then use another subquery to get associated role names, just thinking...
SQL (doesn't work but just to throw something out there for some thought)
SELECT
user.id, user.first,
GROUP_CONCAT(role.id) AS role_ids,
(SELECT id, name FROM role WHERE id IN role_ids) AS roles
FROM user
INNER JOIN user_role ON user.id = user_role.user_id
INNER JOIN role ON user_role.role_id = role.id
WHERE user.id = 1
GROUP BY user.id;
Edit
Based on Amit's answer, I've learned that there's such solution in SQL Server using JSON AUTO. Yes this is something I'm looking for in MySQL.
To articulate precisely.
When you join tables, columns in the first table are generated as
properties of the root object. Columns in the second table are
generated as properties of a nested object.

User this Join Query
FOR JSON AUTO will return JSON for your query result
SELECT U.UserID, U.Name, Roles.RoleID, Roles.RoleName
FROM [dbo].[User] as U
INNER JOIN [dbo].UserRole as UR ON UR.UserID=U.UserID
INNER JOIN [dbo].RoleMaster as Roles ON Roles.RoleID=UR.RoleMasterID
FOR JSON AUTO
out put of above query is
[
{
"UserID": 1,
"Name": "XYZ",
"Roles": [
{
"RoleID": 1,
"RoleName": "Admin"
}
]
},
{
"UserID": 2,
"Name": "PQR",
"Roles": [
{
"RoleID": 1,
"RoleName": "Admin"
},
{
"RoleID": 2,
"RoleName": "User"
}
]
},
{
"UserID": 3,
"Name": "ABC",
"Roles": [
{
"RoleID": 1,
"RoleName": "Admin"
}
]
}
]

Though it is an old question, just thought might help others looking for the same issue. The below script should output the json schema you have been looking for.
SELECT roles, user.* from `user_table` AS user
INNER JOIN `roles_table` AS roles
ON user.id=roles.id

Related

Karate API framework how to match the response values with the table columns?

I have below API response sample
{
"items": [
{
"id":11,
"name": "SMITH",
"prefix": "SAM",
"code": "SSO"
},
{
"id":10,
"name": "James",
"prefix": "JAM",
"code": "BBC"
}
]
}
As per above response, my tests says that whenever I hit the API request the 11th ID would be of SMITH and 10th id would be JAMES
So what I thought to store this in a table and assert against the actual response
* table person
| id | name |
| 11 | SMITH |
| 10 | James |
| 9 | RIO |
Now how would I match one by one ? like first it parse the first ID and first name from the API response and match with the Tables first ID and tables first name
Please share any convenient way of doing it from KARATE
There are a few possible ways, here is one:
* def lookup = { 11: 'SMITH', 10: 'James' }
* def items =
"""
[
{
"id":11,
"name":"SMITH",
"prefix":"SAM",
"code":"SSO"
},
{
"id":10,
"name":"James",
"prefix":"JAM",
"code":"BBC"
}
]
"""
* match each items contains { name: "#(lookup[_$.id+''])" }
And you already know how to use table instead of JSON.
Please read the docs and other stack-overflow answers to get more ideas.

Is it possible to implement with Django Restframework?

I'm making API Server with DRF(DB is MySQL).
Now I made some system similar with facebook's like.
First, below is my Database Structure.
[user table]
userkey(PK)
username
[article table]
articleNo(PK)
userkey(FK to user)
content
[like table]
articleNo
userkey(FK to user)
When user click the "Like" buttons, articleNo and User's key will be inserted into like table.
Currently, When I access to /article/, shows below result.
{
"articleNo": 1,
"userkey": "22222",
"content": "test1",
"date": "2018-02-14T22:34:36.673805+09:00"
},
{
"articleNo": 2,
"userkey": "11111",
"content": "test2",
"date": "2018-02-15T22:34:36.673805+09:00"
},
...
...
If like table has two row like this,
+-----------+---------+
| articleNo | userkey |
+-----------+---------+
| 1 | 11111 |
| 1 | 22222 |
+-----------+---------+
It means that 11111 and 22222 user likes articleNo==1.
So When user access to /article?userkey=11111, What I would like instead as output is something like:
{
"articleNo": 1,
"userkey": "22222",
"content": "test1",
"isLiked": "true", // add this line
"date": "2018-02-14T22:34:36.673805+09:00"
},
{
"articleNo": 2,
"userkey": "11111",
"content": "test2",
"isLiked": "false", // add this line
"date": "2018-02-15T22:34:36.673805+09:00"
},
...
...
Is it possible to implement with DRF?
Thanks.
Yes, this can be done entirely on the ORM level, by using Django 1.8 conditional expressions
Having the following model structure (some example values):
class User(models.Model):
userkey = models.AutoField(primary_key=True)
username = models.CharField(max_length=255)
class Article(models.Model):
articleNo = models.AutoField(primary_key=True)
user = models.ForeignKey(User)
content = models.TextField()
class Like(models.Model):
article = models.ForeignKey(Article)
user = models.ForeignKey(User)
To demonstrate how this works, I created some example data:
john = User.objects.create(userkey=1, username='John')
alice = User.objects.create(userkey=2, username='Alice')
john_article = Article.objects.create(articleNo=1, user=john, content='Hi, I am John!')
alice_article = Article.objects.create(articleNo=2, user=alice, content='Hi, I am John!')
alice_likes_john_article = Like.objects.create(user=alice, article=john_article)
alice_likes_her_article = Like.objects.create(user=alice, article=alice_article)
john_likes_his_article = Like.objects.create(user=john, article=john_article)
You could achieve what you want on the ORM level:
articles = Article.objects.all().annotate(
like_count=Sum(
Case(
When(like__user=john, then=1),
default=0,
output_field=IntegerField(),
)
),
).annotate(
likes=Case(
When(like_count__gt=0, then=True),
default=False,
output_field=BooleanField()
)
)
(If somebody knows a simpler way than the above, I would be happy to learn as well)
Now every Article instance in the articles queryset will have two additional attributes: likes_count with the number of likes an article has received from John, and likes, a boolean, indicating if John likes it or not. Obviously you're interested in the latter.
Just override the get_queryset() method of your Article ViewSet, and then add an additional field to the Serializer of your Article class.
Also, you probably need to somehow pass the user instance (or the id) for the filter, but that can be done in various ways, including (for example) reading the query params inside the get_queryset method.

Trying to map json to mysql

These are the two tables I want to end up with:
tableA (I already have data in this table)
id | initials | name
1 | ER | Eric Robinsn
2 | DD | David Dobson
tableB (nothing in here yet)
id | tableA_id | nickname
1 | 1 | Rick
2 | 1 | Ricky
3 | 1 | Mr. Bossman
4 | 2 | Dave
5 | 2 | Davey
This is the JSON I have:
[
{
name: "Eric Robinson",
initials: "ER",
nicknames: ["Rick", "Ricky", "Mr. Bossman"]
},
{
name: "David Dobson",
initials: "DD",
nicknames: ["Dave", "Davey"]
}
]
Inserting into tableA is very easy, you can do it like this with node-mysql:
vary connection = require("mysql");
var json = JSON.parse(require("./data.json"));
var sql = "INSERT INTO tableA(initials, name) VALUES ?";
connection.query(sql, json, callback);
But as a complete SQL noob how would I map the data into tableB? After some researching I'm not sure if I can do this with something like the following:
INSERT INTO tableB (tableA_id, nickname)
SELECT id
FROM tableA
Or maybe I need to include a left join? The part that confuses me the most is how to include the tableA_id part of the query into the statement. I've tried
INSERT INTO tableB (tableA_id, nickname)
SELECT id
FROM tableA
WHERE tableB.tableA_id = tableA.id //this is the part I don't get
This is just an abstracted example. Also, I'm using node-mysql so when I'm inserting into tableB the re-mapped JSON looks looks like this:
[
{
initials: "ER", nickname: "Rick"
},
{
initials: "ER", nickname: "Ricky"
},
{
initials: "ER", nickname: "Mr. Bossman"
},
{
initials: "DD", nickname: "Dave"
},
{
initials: "DD", nickname: "Davey"
}
]

Query for manytomany relation (also return if empty)

I am building a popover, in which you can tick checkboxes. The options and choices are stored in a manytomany relationship inside a mysql database.
[ ] option A
[x] option B
[ ] option C
There are 3 tables. sphotos, sphoto_feedback and sphoto_has_feedbacks. sphoto_has_feedbacks stores a sphoto_id, a sphoto_feedback_id and a user_id, to reference the user that has submitted the voting.
sphoto
id | status_id | ...
1 | ...
sphoto_feedback
id | name | ...
11 | Quality |
12 | Creative |
sphoto_has_feedbacks
id | sphoto_id | sphoto_feedback_id | user_id
1 | 1 | 11 | 9999
The Input is user_id => 9999 and sphoto_id => 1. The desired output would be an array, which has all sphoto_feedback entrys, with a boolean variable, like this:
$output = [
"0" => [
"id" => 11,
"name" => "Quality",
"checked" => true
],
"1" => [
"id" => 12,
"name" => "Creative",
"checked" => false
]
]
It would look like this:
[x] Quality <-- stored in sphoto_feedback, also stored in sphoto_has_feedbacks with reference to user
[ ] Creative <-- stored in sphoto_feedback
I want to retrieve all the options from the database and check, if the user has already voted on the options or not.
I know how to do it in PHP with 2 querys, but I'd like to use just one query and would like to know if this is possible.
Use a LEFT JOIN to join the sphoto_feedback and sphoto_has_feedback tables, returning NULL for all the rows in the first table that don't have a match in the second table.
SELECT f.id, f.name, shf.id IS NOT NULL AS checked
FROM sphoto_feedback AS f
LEFT JOIN sphoto_has_feedback AS shf
ON f.id = shf.sphoto_feedback_id
AND shf.sphoto_id = 1 AND shf.user_id = 9999

How to Simulate subquery in MongoDB query condition

Let's suppose that I have a product logs collection, all changes are being done on my products will be recorded in this collection ie :
+------------------------------+
| productId - status - comment |
| 1 0 .... |
| 2 0 .... |
| 1 1 .... |
| 2 1 .... |
| 1 2 .... |
| 3 0 .... |
+------------------------------+
I want to get all products which their status is 1 but hasn't became 2. In SQL the query would look something like :
select productId from productLog as PL1
where
status = 1
and productId not in (
select productId from productLog as PL2 where
PL1.productId = PL2.productId and PL2.status = 2
)
group by productId
I'm using native PHP MongoDB driver.
Well since the logic here on the subquery join is simply that exactly the same key matches the other then:
Setup
db.status.insert([
{ "productId": 1, "status": 0 },
{ "productId": 2, "status": 0 },
{ "productId": 1, "status": 1 },
{ "productId": 2, "status": 1 },
{ "productId": 1, "status": 2 },
{ "productId": 3, "status": 0 }
])
Then use .aggregate():
db.status.aggregate([
{ "$match": {
"status": { "$ne": 2 }
}},
{ "$group": {
"_id": "$productId"
}}
])
Or using map reduce (with a DBRef):
db.status.mapReduce(
function() {
if ( this.productId.$oid == 2 ) {
emit( this.prouctId.$oid, null )
}
},
function(key,values) {
return null;
},
{ "out": { "inline": 1 } }
);
But again the SQL here was as simple as:
select productId
from productLog
where status <> 2
group by productId
Without the superfluous join on exactly the same key value
This mongo query above doesn't meet the requirements in question,
the result of the mongo-query includes documents with productId=1,
however the result of the SQL in question doesn't. Because in sample data: there exists 1 record with status=2, and productId of that document is 1.
So, assuming db.productLog.insert executed as stated above, you can use the code below to get the results:
//First: subquery for filtering records having status=2:
var productsWithStatus2 = db.productLog .find({"status":2}).map(function(rec) { return rec.productId; });
//Second:final query to get productIds which there not exists having status=2 with same productId :
db.productLog.aggregate([ {"$match":{productId:{$nin:productsWithStatus2}}},{"$group": {"_id": "$productId"}}]) ;
//Alternative for Second final query:
//db.productLog.distinct("productId",{productId:{$nin:productsWithStatus2}});
//Alternative for Second final query,get results with product and status detail:
//db.productLog.find({productId:{$nin:productsWithStatus2}});