Typeorm - Converting SQL Left Join one to one relation into typeorm query builder - sql

I have following entities in a postgresql database using Typeorm:
#Entity('issuer')
export class Issuer {
#PrimaryColumn()
issuer_id: string
#OneToOne(() => UserData, { cascade: true })
#JoinColumn({ name: 'issuer_id', referencedColumnName: 'public_address' })
u_data: UserData
#BeforeInsert()
newid() {
this.issuer_id = this.u_data.public_address
}
...remaining columns...
}
#Entity('user_data')
export class UserData {
#PrimaryColumn({ type: 'varchar', unique: true })
email: string
#Column({ type: 'varchar', nullable: false, unique: true })
public_address: string
...remaining columns...
}
Above in the Issuer entity, I am doing a small trick to be able to make a key both primary and foreign, issuer_id column, which is primary key of Issuer and foreign key of UserData which refers to public_address column of UserData. I wanna join both entities, and I am able to do it with the following query:
SELECT *
FROM user_data
LEFT OUTER JOIN issuer ON issuer.issuer_id = user_data.public_address
WHERE user_data.email = $1
I am unable to convert this simple SQL code into Typeorm query builder. Here is my failed attempt:
await this.userRepository
.createQueryBuilder('user')
.leftJoin('user.public_address', 'issuer')
.where('user.email = :email', { email })
.getOne()
Here is the error I am getting:
TypeORMError: Relation with property path public_address in entity was not found.

It seems when trying to left join (right join doesn't exist on typeorm) from an entity that has no direct relation to its relative, leftJoinAndSelect function should be used with a condition:
return await this.userRepo
.createQueryBuilder('user')
.leftJoinAndSelect(Issuer, 'issuer', 'user.public_address = issuer.issuer_id')
.where('user.email = :email', { email })
.getRawOne()

Related

createQueryBuilder with getOne doesn't return #JoinColumns inside table Typeorm

I have a project written by nestjs and typeorm. In my project I have tables chat and user so I want to get ManyToOne relationships. Here is the chat table
export class Chat extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, (user) => user.chatsCreater, { nullable: false })
#JoinColumn({ name: 'creatorId' })
creator: User;
#ManyToOne(() => User, (user) => user.chatsCompanion, { nullable: false })
#JoinColumn({ name: 'companionId' })
companion: User;
}
and chunk from user table
...
#OneToMany(() => Chat, (chat) => chat.creator)
chatsCreater: Chat[];
#OneToMany(() => Chat, (chat) => chat.companion)
chatsCompanion: Chat[];
...
When I query data from chat I expect to get the hole table {id, companionId, creatorI} not only value of chat.id. Here is my query
.createQueryBuilder('chat')
.where('chat.creatorId = :creatorId AND chat.companionId = :companionId', { creatorId, companionId })
.getOne()
and the result {id: 1}
So what I want is to get values of companionId and creatorId too when I query from chat.
If I change getOne() to getRaw() I get the desired output. But in case of more complex queries (with multiple joins) it becomes a mess with getRaw so is there a way to get all columns using getOne ?
I was able to do it by using leftJoinAndSelect
.createQueryBuilder('chat')
.leftJoinAndSelect('chat.creator', 'creator')
.leftJoinAndSelect('chat.companion', 'companion')
.where('chat.creatorId = :creatorId AND chat.companionId = :companionId', { creatorId, companionId })
.getOne();
In case if hole table is not needed it's also possible to use leftJoin without select and later add to query addSelect(['creator.id','companion.id'])

Create query builder that the source table (FROM) is a join table in TypeORM

I'm trying to implement the following SQL in TypeORM using QueryBuilder:
SELECT
user_places.user_id,
place.mpath
FROM
public.user_root_places_place user_places
INNER JOIN
public.place place
ON place.id = user_places.place_id
The entities are:
#Entity()
export class User {
#Column({ unique: true, primary: true })
id: string;
#ManyToMany(() => Place)
#JoinTable()
rootPlaces: Place[];
}
#Entity()
export class Place {
#PrimaryGeneratedColumn()
id: number;
#Column()
mpath: string;
}
When you create a query builder you have to use some entity or table but the join table is "hidden" by TypeORM
I know I can replace the inner join table order and it will solve the problem but I'm looking for when the source table is the join table
If you don't want to use the generated name just specify explicitly the join table name
#Entity()
export class User {
#Column({ unique: true, primary: true })
id: string;
#ManyToMany(() => Place)
#JoinTable({
name: 'user_places' // <---
})
rootPlaces: Place[];
}
And then:
createQueryBuilder('user_places')
.select(['user_places.userId', 'place.mpath'])
.innerJoin(Place, 'place', 'place.id = user_places.place_id')
.getMany();

Why is my SQL data returning undefined in my array methods?

The First array returned when I console log person
The second array that returns when I console log person
I am trying to create a function that pulls the data from 2 tables in my SQL database. I am using async/await, which is still new to me. The issue is somewhere in the two array methods; for some reason they are returning data as undefined.
async function updateRole() {
console.log('hi');
//cycle through both arrays and create new arrays with same information using maps
//return object with employee and role info
const allEmployees = await db.promise().query(`SELECT * FROM employees`);
const allRoles = await db.promise().query(`SELECT * FROM roles`);
console.log(allEmployees);
const employeeChoices = allEmployees.map((person) => {
return {
name: `${person.first_name} ${person.last_name}`,
value: person.id
}
})
const roleChoices = allRoles.map((role) => {
return {
name: role.title,
value: role.id
}
})
const { employeeId, roleId } = await inquirer.prompt([
{
type: 'list',
name: 'employeeId',
message: 'Which employee would you like to update?',
choices: employeeChoices
},
{
type: 'list',
name: 'roleId',
message: 'What is their new role?',
choices: roleChoices
}])
await db.promise().query(`UPDATE employees SET role_id = ? WHERE id = ?`, [roleId, employeeId])
console.log('Successfully updated employee!');
askQuestions();
Update: I added screenshots fo the console log for person. role returns the same format, but obviously different data. I am unsure what the array of ColumnDefinition objects does, or why it's there.

Sequelize foreign key reference composite primary key

i'm trying to create with sequelize (postgre) 'Ingredient' table, with columns normalizedName/userId where normalizedName is unique per userId.
The second table is 'IngredientQuantity', with columns ingredientId/userId/quantity.
I tried to set in 'Ingredient' normalizedName and userId as primaryKey, and to foreign key this composite PK from 'IngredientQuantity' table with ingredientId, but i saw that was impossible with sequelize, only normalizedName is used for reference in foreign key.
Whats is the best approach to do that ? I thought about id auto increment, but all id are shared among all users. For example user1 create his first ingredient with id = 1, when user2 create his first ingredient he will have id = 2. So. i don't know if it's good idea, if all users have lot of ingredients i should use bigint etc..and if they delete/add/delete/add id will grow up.
Ingredient table
module.exports = (sequelize, DataTypes) => {
var Ingredient = sequelize.define('ingredient', {
name: DataTypes.STRING,
normalizedName: { type: DataTypes.STRING, primaryKey: true },
userId: { type: DataTypes.INTEGER, primaryKey: true }
}, {
freezeTableName: true
});
Ingredient.associate = function (models) {
models.ingredient.hasMany(models.ingredientQuantity);
models.ingredient.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return Ingredient;
};
IngredientQuantity table
module.exports = (sequelize, DataTypes) => {
var IngredientQuantity = sequelize.define('ingredientQuantity', {
quantity: DataTypes.FLOAT,
}, {
freezeTableName: true
});
IngredientQuantity.associate = function (models) {
models.ingredientQuantity.belongsTo(models.ingredient);
models.ingredientQuantity.belongsTo(models.user, {
onDelete: "CASCADE",
foreignKey: {
allowNull: false
}
});
};
return IngredientQuantity;
};
Whats is the best approach if i consider lot of data with lot of users ? Is there an other solution ? Thanks
It's totally normal to use SERIAL as autoincremented integer surrogate PK. Also you can use UUID as autogenerated PKs (in such case you should set default value as uuid_generate_v4()) if you somehow afraid that integer value range will not be enough.
Because it's a service field there is no need it to be unique only for a certain user. Usually you shouldn't rely on a PK value.

setting up a one to many relationship for a self referencing table

I have a Project entity with a non-autogenerated id field and a successor field. This successor is the project that follows next. But maybe there is no following project so this might be null.
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#OneToMany(() => Project, project => project.id, { nullable: true })
public successorId?: string;
}
When creating a new project via
public createProject(id: string, successorId?: string): Promise<Project> {
const project: Project = new Project();
project.id = id;
project.successorId = successorId;
return project.save();
}
there are multiple cases I have to take care for.
Passing in an id that already exists:
This will not throw an error. It just overrides the existing entity.
Passing in undefined for the successorId:
The code works fine then but it does not create a successorId column with null then. The column simply does not exist in the database.
Passing in the same id for id and successorId (this should be possible):
TypeORM throws the error
TypeError: Cannot read property 'joinColumns' of undefined
Passing in a successorId of another existing project:
I'm getting the same error as above
Passing in a successorId of a project that doesn't exist:
I'm getting the same error as above
So how can I fix that? I think my entity design seems to be wrong. Basically it should be
One project might have one successor
A project can be the successor of many projects
Would be awesome if someone could help!
Update
I also tried this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
#Column()
public successorId?: string;
but whenever I want to call the createProject method I'm getting this error
QueryFailedError: null value in column "successorId" violates not-null
constraint
and this
#OneToMany(() => Project, project => project.successorId, { nullable: true })
public successorId?: string;
but then I'm getting this error
TypeError: relatedEntities.forEach is not a function
Please try this solution
#Entity()
export class Project extends BaseEntity {
#PrimaryColumn({ unique: true })
public id: string;
#Column({ nullable: true })
public successorId?: string;
#ManyToOne(() => Project, project => project.id)
#JoinColumn({ name: "successorId" })
public successor?: Project;
}
public successor?: Project; - property is used for building relation between entities (same entity in this case). Related entity must be specified as a property type, because TypeORM uses this type to determine target entity and build relation metadata. You can read more about TypeORM relations here
public successorId?: string; - property is just an "extracted" join column. When you use ManyToOne relation, TypeORM automatically creates a column in the database named propertyName + referencedColumnName (successorId in this case). But you cannot use this column in your code, because it is defined only in table and not in your class. And if you need this column defined in class (for further saving or displaying) you can create a property and mark it with a #Column decorator. Property name must be the same as the join column name in the table. Described in more detail here
Creating an entity with the same id just overrides the existing one
this is an expected behaviour. When you trying to save entity with an existing Id, TypeORM recognizes this as an update, not a create
You can try this
export class RolesPermission {
#PrimaryGeneratedColumn('uuid')
#PrimaryColumn({ type: 'varchar', length: 36, default: 'UUID()' })
entityId?: string;
#Column({ unique: true })
name: string;
#OneToMany(() => RolesPermission, (rolePermission) => rolePermission.parent)
rolePermissions?: RolesPermission[];
#ManyToOne(() => RolesPermission, (rolePermission) => rolePermission.rolePermissions, { nullable: true, createForeignKeyConstraints: false })
parent?: RolesPermission;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP' })
createdAt?: Date;
#Column({ type: 'datetime', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' })
updatedAt?: Date;}