{// This is a poorly nested data architecture, because iterating the children// of the "chats" node to get a list of conversation titles requires// potentially downloading hundreds of megabytes of messages"chats":{"one":{"title":"Historical Tech Pioneers","messages":{"m1":{"sender":"ghopper","message":"Relay malfunction found. Cause: moth."},"m2":{...},// a very long list of messages}},"two":{"..."}}}
{// Chats contains only meta info about each conversation// stored under the chats's unique ID"chats":{"one":{"title":"Historical Tech Pioneers","lastMessage":"ghopper: Relay malfunction found. Cause: moth.","timestamp":1459361875666},"two":{"..."},"three":{"..."}},// Conversation members are easily accessible// and stored by chat conversation ID"members":{// we'll talk about indices like this below"one":{"ghopper":true,"alovelace":true,"eclarke":true},"two":{"..."},"three":{"..."}},// Messages are separate from data we may want to iterate quickly// but still easily paginated and queried, and organized by chat// conversation ID"messages":{"one":{"m1":{"name":"eclarke","message":"The relay seems to be malfunctioning.","timestamp":1459361875337},"m2":{"..."},"m3":{"..."}},"two":{"..."},"three":{"..."}}}
// An index to track Ada's memberships{"users":{"alovelace":{"name":"Ada Lovelace",// Index Ada's groups in her profile"groups":{// the value here doesn't matter, just that the key exists"techpioneers":true,"womentechmakers":true}},// ...},"groups":{"techpioneers":{"name":"Historical Tech Pioneers","members":{"alovelace":true,"ghopper":true,"eclarke":true}},// ...}}
您可能會發現,這會將關係儲存在 Ada 的記錄和群組中,導致部分資料重複。現在 alovelace 會在群組下建立索引,而 techpioneers 會列在 Ada 的個人資料中。因此,如要從群組中刪除 Ada,必須在兩個地方更新。
雙向關係必須有這項備援機制。即使使用者或群組清單擴展到數百萬筆,或Realtime Database安全規則禁止存取部分記錄,您也能快速有效地擷取 Ada 的成員資格。
這種方法會反轉資料,將 ID 列為鍵,並將值設為 true,因此檢查鍵就像讀取 /users/$uid/groups/$group_id 並檢查是否為 null 一樣簡單。與查詢或掃描資料相比,索引的速度更快,效率也高出許多。
[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-08-16 (世界標準時間)。"],[],[],null,["\u003cbr /\u003e\n\nStructuring Data\n\nThis guide covers some of the key concepts in data architecture and best\npractices for structuring the JSON data in your Firebase Realtime Database.\n\nBuilding a properly structured database requires quite a bit of forethought.\nMost importantly, you need to plan for how data is going to be saved and\nlater retrieved to make that process as easy as possible.\n\nHow data is structured: it's a JSON tree\n\nAll Firebase Realtime Database data is stored as JSON objects. You can think of\nthe database as a cloud-hosted JSON tree. Unlike a SQL database, there are no\ntables or records. When you add data to the JSON tree, it becomes a node in the\nexisting JSON structure with an associated key. You can provide your own keys,\nsuch as user IDs or semantic names, or they can be provided for you using\nthe `Push()` method.\n\nIf you create your own keys, they must be UTF-8 encoded, can be a maximum\nof 768 bytes, and cannot contain `.`, `$`, `#`, `[`, `]`, `/`, or ASCII control\ncharacters 0-31 or 127. You cannot use ASCII control characters in the values\nthemselves, either.\n\nFor example, consider a chat application that allows users to store a basic\nprofile and contact list. A typical user profile is located at a path, such as\n`/users/$uid`. The user `alovelace` might have a database entry that\nlooks something like this: \n\n```json\n{\n \"users\": {\n \"alovelace\": {\n \"name\": \"Ada Lovelace\",\n \"contacts\": { \"ghopper\": true },\n },\n \"ghopper\": { \"...\" },\n \"eclarke\": { \"...\" }\n }\n}\n```\n\nAlthough the database uses a JSON tree, data stored in the database can be\nrepresented as certain native types that correspond to available JSON types\nto help you write more maintainable code.\n\nBest practices for data structure\n\nAvoid nesting data\n\nBecause the Firebase Realtime Database allows nesting data up to 32 levels deep,\nyou might be tempted to think that this should be the default structure.\nHowever, when you fetch data at a location in your database, you also retrieve\nall of its child nodes. In addition, when you grant someone read or write access\nat a node in your database, you also grant them access to all data under that\nnode. Therefore, in practice, it's best to keep your data structure as flat\nas possible.\n\nFor an example of why nested data is bad, consider the following\nmultiply-nested structure: \n\n```json\n{\n // This is a poorly nested data architecture, because iterating the children\n // of the \"chats\" node to get a list of conversation titles requires\n // potentially downloading hundreds of megabytes of messages\n \"chats\": {\n \"one\": {\n \"title\": \"Historical Tech Pioneers\",\n \"messages\": {\n \"m1\": { \"sender\": \"ghopper\", \"message\": \"Relay malfunction found. Cause: moth.\" },\n \"m2\": { ... },\n // a very long list of messages\n }\n },\n \"two\": { \"...\" }\n }\n}\n```\n\nWith this nested design, iterating through the data becomes problematic. For\nexample, listing the titles of chat conversations requires the entire `chats`\ntree, including all members and messages, to be downloaded to the client.\n\nFlatten data structures\n\nIf the data is instead split into separate paths, also called denormalization,\nit can be efficiently downloaded in separate calls, as it is needed. Consider\nthis flattened structure: \n\n```json\n{\n // Chats contains only meta info about each conversation\n // stored under the chats's unique ID\n \"chats\": {\n \"one\": {\n \"title\": \"Historical Tech Pioneers\",\n \"lastMessage\": \"ghopper: Relay malfunction found. Cause: moth.\",\n \"timestamp\": 1459361875666\n },\n \"two\": { \"...\" },\n \"three\": { \"...\" }\n },\n\n // Conversation members are easily accessible\n // and stored by chat conversation ID\n \"members\": {\n // we'll talk about indices like this below\n \"one\": {\n \"ghopper\": true,\n \"alovelace\": true,\n \"eclarke\": true\n },\n \"two\": { \"...\" },\n \"three\": { \"...\" }\n },\n\n // Messages are separate from data we may want to iterate quickly\n // but still easily paginated and queried, and organized by chat\n // conversation ID\n \"messages\": {\n \"one\": {\n \"m1\": {\n \"name\": \"eclarke\",\n \"message\": \"The relay seems to be malfunctioning.\",\n \"timestamp\": 1459361875337\n },\n \"m2\": { \"...\" },\n \"m3\": { \"...\" }\n },\n \"two\": { \"...\" },\n \"three\": { \"...\" }\n }\n}\n```\n\nIt's now possible to iterate through the list of rooms by downloading only a\nfew bytes per conversation, quickly fetching metadata for listing or displaying\nrooms in a UI. Messages can be fetched separately and displayed as they arrive,\nallowing the UI to stay responsive and fast.\n\nCreate data that scales\n\nWhen building apps, it's often better to download a subset of a list.\nThis is particularly common if the list contains thousands of records.\nWhen this relationship is static and one-directional, you can simply nest the\nchild objects under the parent.\n\nSometimes, this relationship is more dynamic, or it may be necessary to\ndenormalize this data. Many times you can denormalize the data by using a query\nto retrieve a subset of the data, as discussed in\n[Retrieve Data](/docs/database/cpp/retrieve-data).\n\nBut even this may be insufficient. Consider, for example, a two-way relationship\nbetween users and groups. Users can belong to a group, and groups comprise a\nlist of users. When it comes time to decide which groups a user belongs to,\nthings get complicated.\n\nWhat's needed is an elegant way to list the groups a user belongs to and\nfetch only data for those groups. An *index* of groups can help a\ngreat deal here: \n\n```json\n// An index to track Ada's memberships\n{\n \"users\": {\n \"alovelace\": {\n \"name\": \"Ada Lovelace\",\n // Index Ada's groups in her profile\n \"groups\": {\n // the value here doesn't matter, just that the key exists\n \"techpioneers\": true,\n \"womentechmakers\": true\n }\n },\n // ...\n },\n \"groups\": {\n \"techpioneers\": {\n \"name\": \"Historical Tech Pioneers\",\n \"members\": {\n \"alovelace\": true,\n \"ghopper\": true,\n \"eclarke\": true\n }\n },\n // ...\n }\n}\n```\n\nYou might notice that this duplicates some data by storing the relationship\nunder both Ada's record and under the group. Now `alovelace` is indexed under a\ngroup, and `techpioneers` is listed in Ada's profile. So to delete Ada\nfrom the group, it has to be updated in two places.\n\nThis is a necessary redundancy for two-way relationships. It allows you to\nquickly and efficiently fetch Ada's memberships, even when the list of users or\ngroups scales into the millions or when Realtime Database security rules\nprevent access to some of the records.\n\nThis approach, inverting the data by listing the IDs as keys and setting the\nvalue to true, makes checking for a key as simple as reading\n`/users/$uid/groups/$group_id` and checking if it is `null`. The index is faster\nand a good deal more efficient than querying or scanning the data.\n\nNext Steps\n\n- [Save data to your Realtime Database](/docs/database/cpp/save-data)\n- [Retrieve data from your Realtime Database](/docs/database/cpp/retrieve-data)"]]