Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Sequelize targetKey not working

I am trying to associate two models "Note" and "Resource" using sequelize. However, targetKey is not working as expected.

Note modal :

module.exports = function(sequelize, DataTypes) {
  return sequelize.define('note', {
    NoteID: {
      type: DataTypes.INTEGER(11),
      allowNull: false,
      primaryKey: true,
      autoIncrement: true
    },
    Title: {
      type: DataTypes.STRING(50),
      allowNull: true
    },
    Note: {
      type: DataTypes.STRING(500),
      allowNull: false
    },
    CreatedBy: {
      type: DataTypes.INTEGER(11),
      allowNull: false,
      references: {
        model: 'resource',
        key: 'ResourceID'
      }
    },
    UpdatedBy: {
      type: DataTypes.INTEGER(11),
      allowNull: true,
      references: {
        model: 'resource',
        key: 'ResourceID'
      }
    }
  }, {
    tableName: 'note'
  });
};

Resource modal :

module.exports = function(sequelize, DataTypes) {
  return sequelize.define('resource', {
    ResourceID: {
      type: DataTypes.INTEGER(11),
      allowNull: false,
      primaryKey: true,
      autoIncrement: true
    },
    FirstName: {
      type: DataTypes.STRING(250),
      allowNull: false
    },
    LastName: {
      type: DataTypes.STRING(250),
      allowNull: false
    }
  }, {
    tableName: 'resource'
  });
};

Association:

Resource.belongsTo(Note,{
    foreignKey: 'UpdatedBy',
    as: 'Resource_Updated_Note'
});

Note.hasOne(Resource,{
    foreignKey: 'ResourceID',
    targetKey: 'UpdatedBy',
    as: 'Note_Updated_By'
});

Resource.belongsTo(Note,{
    foreignKey: 'CreatedBy',
    as: 'Resource_Created_Note'
});

Note.hasOne(Resource,{
    foreignKey: 'ResourceID',
    targetKey: 'CreatedBy',
    as: 'Note_Created_By'
});

Although I have mentioned the targetKey while association, it is taking PrimaryKey while joining the tables.

Execution.

Note.findAll({
        include: [{
            model: Resource,
            as: 'Note_Updated_By'
        }],
        where: {
            Status: {
                [SQLOP.or]: ['Active', 'ACTIVE']
            }
        }
    }).then(function (response) {
        callback(response);
    });

On basis of the execution, this select query is generated.

SELECT * FROM `note` LEFT OUTER JOIN `resource` AS `Note_Updated_By` ON `note`.`NoteID` = `Note_Updated_By`.`ResourceID`;

Instead of note.NoteID, it should be note.UpdatedBy

like image 479
Sarjit Delivala Avatar asked Aug 29 '26 19:08

Sarjit Delivala


2 Answers

As of new version that i am using "sequelize": "^5.8.12" use sourceKey instead of targetKey for hasOne and hasMany relations works for me.

ModelName.hasOne(ModelName1, {
  as: 'SomeAlias',
  foreignKey: 'foreign_key',
  onDelete: 'NO ACTION',
  onUpdate: 'NO ACTION',
  sourceKey: 'YOUR_CUSTOM_ASSOCIATION_KEY'
});
like image 157
valar morghulis Avatar answered Aug 31 '26 09:08

valar morghulis


You have to set both hasMany({ sourceKey and belongsTo({ targetKey

This is sequelize docs failing us again, each side has a different name, and we have to set both e.g.:

const Country = sequelize.define('Country', {
  country_name: { type: DataTypes.STRING, unique: true },
});
const City = sequelize.define('City', {
  parent_country: { type: DataTypes.STRING },
  city_name: { type: DataTypes.STRING },
});
Country.hasMany(City, { foreignKey: 'parent_country', sourceKey: 'country_name' } )
City.belongsTo(Country, { foreignKey: 'parent_country', targetKey: 'country_name' } )

If you set just the sourceKey, then the query will be wrong, both are needed.

Minimal runnable example:

main.js

#!/usr/bin/env node
const assert = require('assert')
const path = require('path')
const { DataTypes, Sequelize } = require('sequelize')
let sequelize
if (process.argv[2] === 'p') {
  sequelize = new Sequelize('tmp', undefined, undefined, {
    dialect: 'postgres',
    host: '/var/run/postgresql',
  })
} else {
  sequelize = new Sequelize({
    dialect: 'sqlite',
    storage: 'tmp.sqlite'
  })
}
;(async () => {
const Country = sequelize.define('Country', {
  country_name: { type: DataTypes.STRING, unique: true },
});
const City = sequelize.define('City', {
  parent_country: { type: DataTypes.STRING },
  city_name: { type: DataTypes.STRING },
});
Country.hasMany(City, { foreignKey: 'parent_country', sourceKey: 'country_name' } )
City.belongsTo(Country, { foreignKey: 'parent_country', targetKey: 'country_name' } )
await sequelize.sync({force: true});
await Country.create({country_name: 'germany'})
await Country.create({country_name: 'france'})
await City.create({parent_country: 'germany', city_name: 'berlin'});
await City.create({parent_country: 'germany', city_name: 'munich'});
await City.create({parent_country: 'france', city_name: 'paris'});
const rows = await City.findAll({
  where: { parent_country: 'germany' },
  include: {
    model: Country,
  }
});
assert.strictEqual(rows[0].Country.country_name, 'germany')
assert.strictEqual(rows[1].Country.country_name, 'germany')
assert.strictEqual(rows.length, 2)
})().finally(() => { return sequelize.close() })

package.json

{
  "name": "tmp",
  "private": true,
  "version": "1.0.0",
  "dependencies": {
    "pg": "8.5.1",
    "pg-hstore": "2.3.3",
    "sequelize": "6.14.0",
    "sqlite3": "5.0.2"
  }
}

Produced queries as desired:

Executing (default): DROP TABLE IF EXISTS `Cities`;
Executing (default): DROP TABLE IF EXISTS `Countries`;
Executing (default): DROP TABLE IF EXISTS `Countries`;
Executing (default): CREATE TABLE IF NOT EXISTS `Countries` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `country_name` VARCHAR(255) UNIQUE);
Executing (default): PRAGMA INDEX_LIST(`Countries`)
Executing (default): PRAGMA INDEX_INFO(`sqlite_autoindex_Countries_1`)
Executing (default): DROP TABLE IF EXISTS `Cities`;
Executing (default): CREATE TABLE IF NOT EXISTS `Cities` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `parent_country` VARCHAR(255) REFERENCES `Countries` (`country_name`) ON DELETE CASCADE ON UPDATE CASCADE, `city_name` VARCHAR(255));
Executing (default): PRAGMA INDEX_LIST(`Cities`)
Executing (default): INSERT INTO `Countries` (`id`,`country_name`) VALUES (NULL,$1);
Executing (default): INSERT INTO `Countries` (`id`,`country_name`) VALUES (NULL,$1);
Executing (default): INSERT INTO `Cities` (`id`,`parent_country`,`city_name`) VALUES (NULL,$1,$2);
Executing (default): INSERT INTO `Cities` (`id`,`parent_country`,`city_name`) VALUES (NULL,$1,$2);
Executing (default): INSERT INTO `Cities` (`id`,`parent_country`,`city_name`) VALUES (NULL,$1,$2);
Executing (default): SELECT `City`.`id`, `City`.`parent_country`, `City`.`city_name`, `Country`.`id` AS `Country.id`, `Country`.`country_name` AS `Country.country_name` FROM `Cities` AS `City` LEFT OUTER JOIN `Countries` AS `Country` ON `City`.`parent_country` = `Country`.`country_name` WHERE `City`.`parent_country` = 'germany';

notably we have the desired REFERENES:

CREATE TABLE IF NOT EXISTS `Cities` (
  `id` INTEGER PRIMARY KEY AUTOINCREMENT,
  `parent_country` VARCHAR(255) REFERENCES `Countries` (`country_name`) ON DELETE CASCADE ON UPDATE CASCADE,
  `city_name` VARCHAR(255));

and he desired JOIN ON:

ON `City`.`parent_country` = `Country`.`country_name`

with the custom columns.

Tested on SQLite and PostgreSQL 13.5.

Related:

  • sequelize: association is referencing to wrong foreignKey column name
  • Association is using wrong column