0

What I'm using

node.js, express, sequelize 6.3.3, fast-csv 4.3.1 and mysql database.

What I have

I have a CSV file with following headers and data.

no, name, email, township, father_name, father_phone, mother_name, mother_phone, parent_township

In this CSV, I have two types of data: students and parents.

In my MySQL database, I have three tables:

students, parents and townships 

Students table:

id, name, email, township_id

Parents table:

id, student_id, father_name, father_phone, mother_name, mother_phone, parent_township_id

Townships table:

id, name

What I've done

I read the CSV with fast-csv npm package with the following code.

let csvData = [];

fs.createReadStream(req.file.path)
    .pipe(
      csv.parse({
        ignoreEmpty: true,
        trim: true,
        skipLines: 6,
      })
    )
    .on("error", (error) => console.error(error))
    .on("data", (row) => {
      csvData.push(getHeaders(_.compact(row), csvStudentDataEntryFields));
    })
    .on("end", () => {
      fs.unlinkSync(req.file.path);

      const data = csvData.map((data) => {
        const student = _.pick(data, studentFields);
        const parent = _.pick(data, parentFields);
        return [student, parent];
      });

      return res.status(201).send({
        status: "success",
        data,
      });
    });

What I got

With the above code, I got data with following values.

[
    [ // row 1 from CSV
        {
            "name": "John",
            "email": "john@gmail.com",
            "township": "NYC",
        },
        {
            "fatherName": "Smith",
            "fatherPhone": "9111111111",
            "motherName": "Mary",
            "motherPhone": "9111111111",
            "parentTownship": "NYC"
         }
    ],
    [ // row 2 from CSV
        {
            "name": "Cutter",
            "email": "cutter@gmail.com",
            "township": "NYC",
        },
        {
            "fatherName": "Laseli",
            "fatherPhone": "9111111111",
            "motherName": "Mary",
            "motherPhone": "9111111111",
            "parentTownship": "NYC"
        }
    ]
]

What I want

I want to store those row 1 and row 2 from data into respective tables in the database.

The problems

I think I need to replace those township text data with real id because I have foreign keys stated as above.

How can I achieve it? I want to do it in database level. I don't want to look up the id of that township name in separate js module.

Updated

Student Model

class Student extends Model {
    static associate(models) {
      Student.belongsTo(models.Township, {
        foreignKey: "townshipId",
        as: "township",
        targetKey: "townshipId",
      });

     Student.hasOne(models.Parent, {
        foreignKey: "studentId",
        as: "parent",
        sourceKey: "studentId",
      });
    }
  }

Parent Model

class Parent extends Model {
    static associate(models) {
      Parent.belongsTo(models.Student, {
        foreignKey: "studentId",
        as: "student",
        targetKey: "studentId",
      });

      Parent.belongsTo(models.Township, {
        foreignKey: "parentTownshipId",
        as: "township",
        targetKey: "townshipId",
      });
    }
  }

Township Model

class Township extends Model {
    static associate(models) {
      Township.hasMany(models.Student, {
        foreignKey: "townshipId",
        as: "township",
        sourceKey: "townshipId",
      });

      Township.hasMany(models.Parent, {
        foreignKey: "townshipId",
        as: "parentTownship",
        sourceKey: "townshipId",
      });
    }
  }

Updated:

in my controller,

let std = await models.Student.create(
    {
        nameEn: "John",
        email: "john@gmail.com",
        townshipId: 1,
        parent: [{ ...d[1], parentTownshipId: 1 }],
    },
    { include: ["parent"] }
);
Htet Phyo Naing
  • 464
  • 7
  • 20

1 Answers1

1

I think you can insert all relationship together.

This is from sequelize document

const amidala = await User.create({
  username: 'p4dm3',
  points: 1000,
  profiles: [{
    name: 'Queen',
    User_Profile: {
      selfGranted: true
    }
  }]
}, {
  include: Profile
});

const result = await User.findOne({
  where: { username: 'p4dm3' },
  include: Profile
});

console.log(result);

set source key

 const Foo = sequelize.define('foo', {
  name: { type: DataTypes.TEXT, unique: true }
}, { timestamps: false });
const Bar = sequelize.define('bar', {
  title: { type: DataTypes.TEXT, unique: true }
}, { timestamps: false });
const Baz = sequelize.define('baz', { summary: DataTypes.TEXT }, { timestamps: false });
Foo.hasOne(Bar, { sourceKey: 'name', foreignKey: 'fooName' });
Bar.hasMany(Baz, { sourceKey: 'title', foreignKey: 'barTitle' });
// [...]
await Bar.setFoo("Foo's Name Here");
await Baz.addBar("Bar's Title Here");

ref https://sequelize.org/master/manual/advanced-many-to-many.html#using-one-to-many-relationships-instead

AnonyMouze
  • 416
  • 2
  • 7
  • Thank you so much for your answer. But I got the following error. `SequelizeDatabaseError: Incorrect integer value: 'Falam' for column 'township_id'` – Htet Phyo Naing Jul 31 '20 at 13:35
  • this is probably association problems. – AnonyMouze Jul 31 '20 at 13:51
  • I've updated and added `associations` in my question. Please see this. – Htet Phyo Naing Jul 31 '20 at 14:10
  • try add target key source.belongsTo(target, {foreignKey: 'fkname', targetKey: 'id'}); – AnonyMouze Jul 31 '20 at 14:18
  • Still not working. `SequelizeValidationError: notNull Violation: Student.townshipId cannot be null` – Htet Phyo Naing Jul 31 '20 at 14:36
  • set sourceKey as well..in association for hasMany – AnonyMouze Jul 31 '20 at 14:39
  • Updated the models in the question. But same error. – Htet Phyo Naing Jul 31 '20 at 14:50
  • added in the answer – AnonyMouze Jul 31 '20 at 14:53
  • My friend, thank you so much for helping me that far. I'm now OK with association, i.e. I can now insert `student` together with `parent`. But as you can see, those `townshipId` are hard coded. In the req, township is a text value like `NYC`. (But that `NYC` exists in `townships` table with a `id`.) How can I insert that? – Htet Phyo Naing Jul 31 '20 at 16:11
  • Updated in my question for my above comment. – Htet Phyo Naing Jul 31 '20 at 16:31
  • one way i could think of check township first, find an township with "nyc" name, if there is return you change the value to id. another way is use upsert on township then insert with return id. upsert will insert and update on duplicate, which means if nyc exist then it won't do anything . You should use transaction for this case. – AnonyMouze Jul 31 '20 at 22:30
  • however, i recommend add township route or add it directly to db and use the id , because it easier to manage . – AnonyMouze Jul 31 '20 at 22:32
  • 1
    Thanks. I see clearly your first way to check first if the incoming `NYC` exists in `townships` table and if it is there, grab the `id` and replace it with `NYC`. I think `upset` will not be OK because it will insert if `NYC` is not there in `townships`. It could allow inserting false data. And sorry I don't understand your recommend way in last comment. (There will be routes to CRUD `townships` resource separately.) – Htet Phyo Naing Aug 01 '20 at 08:04