วิธีสร้างแบบสอบถามเข้าร่วมโดยใช้ Sequelize บน Node.js


106

ฉันใช้ ORM ภาคต่อ; ทุกอย่างดีและสะอาด แต่ฉันมีปัญหาเมื่อใช้กับjoinแบบสอบถาม ฉันมีสองรุ่น: ผู้ใช้และโพสต์

var User = db.seq.define('User',{
    username: { type: db.Sequelize.STRING},
    email: { type: db.Sequelize.STRING},
    password: { type: db.Sequelize.STRING},
    sex : { type: db.Sequelize.INTEGER},
    day_birth: { type: db.Sequelize.INTEGER},
    month_birth: { type: db.Sequelize.INTEGER},
    year_birth: { type: db.Sequelize.INTEGER}

});

User.sync().success(function(){
    console.log("table created")
}).error(function(error){
    console.log(err);
})


var Post = db.seq.define("Post",{
    body: { type: db.Sequelize.TEXT },
    user_id: { type: db.Sequelize.INTEGER},
    likes: { type: db.Sequelize.INTEGER, defaultValue: 0 },

});

Post.sync().success(function(){
    console.log("table created")
}).error(function(error){
    console.log(err);
})

ฉันต้องการข้อความค้นหาที่ตอบกลับด้วยโพสต์ที่มีข้อมูลของผู้ใช้ที่สร้างขึ้น ในแบบสอบถามดิบฉันได้รับสิ่งนี้:

db.seq.query('SELECT * FROM posts, users WHERE posts.user_id = users.id ').success(function(rows){
            res.json(rows);
        });

คำถามของฉันคือฉันจะเปลี่ยนรหัสให้ใช้สไตล์ ORM แทนแบบสอบถาม SQL ได้อย่างไร

คำตอบ:


134
User.hasMany(Post, {foreignKey: 'user_id'})
Post.belongsTo(User, {foreignKey: 'user_id'})

Post.find({ where: { ...}, include: [User]})

ซึ่งจะทำให้คุณ

SELECT
  `posts`.*,
  `users`.`username` AS `users.username`, `users`.`email` AS `users.email`,
  `users`.`password` AS `users.password`, `users`.`sex` AS `users.sex`,
  `users`.`day_birth` AS `users.day_birth`,
  `users`.`month_birth` AS `users.month_birth`,
  `users`.`year_birth` AS `users.year_birth`, `users`.`id` AS `users.id`,
  `users`.`createdAt` AS `users.createdAt`,
  `users`.`updatedAt` AS `users.updatedAt`
FROM `posts`
  LEFT OUTER JOIN `users` AS `users` ON `users`.`id` = `posts`.`user_id`;

ข้อความค้นหาด้านบนอาจดูซับซ้อนเล็กน้อยเมื่อเทียบกับสิ่งที่คุณโพสต์ แต่สิ่งที่ทำนั้นเป็นเพียงการใส่นามแฝงคอลัมน์ทั้งหมดของตารางผู้ใช้เพื่อให้แน่ใจว่าพวกเขาวางไว้ในโมเดลที่ถูกต้องเมื่อส่งคืนและไม่ปะปนกับโมเดลโพสต์

นอกเหนือจากนั้นคุณจะสังเกตเห็นว่ามันทำการ JOIN แทนที่จะเลือกจากสองตาราง แต่ผลลัพธ์ควรจะเหมือนกัน

อ่านเพิ่มเติม:


8
จะเกิดอะไรขึ้นถ้าฉันต้องการเข้าร่วมเฉพาะผู้ใช้ที่เกิดในปี 1984? ใน SQL ฉันจะทำ:SELECT * FROM posts JOIN users ON users.id = posts.user_id WHERE users.year_birth = 1984
Iwazaru

1
ลิงก์ทั้งหมดไม่ตาย :-(
Manwal

1
คำถามนั้นเคยถูกโพสต์ในคำถามที่ตอบแล้วหรือไม่?
เทพารักษ์

1
เราต้องการทั้งสองอย่างหรืออย่างใดอย่างหนึ่งก็เพียงพอแล้ว: User.hasMany (โพสต์, {ForeignKey: 'user_id'}) Post.belongsTo (User, {ForeignKey: 'user_id'})
ก่อนหน้า

2
@antew เมื่อคุณเรียกUser.hasMany(Post)คุณเพิ่มวิธีการ / แอตทริบิวต์ให้กับวัตถุผู้ใช้และเมื่อคุณเรียกPost.belongsTo(User)คุณเพิ่มวิธีการลงในคลาสโพสต์ หากคุณโทรจากทิศทางเดียวเสมอ (เช่น User.getPosts ()) คุณก็ไม่จำเป็นต้องเพิ่มอะไรลงในวัตถุโพสต์ แต่ดีที่มีวิธีการทั้งสองด้าน
Ryan Shillington

178

แม้ว่าคำตอบที่ได้รับการยอมรับจะไม่ผิดในทางเทคนิค แต่ก็ไม่ได้ตอบคำถามเดิมหรือคำถามติดตามในความคิดเห็นซึ่งเป็นสิ่งที่ฉันมาที่นี่เพื่อค้นหา แต่ฉันคิดออกแล้วนี่ไป

หากคุณต้องการค้นหาโพสต์ทั้งหมดที่มีผู้ใช้ (และเฉพาะโพสต์ที่มีผู้ใช้) โดยที่ SQL จะมีลักษณะดังนี้:

SELECT * FROM posts INNER JOIN users ON posts.user_id = users.id

ซึ่งมีความหมายเหมือนกับ SQL ดั้งเดิมของ OP:

SELECT * FROM posts, users WHERE posts.user_id = users.id

นี่คือสิ่งที่คุณต้องการ:

Posts.findAll({
  include: [{
    model: User,
    required: true
   }]
}).then(posts => {
  /* ... */
});

การตั้งค่าที่จำเป็นเป็นจริงเป็นกุญแจสำคัญในการสร้างการรวมภายใน หากคุณต้องการการรวมภายนอกด้านซ้าย (ซึ่งคุณจะได้รับโพสต์ทั้งหมดไม่ว่าจะมีผู้ใช้เชื่อมโยงอยู่หรือไม่) ให้เปลี่ยนจำเป็นเป็นเท็จหรือปล่อยทิ้งไว้เนื่องจากเป็นค่าเริ่มต้น:

Posts.findAll({
  include: [{
    model: User,
//  required: false
   }]
}).then(posts => {
  /* ... */
});

หากคุณต้องการค้นหาโพสต์ทั้งหมดที่เป็นของผู้ใช้ที่มีปีเกิดในปี 1984 คุณต้องการ:

Posts.findAll({
  include: [{
    model: User,
    where: {year_birth: 1984}
   }]
}).then(posts => {
  /* ... */
});

โปรดทราบว่าจำเป็นต้องเป็นจริงตามค่าเริ่มต้นทันทีที่คุณเพิ่ม where clause in

หากคุณต้องการโพสต์ทั้งหมดไม่ว่าจะมีผู้ใช้แนบมาหรือไม่ แต่หากมีผู้ใช้ก็จะมีเพียงผู้ที่เกิดในปี 1984 ให้เพิ่มฟิลด์ที่ต้องการกลับเข้าไป:

Posts.findAll({
  include: [{
    model: User,
    where: {year_birth: 1984}
    required: false,
   }]
}).then(posts => {
  /* ... */
});

หากคุณต้องการให้โพสต์ทั้งหมดที่มีชื่อว่า "Sunshine" และหากเป็นของผู้ใช้ที่เกิดในปี 1984 เท่านั้นคุณจะต้องดำเนินการดังนี้:

Posts.findAll({
  where: {name: "Sunshine"},
  include: [{
    model: User,
    where: {year_birth: 1984}
   }]
}).then(posts => {
  /* ... */
});

หากคุณต้องการให้โพสต์ทั้งหมดที่มีชื่อว่า "Sunshine" และเฉพาะในกรณีที่เป็นของผู้ใช้ที่เกิดในปีเดียวกันซึ่งตรงกับแอตทริบิวต์ post_year ในโพสต์คุณจะต้องดำเนินการดังนี้:

Posts.findAll({
  where: {name: "Sunshine"},
  include: [{
    model: User,
    where: ["year_birth = post_year"]
   }]
}).then(posts => {
  /* ... */
});

ฉันรู้ว่ามันไม่สมเหตุสมผลที่ใครบางคนจะโพสต์ปีที่พวกเขาเกิด แต่มันเป็นแค่ตัวอย่าง - ไปด้วยกัน :)

ฉันพบสิ่งนี้ (ส่วนใหญ่) จากเอกสารนี้:


9
ขอบคุณมากสิ่งนี้มีประโยชน์
นีโอ

6
ใช่คำตอบที่ดี
duxfox--

แล้วอะไรเป็นตัวแทนposts.user_id = users.idของผลสืบเนื่องของคุณ? ขอบคุณ
hanzichi

6
คำตอบนี้ละเอียดถี่ถ้วนมากจึงควรเชื่อมโยงในเอกสารของ Sequelize
Giorgio Andretti

2
บทช่วยสอนที่ยอดเยี่ยมที่ฉันกำลังมองหาขอบคุณมาก
Andrew Rayan


2

ในกรณีของฉันฉันทำสิ่งต่อไปนี้ ใน UserMaster หมายเลขผู้ใช้เป็น PK และ UserAccess หมายเลขผู้ใช้เป็น FK ของ UserMaster

UserAccess.belongsTo(UserMaster,{foreignKey: 'userId'});
UserMaster.hasMany(UserAccess,{foreignKey : 'userId'});
var userData = await UserMaster.findAll({include: [UserAccess]});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.