Explain typeorm join syntax - typeorm

I'm new to TypeOrm and I'm trying to use an inner join, but the documentation doesn't explain it, so I have an example:
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import {Photo} from "./Photo";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(type => Photo, photo => photo.user)
photos: Photo[];
}
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
#Entity()
export class Photo {
#PrimaryGeneratedColumn()
id: number;
#Column()
url: string;
#ManyToOne(type => User, user => user.photos)
user: User;
}
If you want to use INNER JOIN instead of LEFT JOIN just use innerJoinAndSelect instead:
const user = await createQueryBuilder("user")
.innerJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
.where("user.name = :name", { name: "Timber" })
.getOne();
This will generate:
SELECT user.*, photo.* FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
Can somebody explain me in more detail how it functions, for example, I don't know what the first("user") refers to, I mean, is it a column?, how does it change if I need to use it between columns?
Also, what is the explanation for innerJoinAndSelect, why does it have 3 values, how are the values defined in TypeOrm syntax?
When it is using :name and after {name: "Timber"}, that object is defining :name?, and at last what happens if I don't want get only one and I want to get all the information from table1 where matches with table2, because actually that is what I want to do.

For the First question the param of createQueryBuilder which is user in your case, it's the alias you use in your query:
SELECT user., photo. FROM users user
INNER JOIN photos photo ON photo.user = user.id AND photo.isRemoved = FALSE
WHERE user.name = 'Timber'
const user = await createQueryBuilder("user")
.innerJoinAndSelect("user.photos", "photo", "photo.isRemoved = :isRemoved", { isRemoved: false })
.where("user.name = :name", { name: "Timber" })
.getOne();
The second question:The first argument is the relation you want to load, the second argument is an alias you assign to this relation's table and the third one is optional for any condition you'd add
the forth question, you should use getMany() instead of getOne();
I didn't understand the third question.

Related

TypeORM createQueryBuilder where statement not respecting Enitity column name

I have a pre-existing database table that has a column with a name of account_number, however, I wanted to reference this column in my code as accountNumber instead.
I have the following entity where I set the column name for the property type of accountNumber to be account_number
import { ObjectType, Field } from 'type-graphql';
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity } from 'typeorm';
#ObjectType()
#Entity({ database: 'main', name: 'account' })
class Account extends BaseEntity {
#Field(() => String)
#Column({ name: 'account_number' })
#PrimaryGeneratedColumn()
accountNumber!: string;
#Field(() => String)
#Column()
organisation!: string;
}
export default Account;
This is the query builder that I am having an issue with
await AccountRepository
.createQueryBuilder('a')
.where('a.`account_number` = :accountNumber', { accountNumber: "abc123" })
.getOne();
Expected Behavior
Query to resolve using the account_number column as is stated in the Column decorator.
SELECT `a`.`account_number` AS `a_accountNumber`,
`a`.`organisation` AS `a_organisation`
FROM `main`.`account` `a`
WHERE a.`account_number` = 'abc123'
Actual Behavior
However, it seems to want to use accountNumber instead of account_number even though I have set it as so in the Entity
SELECT `a`.`accountNumber` AS `a_accountNumber`,
`a`.`organisation` AS `a_organisation`
FROM `main`.`account` `a`
WHERE a.`account_number` = 'abc123'
It looks like you're doing the same thing as in this question:
Typeorm ignoring #column name of columns
I'm not clear if this is documented, but I think something is wrong with explicitly named PrimaryColumn() and PrimaryGeneratedColumn(). If you absolutely need the generated column, you should be able to use
#Column(name: 'account_number',
// "serial" type is equivalent to AI in PG
type: 'serial',
primary: true)
accountNumber: string;
(from here: https://github.com/typeorm/typeorm/issues/1517).
Also, you don't need the ! on a primary key, it's always going to be required.

repository.save uses insert instead of update

I am using TypeORM's repository pattern with a simple table, two columns, estate_num as primary key and estateId with mysql.
The repository.save method is supposed to insert if the record by primary key does not exist, otherwise, update it.
Instead, I get the following error:
query: SELECT `estate`.`estate_num` AS `estate_estate_num`, `estate`.`estateId` AS `estate_estateId` FROM `estate` `estate` WHERE (`estate`.`estate_num` = ?) -- PARAMETERS: ["0"]
query: START TRANSACTION
query: INSERT INTO `estate`(`estate_num`, `estateId`) VALUES (?, ?) -- PARAMETERS: ["0","caae4e67796e4ac000743f009210fcb0467fdf87d0fbbf6107359cf53f5b544b79eefdfdd78f95d50301f20a9c3212cd3970af22b722e59313813e8c5d2732d0"]
query failed: INSERT INTO `estate`(`estate_num`, `estateId`) VALUES (?, ?) -- PARAMETERS: ["0","caae4e67796e4ac000743f009210fcb0467fdf87d0fbbf6107359cf53f5b544b79eefdfdd78f95d50301f20a9c3212cd3970af22b722e59313813e8c5d2732d0"]
error: { Error: ER_DUP_ENTRY: Duplicate entry '0' for key 'PRIMARY'
I can run the SELECT manually, which is what I think determines whether to insert vs update. It returns a row as I would expect with the matching estate_num.
The entity model for this is:
#Entity()
export class Estate {
#PrimaryColumn({
name: 'estate_num',
})
estateNum: number;
#Column({
name: 'estateId',
})
estateId: string;
}
Note: the estate_num is not auto-increment, the key will be always supplied. It is odd, I understand.
Thanks!
I had the same problem. I was doing it wrong.
Before I was doing it multiple ways, but in the wrong way.
First I was doing it like this:
const user = User.create({ id: 1, username: "newName" })
user.save();
giving me an error of ID Duplicate Entry
Second I was doing it like this:
const user = User.create({ id: 1, username: "newName" })
User.save(user);
same error, Duplicate Entry.
Third I was doing it like this:
const user = User.findOneOrFaile(1);
const newUser = { ...user, ..req.body }
User.save(newUser);
same error.
And I just got helped and It should be done like this. You need to get the instance and merged it using Object.assign()
try {
const user: User = await User.findOneOrFail(id);
Object.assign(user, { ...req.body }); <---- SO ITS LIKE THIS
await user.save();
res.json({
meta: {},
payload: {
...req.body
}
});
} catch (error) {
logger.error(error);
res.status(404).json({
error
});
}
From your logs it's clear that you've passed ID as a string -- PARAMETERS: ["0"], but Estate entity estateNum is of type number. Convert the ID to number before passing it to repository.save.
I put together the following ts file to test this:
import 'reflect-metadata';
import { createConnection } from 'typeorm';
import { Estate } from './entity/Estate';
createConnection().then(async connection => {
const estate = new Estate();
estate.estateNum = 0;
estate.estateId = 'alpha';
await connection.getRepository(Estate).save(estate);
estate.estateId = 'beta';
await connection.getRepository(Estate).save(estate);
});
It works as expected (based on the following log output):
query: SELECT "Estate"."estate_num" AS "Estate_estate_num", "Estate"."estateId" AS
"Estate_estateId" FROM "estate" "Estate" WHERE ("Estate"."estate_num" = ?) --
PARAMETERS: [0]
query: BEGIN TRANSACTION
query: INSERT INTO "estate"("estate_num", "estateId") VALUES (?, ?) -- PARAMETERS:
[0,"alpha"]
query: COMMIT
query: SELECT "Estate"."estate_num" AS "Estate_estate_num", "Estate"."estateId" AS
"Estate_estateId" FROM "estate" "Estate" WHERE ("Estate"."estate_num" = ?) --
PARAMETERS: [0]
query: BEGIN TRANSACTION
query: UPDATE "estate" SET "estateId" = ? WHERE "estate_num" = ? -- PARAMETERS:
["beta",0]
query: COMMIT
I did notice that if you you do not await the save results, typeorm will attempt to insert the row twice (because the first save didn't complete before the second started).
Could this be your problem?
(Note I used sqlite3 in this example).

Criteria in relation between entities - Grails

I have an app with the following entities:
Topic:
class Topic {
UUID id
String description
String name
boolean visibility = true
// Relation
static hasMany = [tests:Test]
...
}
Test:
class Test {
UUID id
boolean active = true
String description
...
static hasMany = [evaluationsTest: Evaluation]
static belongsTo = [topic: Topic, catalog: Catalog]
}
When I show all visible topics to the user I request the query:
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
This query returns me for example: [['English'], ['Spanish']]. Then, I can show the full information about each topic to the user.
But I also want to indicate to the user the number of active test in each visible topic.
For example:
English topic has 2 active test.
Spanish topic has a total of 2 test. One is active and the other is not.
German topic has not any active test.
Then I need a query whose result is: def activeTotalEachTopic = [[2],[1],[0]] and I can pass the activeTotalEachTopic variable to the view (.gsp).
Solution:
From the first query where I can obtain all visible topics, I get the number of active test.
def visibleTopics = Topic.findAllByVisibility(true, [sort:"name", order:"asc"])
def numberActiveTest = []
activeTopics.each { topic ->
def result = Test.findAllByTopicAndActive(topic, true).size()
numberActiveTest.push(result)
}
And I pass to the view both variables.
render view: 'home', model: [activeTopics: activeTopics, numberActiveTest: numberActiveTest]
What you are missing is grouping so that you get the count per group, rather than a total count.
You also need to change the join type from the default inner join to an outer join in order for topics without an active test to return 0. However, a side-effect of this is that it changes how association properties are referenced due to the alias that's created by the join. Something like this:
import static org.hibernate.sql.JoinType.*
def activeTotalEachTopic = Topic.createCriteria().list() {
createAlias('tests', 't', LEFT_OUTER_JOIN)
eq 'visibility', true
or {
eq 't.active', true
isNull 't.id'
}
projections {
groupProperty 'name'
count()
}
order ("name", "asc")
}
Now, another issue to address is that the output would be something like this due to the grouping: [['English', 2],['Spanish', 1],['German', 0]]. So what you can do is collect the second item in each sub-list:
activeTotalEachTopic*.getAt(1)
// Which is the equivalent of...
activeTotalEachTopic.collect { it[1] }

Parse.js + AngularJS app relational data query issue

I have two parse classes; Companies and Ratings. It is a one to many relationship. Companies can have many Ratings. This is the statement I would perform in SQL:
SELECT Companies.name, Ratings.rating
FROM Companies
INNER JOIN Ratings
ON Ratings.name_id = Companies.name_id
ORDER BY Companies.name
I want the equivalent of this in Parse, but I'm not sure of how to go about it. Here is what I've currently tried:
function getRatings() {
var tableA = new Parse.Query(Companies);
var tableB = new Parse.Query(Ratings);
tableB.equalTo("name_id", tableA.name_id);
tableB.find({
success: function(results) {
$scope.$apply(function() {
$scope.companies = results.map(function(obj) {
return {
id: obj.get("name_id"),
name: obj.get(tableA.name),
rating: obj.get("rating"),
parseObject: obj
};
});
});
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
}
I am calling this function when the controller loads. This code displays the rating in my output, but not the name of the company.
I am trying to get all the companies listed in the companies object, then pair them with all the ratings they have in the ratings object. Their common key is name_id. This is the code I am using within my Angular view:
<div class="span12">
<div ng-repeat="company in companies | filter: query | orderBy: orderList"
class="well company-description">
<h1>{{company.name}}</h1>
<h3>Rating: {{company.rating}}</h3>
</div>
</div>
If I am way off base on this, please let me know
Get rid of the name_id column in the Ratings class. This isn't how you're supposed to define relationship using Parse.
There are a couple of options for you to choose.
Option 1
Using the Parse data browser, add a new column under the Companies class, called ratings. It should be a column of type Relation and point to Ratings as the target class. (Let me know if you need more information on how to do this.)
Then, when you create or edit a company, add ratings as follows:
var Companies = Parse.Object.extend("Companies");
var Ratings = Parse.Object.extend("Ratings");
var company = new Companies({name: "Some Company"});
company.relation("ratings").add(new Ratings({stars: 5}));
company.save();
Then, when querying Companies, do so as follows:
new Parse.Query(Companies).find({
success: function(companies) {
for (var i = 0; i < companies.length; i++) {
companies[i].relation("ratings").query().find({
success: function(ratings) {
// Finally, I have the ratings for this company
}
}
}
}
});
Option 2
Using the Parse data browser, add a new column under the Companies class, called ratings. It should be a column of type Array.
Then, when you create or edit a company, add ratings as follows:
var Companies = Parse.Object.extend("Companies");
var Ratings = Parse.Object.extend("Ratings");
var company = new Companies({
name: "Some Company",
ratings: [new Ratings({stars: 5})]
});
company.save();
Then, when querying Companies, do so as follows:
new Parse.Query(Companies).include("ratings").find({
success: function(companies) {
// Yay, I have access to ratings via companies[0].get("ratings")
}
});
include("ratings") tells Parse to include the actual objects, rather than pointers to objects for the given key.
Conclusion
Option 1 is better if you are expecting to have a large amount of ratings for each company, and if you don't always plan on retrieving all the ratings each time you query the companies.
Option 2 is better if the number of ratings for each company is relatively small, and you always want ratings to come back when you query companies.
I found out how to resolve the Uncaught You can't add an unsaved Parse.Object to a relation. error.
var addRating = new Ratings({stars: rating}); // save rating first, then associate it with a company
addRating.save({
success: function() {
var addCompany = new Companies({name: name});
addCompany.relation("ratings").add(addRating);
addCompany.save();
}
});
The rating has to be saved first, then the company relation can be added later on... makes sense, but took me awhile to figure it out! :S

sfDoctrineForm - How can i check if the object in an embedded form exists and relate it to the new parent object instead of creating a new one?

Im working on a user registration form that involves 3 different objects, The user, member profile, and member organization. Im trying to embed all these into a single registration form like User > Member > School. The basic implementation of this works just fine with the typical sfDoctrineForm::embedRealtion procedure.
The problem is the organization has a unique index on its member_number, the only time the the supplied value for this wont be in the database is when that user is first person to sign up from their organization. So in this case we will get a vlaidation error (or a key constraint violation if we turn the validation off).
What I want to happen instead, is that i check for the existence of the MemberOrganization with the same member_number in the database (either in a pre/post validator or in updateObject or wherever is appropriate). If it the member number already exists then i want to relate the new MemberProfile to this existing organization instead of linking it to the new one that was submitted, throwing out all the new values for that organization.
Ive tried modifying the object in the organization form via validation but this always results in a organization_id constraint violation coming from the profile:
$object = $this->getObject();
$table = $object->getTable();
$existing = $table->findOneByMemberNumber($values['member_number']);
if($existing)
{
$members = clone $object->Members;
$object->assignIdentifier($existing->identifier());
$object->fromArray($existing->toArray(false), false);
foreach($members as $member)
{
$member->link($object);
}
$values = $object->toArray(false); // return only the direct values
}
return $values;
The schema looks something like this:
User:
columns:
username: {type: string(100)}
email: {type: string(255), unique: true}
MemberProfile:
columns:
# some none authentication related user details
organization_id: {type: integer, notull: true}
user_id: {type: integer, notnull: true}
relations:
User:
local: user_id
type: one
foreign: id
foreignType: one
MemberOrganization:
local: orgainization_id
type: one
foreign: id
foreignType: many
foreignAlias: Members
MemberOrganization:
columns:
membership_number: {type: string(255), unique: true}
# other organization data
So what i ended up doing was overriding bind on the top level form (the one for the User). In this method i check for the existence of the Orgainization and if it exists i attach it to the Member Profile object and then re-embed all the subforms.
Ideally i would actually do this in the Member form but since the values are only bound at the top level form and then just cascade through the error schema for validation this seems to be a no go. Complete re-embedding seems to be required to get the object associations correct.
Some sample code (less some sanitizing code on the member number before issuing the query):
public function linkOrganizationIfExists(array $orgValues)
{
$defaultOrg = $this->embeddedForms['member_profile']->embeddedForms['organization']->getObject();
$table = $defaultOrg->getTable();
if(isset($orgValues['member_number'])
&& ($existingOrg = $table->findOneByMemberNumber($orgValues['member_number'])))
{
$user = $this->getObject();
$profile = $user->getMemberProfile();
$profile->Organization = $existingOrg;
// prepare the current values from the db to return
$orgValues = array_merge($orgValues, array_intersect_key($existingOrg->toArray(false), $orgValues));
$this->embedRelation('MemberProfile as member_profile', 'MemberProfileRegisttrationForm');
}
return $orgValues;
}
public function bind(array $taintedValues = null, array $taintedFiles = null)
{
if(isset($taintedValues['member_profile']['organization']))
{
$taintedValues['member_profile']['organization'] = $this->linkOrganizationIfExists($taintedValues['member_profile']['organization']);
}
parent::bind($taintedValues, $taintedFiles);
}

Resources