เอกสารนี้ครอบคลุมการทำงานกับรายการข้อมูลใน Firebase หากต้องการเรียนรู้ พื้นฐานของการอ่านและการเขียนข้อมูล Firebase โปรดดู อ่านและเขียนข้อมูลใน Android
รับ DatabaseReference
หากต้องการอ่านและเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ของ DatabaseReference ดังนี้
Kotlin
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 ดังนี้
| Listener | การเรียกกลับของเหตุการณ์ | การใช้งานทั่วไป |
|---|---|---|
ChildEventListener
| onChildAdded() |
ดึงข้อมูลรายการหรือฟังการเพิ่มลงในรายการ
การเรียกกลับนี้จะทริกเกอร์ 1 ครั้งสำหรับรายการย่อยที่มีอยู่แต่ละรายการ จากนั้นจะทริกเกอร์อีกครั้ง
ทุกครั้งที่มีการเพิ่มรายการย่อยใหม่ลงในเส้นทางที่ระบุ `DataSnapshot` ที่ส่งไปยัง Listener จะมีข้อมูลของรายการย่อยใหม่
|
onChildChanged() |
ฟังการเปลี่ยนแปลงของรายการในรายการ เหตุการณ์นี้จะเริ่มทำงานทุกครั้งที่มีการแก้ไขโหนดรายการย่อย รวมถึงการแก้ไขรายการย่อยของโหนดรายการย่อย DataSnapshot ที่ส่งไปยัง Listener ของเหตุการณ์จะมีข้อมูลที่อัปเดตของรายการย่อย
|
|
onChildRemoved() |
ฟังการนำรายการออกจากรายการ
ที่ส่งไปยังการเรียกกลับของเหตุการณ์จะมีข้อมูลของรายการย่อยที่นำออกDataSnapshot
|
|
onChildMoved() |
ฟังการเปลี่ยนแปลงลำดับของรายการในรายการที่เรียงลำดับ
เหตุการณ์นี้จะทริกเกอร์ทุกครั้งที่ onChildChanged()
การเรียกกลับทริกเกอร์โดยการอัปเดตที่ทำให้มีการจัดลำดับรายการย่อยใหม่
โดยจะใช้กับข้อมูลที่เรียงลำดับด้วย orderByChild หรือ orderByValue
|
ตัวอย่างเช่น แอปบล็อกโซเชียลอาจใช้วิธีการเหล่านี้ร่วมกันเพื่อตรวจสอบกิจกรรมในความคิดเห็นของโพสต์ ดังที่แสดงด้านล่าง
Kotlin
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 รายการเดียว ซึ่งคุณสามารถวนซ้ำเพื่อเข้าถึงรายการย่อยแต่ละรายการได้
แม้ว่าจะมีรายการเดียวที่ตรงกับคำค้นหา แต่ Snapshot ก็ยังคงเป็นรายการ โดยจะมีเพียงรายการเดียว หากต้องการเข้าถึงรายการ คุณต้องวนซ้ำผลลัพธ์ดังนี้
Kotlin
// 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 เพิ่มเติม
ยกเลิกการแนบ Listener
ระบบจะนำการเรียกกลับออกโดยการเรียกเมธอด removeEventListener() ในการอ้างอิงฐานข้อมูล Firebase
หากมีการเพิ่ม Listener ลงในตำแหน่งข้อมูลหลายครั้ง ระบบจะเรียก Listener หลายครั้งสำหรับแต่ละเหตุการณ์ และคุณต้องยกเลิกการแนบ Listener จำนวนครั้งเท่ากันเพื่อนำออกอย่างสมบูรณ์
การเรียก removeEventListener() ใน Listener หลักจะไม่นำ Listener ที่ลงทะเบียนในโหนดรายการย่อยออกโดยอัตโนมัติ คุณต้องเรียก removeEventListener() ใน Listener ของรายการย่อยด้วยเพื่อนำการเรียกกลับออก
การจัดเรียงและการกรองข้อมูล
คุณสามารถใช้คลาส Realtime Database Query เพื่อดึงข้อมูลที่จัดเรียงตาม
คีย์ ตามค่า หรือตามค่าของรายการย่อย นอกจากนี้ คุณยังกรองผลลัพธ์ที่จัดเรียงแล้วให้แสดงผลลัพธ์ตามจำนวนที่เฉพาะเจาะจงหรือช่วงของคีย์หรือค่าได้ด้วย
จัดเรียงข้อมูล
หากต้องการดึงข้อมูลที่จัดเรียงแล้ว ให้เริ่มต้นด้วยการระบุเมธอดการจัดเรียงตามรายการใดรายการหนึ่งเพื่อกำหนดวิธีจัดเรียงผลลัพธ์
| วิธีการ | การใช้งาน |
|---|---|
orderByChild() |
จัดเรียงผลลัพธ์ตามค่าของคีย์รายการย่อยหรือเส้นทางรายการย่อยที่ซ้อนกันที่ระบุ |
orderByKey()
| จัดเรียงผลลัพธ์ตามคีย์รายการย่อย |
orderByValue() |
จัดเรียงผลลัพธ์ตามค่ารายการย่อย |
คุณใช้เมธอดการจัดเรียงตามได้เพียงรายการเดียว ในแต่ละครั้ง การเรียกเมธอดการจัดเรียงตามหลายครั้งในการค้นหาเดียวกันจะทำให้เกิดข้อผิดพลาด
ตัวอย่างต่อไปนี้แสดงวิธีดึงข้อมูลรายการโพสต์ยอดนิยมของผู้ใช้ที่จัดเรียงตามจำนวนดาว
Kotlin
// 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 // ... });
ตัวอย่างนี้กำหนดการค้นหาที่เมื่อรวมกับ Listener ของรายการย่อย แล้ว จะซิงค์ไคลเอ็นต์กับโพสต์ของผู้ใช้จากเส้นทางในฐานข้อมูล ตามรหัสผู้ใช้ โดยจัดเรียงตามจำนวนดาวที่แต่ละโพสต์ได้รับ เทคนิคการใช้รหัสเป็นคีย์ดัชนีนี้เรียกว่าการกระจายข้อมูล คุณสามารถอ่าน ข้อมูลเพิ่มเติมเกี่ยวกับเทคนิคนี้ได้ใน หัวข้อการจัดโครงสร้างฐานข้อมูล
การเรียกเมธอด 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
// 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() เพื่อจำกัดผลลัพธ์ให้อยู่ในช่วงค่าที่ระบุ
แม้ว่าจะมีรายการเดียวที่ตรงกับคำค้นหา แต่ Snapshot ก็ยังคงเป็นรายการ โดยจะมีเพียงรายการเดียว หากต้องการเข้าถึงรายการ คุณต้องวนซ้ำผลลัพธ์ดังนี้
Kotlin
// 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 รายการในตอนแรก หากคุณเก็บรายการไว้ในฐานข้อมูล Firebase น้อยกว่า 100 รายการ การเรียกกลับ onChildAdded() จะเริ่มทำงานสำหรับแต่ละรายการ
เมื่อรายการมีการเปลี่ยนแปลง คุณจะได้รับการเรียกกลับ onChildAdded() สำหรับรายการที่เข้าสู่การค้นหา และการเรียกกลับ onChildRemoved() สำหรับรายการที่ออกจากผลการค้นหา เพื่อให้จำนวนรวมยังคงอยู่ที่ 100 รายการ
ตัวอย่างต่อไปนี้แสดงวิธีที่แอปบล็อกตัวอย่างกำหนดการค้นหาเพื่อดึงข้อมูลรายการโพสต์ล่าสุด 100 รายการจากผู้ใช้ทั้งหมด
Kotlin
// 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() ระบบจะจัดเรียงข้อมูลที่มีคีย์รายการย่อยที่ระบุไว้ดังนี้
- รายการย่อยที่มีค่า
nullสำหรับคีย์รายการย่อยที่ระบุจะแสดง ก่อน - รายการย่อยที่มีค่า
falseสำหรับคีย์รายการย่อยที่ระบุ จะแสดงถัดไป หากรายการย่อยหลายรายการมีค่าfalseระบบจะจัดเรียงรายการย่อยเหล่านั้น ตามลำดับพจนานุกรมตามคีย์ - รายการย่อยที่มีค่า
trueสำหรับคีย์รายการย่อยที่ระบุ จะแสดงถัดไป หากรายการย่อยหลายรายการมีค่าtrueระบบจะจัดเรียงรายการย่อยเหล่านั้น ตามลำดับพจนานุกรมตามคีย์ - รายการย่อยที่มีค่าเป็นตัวเลขจะแสดงถัดไป โดยจัดเรียงตามลำดับจากน้อยไปมาก หากรายการย่อยหลายรายการมีค่าตัวเลขเดียวกันสำหรับโหนดรายการย่อยที่ระบุ ระบบจะจัดเรียงรายการย่อยเหล่านั้นตามคีย์
- สตริงจะแสดงหลังตัวเลขและจัดเรียงตามลำดับพจนานุกรมจากน้อยไปมาก หากรายการย่อยหลายรายการมีค่าเดียวกันสำหรับโหนดรายการย่อยที่ระบุ ระบบจะจัดเรียงรายการย่อยเหล่านั้นตามลำดับพจนานุกรมตามคีย์
- ออบเจ็กต์จะแสดงเป็นรายการสุดท้ายและจัดเรียงตามลำดับพจนานุกรมตามคีย์จากน้อยไปมาก
orderByKey
เมื่อใช้ orderByKey() เพื่อจัดเรียงข้อมูล ระบบจะแสดงผลข้อมูลตามลำดับจากน้อยไปมากตามคีย์
- รายการย่อยที่มีคีย์ที่แยกวิเคราะห์เป็นจำนวนเต็ม 32 บิตได้จะแสดงก่อน โดยจัดเรียงตามลำดับจากน้อยไปมาก
- รายการย่อยที่มีค่าสตริงเป็นคีย์จะแสดงถัดไป โดยจัดเรียงตามลำดับพจนานุกรมจากน้อยไปมาก
orderByValue
เมื่อใช้ orderByValue() ระบบจะจัดเรียงรายการย่อยตามค่า เกณฑ์การจัดเรียงจะเหมือนกับใน orderByChild() ยกเว้นว่าจะใช้ค่าของโหนดแทนค่าของคีย์รายการย่อยที่ระบุ