ทำงานกับรายการข้อมูลบน Android

เอกสารนี้ครอบคลุมการทำงานกับรายการข้อมูลใน Firebase หากต้องการเรียนรู้พื้นฐานของการอ่านและเขียนข้อมูล Firebase โปรดดู อ่านและเขียนข้อมูลบน Android

รับการอ้างอิงฐานข้อมูล

หากต้องการอ่านและเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ของ DatabaseReference :

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

อ่านและเขียนรายการ

ผนวกเข้ากับรายการข้อมูล

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

คุณสามารถใช้การอ้างอิงไปยังข้อมูลใหม่ที่ส่งกลับโดยเมธอด push() เพื่อรับค่าของคีย์ที่สร้างอัตโนมัติของเด็กหรือตั้งค่าข้อมูลสำหรับเด็ก การเรียก getKey() ในการอ้างอิง push() จะคืนค่าของคีย์ที่สร้างขึ้นโดยอัตโนมัติ

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

ฟังกิจกรรมสำหรับเด็ก

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

เหตุการณ์ลูกจะถูกทริกเกอร์เพื่อตอบสนองต่อการดำเนินการเฉพาะที่เกิดขึ้นกับลูกของโหนดจากการดำเนินการ เช่น ลูกใหม่ที่เพิ่มผ่านเมธอด push() หรือลูกที่ได้รับการอัปเดตผ่านเมธอด updateChildren() แต่ละสิ่งเหล่านี้รวมกันจะมีประโยชน์สำหรับการฟังการเปลี่ยนแปลงของโหนดเฉพาะในฐานข้อมูล

หากต้องการฟังเหตุการณ์ลูกบน DatabaseReference ให้แนบ ChildEventListener :

ผู้ฟัง โทรกลับเหตุการณ์ การใช้งานทั่วไป
ChildEventListener onChildAdded() ดึงรายการของรายการหรือฟังการเพิ่มเติมในรายการของรายการ การเรียกกลับนี้จะถูกทริกเกอร์หนึ่งครั้งสำหรับรายการย่อยที่มีอยู่แต่ละรายการ และอีกครั้งทุกครั้งที่มีการเพิ่มรายการย่อยใหม่ในเส้นทางที่ระบุ DataSnapshot ที่ส่งผ่านไปยังผู้ฟังประกอบด้วยข้อมูลของเด็กใหม่
onChildChanged() ฟังการเปลี่ยนแปลงของรายการในรายการ เหตุการณ์นี้เกิดขึ้นเมื่อใดก็ตามที่มีการแก้ไขโหนดลูก รวมถึงการแก้ไขใดๆ ที่เกิดขึ้นกับโหนดย่อย DataSnapshot ที่ส่งผ่านไปยังตัวฟังเหตุการณ์ประกอบด้วยข้อมูลที่อัปเดตสำหรับเด็ก
onChildRemoved() ฟังรายการที่ถูกลบออกจากรายการ DataSnapshot ที่ส่งผ่านไปยังการเรียกกลับเหตุการณ์ประกอบด้วยข้อมูลสำหรับเด็กที่ถูกลบออก
onChildMoved() ฟังการเปลี่ยนแปลงลำดับของรายการในรายการสั่งซื้อ เหตุการณ์นี้จะถูกทริกเกอร์เมื่อใดก็ตามที่การโทรกลับ onChildChanged() ถูกทริกเกอร์โดยการอัปเดตที่ทำให้เกิดการเรียงลำดับของเด็กใหม่ ใช้กับข้อมูลที่เรียงลำดับด้วย orderByChild หรือ orderByValue

ตัวอย่างเช่น แอปบล็อกโซเชียลอาจใช้วิธีการเหล่านี้ร่วมกันเพื่อตรวจสอบกิจกรรมในความคิดเห็นของโพสต์ ดังที่แสดงด้านล่าง:

Kotlin+KTX

val childEventListener = object : ChildEventListener {
    override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!)

        // A new comment has been added, add it to the displayed list
        val comment = dataSnapshot.getValue<Comment>()

        // ...
    }

    override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildChanged: ${dataSnapshot.key}")

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        val newComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildRemoved(dataSnapshot: DataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!)

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!)

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        val movedComment = dataSnapshot.getValue<Comment>()
        val commentKey = dataSnapshot.key

        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException())
        Toast.makeText(
            context,
            "Failed to load comments.",
            Toast.LENGTH_SHORT,
        ).show()
    }
}
databaseReference.addChildEventListener(childEventListener)

Java

ChildEventListener childEventListener = new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

        // A new comment has been added, add it to the displayed list
        Comment comment = dataSnapshot.getValue(Comment.class);

        // ...
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so displayed the changed comment.
        Comment newComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
        Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

        // A comment has changed, use the key to determine if we are displaying this
        // comment and if so remove it.
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
        Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

        // A comment has changed position, use the key to determine if we are
        // displaying this comment and if so move it.
        Comment movedComment = dataSnapshot.getValue(Comment.class);
        String commentKey = dataSnapshot.getKey();

        // ...
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        Log.w(TAG, "postComments:onCancelled", databaseError.toException());
        Toast.makeText(mContext, "Failed to load comments.",
                Toast.LENGTH_SHORT).show();
    }
};
databaseReference.addChildEventListener(childEventListener);

รับฟังเหตุการณ์อันทรงคุณค่า

แม้ว่าการใช้ ChildEventListener เป็นวิธีที่แนะนำในการอ่านรายการข้อมูล แต่ก็มีสถานการณ์ที่การแนบ ValueEventListener เข้ากับการอ้างอิงรายการจะมีประโยชน์

การแนบ ValueEventListener เข้ากับรายการข้อมูลจะส่งคืนรายการข้อมูลทั้งหมดเป็น DataSnapshot เดียว ซึ่งคุณสามารถวนซ้ำเพื่อเข้าถึงรายการย่อยแต่ละรายการได้

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

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
})

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

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

แยกผู้ฟังออก

การเรียกกลับจะถูกลบออกโดยการเรียกเมธอด removeEventListener() ในการอ้างอิงฐานข้อมูล Firebase ของคุณ

หากมีการเพิ่ม Listener หลายครั้งในตำแหน่งข้อมูล ระบบจะเรียกหลายครั้งสำหรับแต่ละเหตุการณ์ และคุณต้องแยกออกในจำนวนเท่าเดิมจึงจะลบออกทั้งหมดได้

การเรียก removeEventListener() บน Listener หลักจะไม่ลบ Listener ที่ลงทะเบียนบนโหนดลูกโดยอัตโนมัติ นอกจากนี้ จะต้องเรียกใช้ removeEventListener() บน Listener ระดับย่อยเพื่อลบการโทรกลับ

การเรียงลำดับและการกรองข้อมูล

คุณสามารถใช้คลาส Realtime Database Query เพื่อดึงข้อมูลที่จัดเรียงตามคีย์ ตามค่า หรือตามค่าของรายการย่อย คุณยังสามารถกรองผลลัพธ์ที่จัดเรียงตามจำนวนผลลัพธ์หรือช่วงของคีย์หรือค่าที่ต้องการได้

จัดเรียงข้อมูล

หากต้องการดึงข้อมูลที่เรียงลำดับ ให้เริ่มต้นด้วยการระบุวิธีเรียงลำดับตามวิธีใดวิธีหนึ่งเพื่อกำหนดวิธีเรียงลำดับผลลัพธ์:

วิธี การใช้งาน
orderByChild() เรียงลำดับผลลัพธ์ตามค่าของคีย์ลูกที่ระบุหรือเส้นทางลูกที่ซ้อนกัน
orderByKey() เรียงลำดับผลลัพธ์ตามคีย์ลูก
orderByValue() เรียงลำดับผลลัพธ์ตามค่าลูก

คุณสามารถใช้วิธีสั่งซื้อได้ครั้งละ หนึ่ง วิธีเท่านั้น การเรียกวิธีการเรียงลำดับหลายครั้งในแบบสอบถามเดียวกันทำให้เกิดข้อผิดพลาด

ตัวอย่างต่อไปนี้สาธิตวิธีที่คุณสามารถดึงรายการโพสต์ยอดนิยมของผู้ใช้โดยจัดเรียงตามจำนวนดาว:

Kotlin+KTX

// My top posts by number of stars
val myUserId = uid
val myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
    .orderByChild("starCount")

myTopPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Java

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
        .orderByChild("starCount");
myTopPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

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

การเรียกเมธอด orderByChild() ระบุคีย์ลูกเพื่อเรียงลำดับผลลัพธ์ตาม ในกรณีนี้ โพสต์จะถูกจัดเรียงตามค่าของรายการย่อย "starCount" ที่เกี่ยวข้อง การสืบค้นสามารถเรียงลำดับตามรายการย่อยที่ซ้อนกันได้ ในกรณีที่คุณมีข้อมูลที่มีลักษณะดังนี้:

"posts": {
  "ts-functions": {
    "metrics": {
      "views" : 1200000,
      "likes" : 251000,
      "shares": 1200,
    },
    "title" : "Why you should use TypeScript for writing Cloud Functions",
    "author": "Doug",
  },
  "android-arch-3": {
    "metrics": {
      "views" : 900000,
      "likes" : 117000,
      "shares": 144,
    },
    "title" : "Using Android Architecture Components with Firebase Realtime Database (Part 3)",
    "author": "Doug",
  }
},

ในตัวอย่างนี้ เราสามารถเรียงลำดับองค์ประกอบรายการของเราตามค่าที่ซ้อนอยู่ใต้ metrics โดยการระบุเส้นทางสัมพันธ์ไปยังรายการย่อยที่ซ้อนกันในการเรียก orderByChild() ของเรา

Kotlin+KTX

// Most viewed posts
val myMostViewedPostsQuery = databaseReference.child("posts")
    .orderByChild("metrics/views")
myMostViewedPostsQuery.addChildEventListener(object : ChildEventListener {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
})

Java

// Most viewed posts
Query myMostViewedPostsQuery = databaseReference.child("posts")
        .orderByChild("metrics/views");
myMostViewedPostsQuery.addChildEventListener(new ChildEventListener() {
    // TODO: implement the ChildEventListener methods as documented above
    // ...
});

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีการเรียงลำดับข้อมูลประเภทอื่นๆ โปรดดู วิธีการเรียงลำดับข้อมูลคิวรี

การกรองข้อมูล

หากต้องการกรองข้อมูล คุณสามารถรวมวิธีการจำกัดหรือช่วงใดๆ เข้ากับวิธีเรียงลำดับตามเมื่อสร้างแบบสอบถามได้

วิธี การใช้งาน
limitToFirst() ตั้งค่าจำนวนรายการสูงสุดที่จะส่งคืนจากจุดเริ่มต้นของรายการผลลัพธ์ที่เรียงลำดับ
limitToLast() ตั้งค่าจำนวนรายการสูงสุดที่จะส่งคืนจากจุดสิ้นสุดของรายการผลลัพธ์ที่เรียงลำดับ
startAt() ส่งคืนรายการที่มากกว่าหรือเท่ากับคีย์หรือค่าที่ระบุ ขึ้นอยู่กับวิธีการเรียงลำดับที่เลือก
startAfter() ส่งคืนรายการที่มากกว่าคีย์หรือค่าที่ระบุ ขึ้นอยู่กับวิธีการเรียงลำดับที่เลือก
endAt() ส่งคืนรายการที่น้อยกว่าหรือเท่ากับคีย์หรือค่าที่ระบุ ขึ้นอยู่กับวิธีการเรียงลำดับที่เลือก
endBefore() ส่งคืนรายการที่น้อยกว่าคีย์หรือค่าที่ระบุ ขึ้นอยู่กับวิธีการเรียงลำดับที่เลือก
equalTo() ส่งคืนสินค้าเท่ากับคีย์หรือค่าที่ระบุ ขึ้นอยู่กับวิธีการเรียงลำดับที่เลือก

ต่างจากวิธีการเรียงลำดับตาม คุณสามารถรวมฟังก์ชันขีดจำกัดหรือช่วงหลายรายการเข้าด้วยกันได้ ตัวอย่างเช่น คุณสามารถรวมเมธอด startAt() และ endAt() เพื่อจำกัดผลลัพธ์ให้อยู่ในช่วงของค่าที่ระบุ

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

Kotlin+KTX

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        for (postSnapshot in dataSnapshot.children) {
            // TODO: handle the post
        }
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
        // ...
    }
})

Java

// My top posts by number of stars
myTopPostsQuery.addValueEventListener(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        for (DataSnapshot postSnapshot: dataSnapshot.getChildren()) {
            // TODO: handle the post
        }
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
        // ...
    }
});

จำกัดจำนวนผลลัพธ์

คุณสามารถใช้เมธอด limitToFirst() และ limitToLast() เพื่อตั้งค่าจำนวนลูกสูงสุดที่จะซิงค์สำหรับการโทรกลับที่กำหนด ตัวอย่างเช่น หากคุณใช้ limitToFirst() เพื่อกำหนดขีดจำกัดไว้ที่ 100 ในตอนแรกคุณจะได้รับการเรียกกลับ onChildAdded() สูงสุด 100 ครั้งเท่านั้น หากคุณมีรายการจัดเก็บน้อยกว่า 100 รายการในฐานข้อมูล Firebase ของคุณ การเรียกกลับ onChildAdded() จะเริ่มทำงานสำหรับแต่ละรายการ

เมื่อรายการเปลี่ยนแปลง คุณจะได้รับการโทรกลับ onChildAdded() สำหรับรายการที่ป้อนแบบสอบถาม และการเรียกกลับ onChildRemoved() สำหรับรายการที่ออกจากรายการ เพื่อให้จำนวนทั้งหมดยังคงอยู่ที่ 100

ตัวอย่างต่อไปนี้สาธิตวิธีที่แอปบล็อกตัวอย่างกำหนดการสืบค้นเพื่อดึงรายการโพสต์ล่าสุด 100 รายการโดยผู้ใช้ทั้งหมด:

Kotlin+KTX

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys.
databaseReference.child("posts").limitToFirst(100)

Java

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
        .limitToFirst(100);

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

กรองตามคีย์หรือค่า

คุณสามารถใช้ startAt() , startAfter() , endAt() , endBefore() และ equalTo() เพื่อเลือกจุดเริ่มต้น จุดสิ้นสุด และจุดเทียบเท่าตามต้องการสำหรับการสืบค้น สิ่งนี้มีประโยชน์สำหรับการแบ่งหน้าข้อมูลหรือค้นหารายการที่มีรายการลูกที่มีค่าเฉพาะ

วิธีเรียงลำดับข้อมูลการสืบค้น

ในส่วนนี้จะอธิบายวิธีการจัดเรียงข้อมูลตามวิธีเรียงลำดับตามแต่ละวิธีในคลาส Query

orderByChild

เมื่อใช้ orderByChild() ข้อมูลที่มีคีย์ลูกที่ระบุจะถูกเรียงลำดับดังนี้:

  1. รายการย่อยที่มีค่า null สำหรับคีย์ลูกที่ระบุจะต้องมาก่อน
  2. รายการย่อยที่มีค่า false สำหรับคีย์ลูกที่ระบุจะอยู่ลำดับถัดไป หากลูกหลายคนมีค่าเป็น false พวกเขาจะถูกจัดเรียงตาม พจนานุกรม ตามคีย์
  3. รายการย่อยที่มีค่า true สำหรับรหัสลูกที่ระบุจะอยู่ลำดับถัดไป หากรายการย่อยหลายรายการมีค่าเป็น true รายการเหล่านั้นจะถูกจัดเรียงตามพจนานุกรมตามคีย์
  4. ลูกที่มีค่าตัวเลขจะมาถัดไป เรียงลำดับจากน้อยไปหามาก ถ้าลูกหลายคนมีค่าตัวเลขเหมือนกันสำหรับโหนดลูกที่ระบุ พวกเขาจะถูกจัดเรียงตามคีย์
  5. สตริงจะอยู่หลังตัวเลข และจัดเรียงตามพจนานุกรมจากน้อยไปหามาก หากลูกหลายคนมีค่าเดียวกันสำหรับโหนดลูกที่ระบุ พวกเขาจะถูกเรียงลำดับตามพจนานุกรมตามคีย์
  6. วัตถุจะอยู่ลำดับสุดท้ายและจัดเรียงตามพจนานุกรมตามคีย์จากน้อยไปหามาก

orderByKey

เมื่อใช้ orderByKey() เพื่อจัดเรียงข้อมูล ข้อมูลจะถูกส่งกลับโดยเรียงจากน้อยไปมากตามคีย์

  1. รายการย่อยที่มีคีย์ที่สามารถแยกวิเคราะห์เป็นจำนวนเต็ม 32 บิตมาก่อน โดยเรียงลำดับจากน้อยไปหามาก
  2. รายการย่อยที่มีค่าสตริงเป็นคีย์จะอยู่ลำดับถัดไป โดยเรียงลำดับตามพจนานุกรมจากน้อยไปหามาก

orderByValue

เมื่อใช้ orderByValue() ลูกจะถูกเรียงลำดับตามค่าของพวกเขา เกณฑ์การเรียงลำดับจะเหมือนกับใน orderByChild() ยกเว้นว่าจะใช้ค่าของโหนดแทนค่าของคีย์ลูกที่ระบุ

ขั้นตอนถัดไป