Prisma insert relation one to many - sql

I read about prisma as an ORM provider and than decided to give it a try. My knowledge in database querying especially postrgres is not very experience at the moment, so I got into a situation where I need some help for a proper solution.
Let's assume the following schema.prisma:
model languages {
language_id Int #id #default(autoincrement())
language_code String #db.VarChar
translations_categories translations_categories[]
}
model categories {
category_id Int #id #default(autoincrement())
category_name String #db.VarChar
translations_categories translations_categories[]
}
model translations_categories {
translation_id Int #id #default(autoincrement())
language_id Int
category_id Int
translation_value String #db.VarChar
categories categories #relation(fields: [category_id], references: [category_id])
language languages #relation(fields: [language_id], references: [language_id])
}
So I try to solve the following update query with some api data:
Add a new category and insert it's category_name
Add the given translations for this category within translations_categories (within this step, the provided information contains the language_code, not the language_id!)
The request body contains the following fields:
{
id: 2
identifier: "TEST-1"
translations: {
de: "german",
en: "english",
fr: "france",
it: "italian"
}
}
The following questions have arisen:
Is it possible to achieve this workflow by a single query?
When inserting the translation, I thought it would be possible by the provided language_code on it's own, isn't it?

I would suggest creating the schema in the following manner:
model languages {
language_id Int #id #default(autoincrement())
language_code String #unique
translations_categories translations_categories[]
}
model categories {
category_id Int #id #default(autoincrement())
category_name String
translations_categories translations_categories[]
}
model translations_categories {
translation_id Int #id #default(autoincrement())
language_id Int
category_id Int
translation_value String
categories categories #relation(fields: [category_id], references: [category_id])
language languages #relation(fields: [language_id], references: [language_id])
##unique([language_id, category_id])
}
The reason for this is that language_code will always be unique for each language so having the #unique would make it much easier to create or update the category.
So you could create/update the category as follows:
let data = {
id: 2,
identifier: 'TEST-1',
translations: {
de: 'german',
en: 'english',
fr: 'france',
it: 'italian',
},
}
let translations = Object.entries(data.translations).map(([key, val]) => ({
language: {
connectOrCreate: {
where: { language_code: key },
create: { language_code: key },
},
},
translation_value: val,
}))
await prisma.categories.upsert({
where: { category_id: data.id },
create: {
category_name: data.identifier,
translations_categories: { create: translations },
},
update: {
category_name: data.identifier,
translations_categories: { create: translations },
},
})

Related

Filtering on related tables using Prisma and Postgres

I have a Prisma model for a Postgres database with a User that can belong to many Organisations, and organizations that can have many users. So I've created a typical many-to-many relationship.
model Organization {
organizationId Int #id #default(autoincrement())
slug String #unique
name String
users UserInOrganization[]
}
model User {
userId Int #id #default(autoincrement())
name String
organizations UserInOrganization[]
}
model UserInOrganization {
fkOrganizationId Int
fkUserId Int
organization Organization #relation(fields: [fkOrganizationId], references: [organizationId], onDelete: Cascade)
user User #relation(fields: [fkUserId], references: [userId], onDelete: Cascade)
role Role #relation(fields: [fkRoleId], references: [roleId])
##id([fkOrganizationId, fkUserId])
}
Next, I need to get all the organizations for a specific user and slug.
If I'd write it in SQL I'd do something like:
select o.name, u."userId" from "Organization" as o
join "UserInOrganization" as uio on o."organizationId" = uio."fkOrganizationId"
join "User" as u on u."userId" = uio."fkUserId"
where u."userId" = 1 and o.slug='slug'
But what would that be in Prisma? Doing something like below seems to give me all the organizations matching the slug (not considering the where on the userId). The where-clause is just filtering the users for each organization. πŸ₯Ί
const organization = await prisma.organization.findUnique({
where: { slug: slug },
select: {
name: true,
users: { where: { user: { userId: userId } } },
},
});
How can I get just the organizations that have the actual userId?
You can use include with nested select to get organisations that match slug and userId.
Here's the query for it:
const organization = await prisma.organization.findUnique({
where: {
slug: 'prisma',
},
include: {
users: {
where: {
user: {
userId: 1,
},
},
select: {
user: true,
},
},
},
});
console.log(JSON.stringify(organization, null, 2));
Here's the output:
{
"organizationId": 1,
"slug": "prisma",
"name": "Prisma",
"users": [
{
"user": {
"userId": 1,
"name": "John Doe"
}
}
]
}

How to create or update many-to-many relation in Prisma?

I have the following models, and many-to-many relation between them:
model User {
id String #id #default(cuid())
name String?
email String? #unique
followings Artist[]
}
model Artist {
id String #id #default(cuid())
name String #unique
spotifyId String #unique
followers User[]
}
When a user logs into my app, I retrieve their current followed artists, and need to update my database.
I have managed to select artists data from database (for updating user <-> artist relation), sample data:
const followings = [
{
id: '...',
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A'
},
{
id: '...',
name: 'Dua Lipa',
spotifyId: '6M2wZ9GZgrQXHCFfjv46we'
},
]
Now, this is my user object:
const user = {
id: 'someId',
name: 'someName',
email: 'someEmail'
}
I tried to insert or update user <-> artist relation with this query but I'm getting Bad Request error:
await prisma.user.upsert({
where: {
email: user.email
},
create: {
name: user.name,
email: user.email,
followings: {
connectOrCreate: followings
}
},
update: {
followings: {
connectOrCreate: followings
}
}
})
Please advise what I need to do. Thanks in advance.
P.S. I took the idea of the query from Updating a many-to-many relationship in Prisma post, but it didn't work for me, so please don't mark duplicate.
connectOrCreate should specify where key with id (so Prisma could find this entity) and create key with all required model fields (so Prisma could create it if it not already present), but you just passing an array of models. Change your code to this one:
await prisma.user.upsert({
where: {
email: 'user.email',
},
create: {
name: 'user.name',
email: 'user.email',
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
update: {
followings: {
connectOrCreate: [
{
create: {
name: 'MARINA',
spotifyId: '6CwfuxIqcltXDGjfZsMd9A',
},
where: { id: '...' },
},
],
},
},
});

GraphQL, Apollo : Creating an efficient schema

I recently started studying server development with GraphQL and Apollo.
In the code below, the formula for fetching each data is somewhat understandable.
schema.js
const { gql } = require('apollo-server');
const _ = require('lodash');
const onepieces = [
{
"id": "onepiece1",
"title": "μ›ν”ΌμŠ€ 1ꢌ",
"price": "1,360",
"desc": "λ™ν„°μ˜€λŠ” λͺ¨ν—˜μ˜ μ‹œλŒ€"
},
{
"id": "onepiece2",
"title": "μ›ν”ΌμŠ€ 2ꢌ",
"price": "1,360",
"desc": "λŒ€κ²° 버기 해적단"
}
];
const narutos = [
{
"id": "naruto1",
"title": "λ‚˜λ£¨ν†  1ꢌ",
"price": "1,360",
"desc": "λ‚˜λ£¨ν†  λͺ¨ν—˜μ˜ μ‹œμž‘"
},
{
"id": "naruto2",
"title": "λ‚˜λ£¨ν†  2ꢌ",
"price": "1,360",
"desc": "λ‚˜λ£¨ν† μ™€ μ•ˆκ°œλ§ˆμ„"
}
];
const typeDefs = gql`
type Onepiece { id: ID, title: String, price: String, desc: String }
type Naruto { id: ID, title: String, price: String, desc: String }
type Query {
onepiece(id: String!): Onepiece,
naruto(id: String!): Naruto,
getOnepieces: [Onepiece],
getNarutos: [Naruto]
}
`;
const resolvers = {
Query: {
onepiece: (parent, args) => _.find(onepieces, {id: args.id}),
naruto: (parent, args) => _.find(narutos, {id: args.id}),
getOnepieces: () => onepieces,
getNarutos: () => narutos
}
};
module.exports = { typeDefs, resolvers };
But It's inefficient code. If the category of comic book increases, I should continue to add the query. So I want to improve More convenient and readable.
For example, I would like to manage the Onepiece and Naruto categories in Comic Book.
How can I improve?
You might start by writing a GraphQL enum of the possible categories.
enum Category { ONEPIECE NARUTO }
Since both kinds of comic books have the same structure, you can have a single GraphQL type to represent them. We'll incorporate the category we just wrote so that you can tell which one is which.
type ComicBook implements Node {
id: ID!
category: Category!
title: String!
price: String!
desc: String!
}
There's a somewhat standard convention for retrieving arbitrary GraphQL objects by their ID; while it comes from Facebook's Relay Javascript client it's not specifically tied to that client, and I'd use it here.
interface Node {
id: ID!
}
type Query {
node(id: ID!): Node
}
This replaces your top-level queries to retrieve specific kinds of books by ID; you could write a query like
{
node(id: "naruto1") {
... on ComicBook { category title price desc }
}
}
Now that you have the category enum, you can also write a single top-level query to return comic books possibly filtered by category
type Query {
comicBooks(category: Category): [ComicBook!]!
}
{
comicBooks(category: ONEPIECE) { id title price desc }
}
There's some corresponding code changes to make this work; I'd probably start by combining the two lists of comic books into one and adding a similar category field there.
Once you've done this, if you add a third category, you need to add it to the enum and add it to the data set, but you should not need to make any other changes to either the code, the GraphQL schema, or the queries.

GraphQL field resolver needs contextual information

Maybe my terminology is not accurate. I'm using AWS AppSync. My schema:
type Book {
title: String
author: Author
}
type Author {
name: String
}
type Query {
getBook(title:String!): Book
}
The resolver for getBook returns an object shaped as:
{
title: <string>
authorId: <number>
}
Where authorId is always returned.
What I'd like to do is specify a resolver for the field Book.author that will receive authorId and fetch that object from its own data store. Is this possible?
If what I'm trying to do is not possible, what is the proper way of doing this, where one data store is a table with two columns - { title, authorId }, and a separate store has a table with a list of authors, where the primary key is a column authorId. Since these are two different services, I can't just join the two like a SQL query.
As long as authorId is returned from the getBook resolver, it will be accessible via $ctx.source.authorId when resolving Book.author.
I reproduced your API with local resolvers using your schema:
Query.getBook request mapping template:
{
"version": "2018-05-29",
"payload": {
"title": "$context.arguments.title",
"authorId": "2" ## returned in addition to other fields. It will be used by Book.author resolver.
}
}
Query.getBook response mapping template:
$util.toJson($context.result)
Book.author request mapping template:
{
"version": "2018-05-29",
"payload": {
"name": "author name with authorId: $context.source.authorId"
}
}
Book.author response mapping template:
$util.toJson($context.result)
The following query:
query {
getBook(title:"AWS AppSync") {
title
author {
name
}
}
}
will yield the results:
{
"data": {
"getBook": {
"title": "AWS AppSync",
"author": {
"name": "author name with authorId: 2"
}
}
}
}
You might need to have bookID as parent's ID inside Author:
type Author {
# parent's id
bookID: ID!
# author id
id: ID!
name: String!
}
type Book {
id: ID!
title: String!
author: Author!
}
When Create Resource, just make:
- Book.id as primary key of BookTable
- Author.bookID as primary key and Author.id as sort key of
AuthorTable
You also need to attach resolver for Book.author using $ctx.source.id
After you attach Book.author resolver, you are good to go. You can get result something like below:
getBook(title: "xx") {
id
title
author {
id
name
}
}

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")));
}