The documentation does not have any examples on how to add a subcollection to a document. I know how to add document to a collection and how to add data to a document, but how do I add a collection (subcollection) to a document?
Shouldn't there be some method like this:
dbRef.document("example").addCollection("subCollection")
Edit 13 Jan 2021:
According to the updated documentation regarding array membership, now it is possible to filter data based on array values using whereArrayContains() method. A simple example would be:
CollectionReference citiesRef = db.collection("cities");
citiesRef.whereArrayContains("regions", "west_coast");
This query returns every city document where the regions field is an array that contains west_coast. If the array has multiple instances of the value you query on, the document is included in the results only once.
Assuming we have a chat application that has a database structure that looks similar to this:
To write a subCollection in a document, please use the following code:
DocumentReference messageRef = db
.collection("rooms").document("roomA")
.collection("messages").document("message1");
Creating a messages collection and calling addDocument() 1000 times will be expensive for sure, but this is how Firestore works. You can switch to Firebase Realtime Database if you want where the number of writes doesn't matter. But regarding Supported Data Types in Firestore, in fact, you can use an array because it is supported. In Firebase Realtime database you could also use an array, but this is an anti-pattern. One of the many reasons Firebase recommends against using arrays is that it makes the security rules impossible to write.
Cloud Firestore can store arrays, but it does not support querying array members or updating single array elements. However, you can still model this kind of data by leveraging the other capabilities of the Cloud Firestore. Here is the documentation where it is very well explained.
You also cannot create a subcollection with 1000 messages, add all of them to the database, and expect it to be considered a single record. It will be considered one write operation for every message, in total 1000 operations. The picture above does not show how to retrieve data, it shows a database structure in which you have something like this:
collection -> document -> subCollection -> document
Here's a variation where the subcollection is storing ID values at the collection level, rather than within a document where the subcollection is a field there with additional data.
This is useful for connecting a 1-to-Many ID mapping w/out having to drill through an additional document:
function fireAddStudentToClassroom(studentUserId, classroomId) {
var db = firebase.firestore();
var studentsClassroomRef =
db.collection('student_class').doc(classroomId)
.collection('students');
studentsClassroomRef
.doc(studentUserId)
.set({})
.then(function () {
console.log('Document Added ');
})
.catch(function (error) {
console.error('Error adding document: ', error);
});
}
Thanks to #Alex's answer
This answer a bit off from the original question here, where it explicitly asks for adding a collection to a document. However, after searching for a solution for this scenario and not finding any mention in docs or on SO, this post seems like a reasonable place to share the findings
Here's my code:
firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).set(wordData)
.then(function() {
console.log("Collection added to Firestore!");
var promises = [];
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('AudioSources').doc($scope.accentDialect).set(accentDialectObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('FunFacts').doc($scope.longLanguage).set(funFactObject));
promises.push(firebase.firestore().collection($scope.longLanguage + 'Words').doc($scope.word).collection('Translations').doc($scope.translationLongLanguage).set(translationObject));
Promise.all(promises).then(function() {
console.log("All subcollections were added!");
})
.catch(function(error){
console.log("Error adding subcollections to Firestore: " + error);
});
})
.catch(function(error){
console.log("Error adding document to Firestore: " + error);
});
This makes a collection EnglishWords, which has a document of. The document of has three subcollections: AudioSources (recordings of the word in American and British accents), FunFacts, and Translations. The subcollection Translations has one document: Spanish. The Spanish document has three key-value pairs, telling you that 'de' is the Spanish translation of 'of'.
The first line of the code creates the collection EnglishWords. We wait for the promise to resolve with .then, and then we create the three subcollections. Promise.all tells us when all three subcollections are set.
IMHO, I use arrays in Firestore when the entire array is uploaded and downloaded together, i.e., I don't need to access individual elements. For example, an array of the letters of the word 'of' would be ['o', 'f']. The user can ask, "How do I spell 'of'?" The user isn't going to ask, "What's the second letter in 'of'?"
I use collections when I need to access individual elements, a.k.a. documents. With the older Firebase Realtime Database, I had to download arrays and then iterate through the arrays with forEach to get the element I wanted. This was a lot of code, and with a deep data structure and/or large arrays I was downloading tons of data that I didn't need, and slowing my app running forEach loops on large arrays. Firestore puts the iterators in the database, on their end, so that I can request a single element and it sends me just that element, saving me bandwidth and making my app run faster. This might not matter for a web app, if your computer has a broadband connection, but for mobile apps with poor data connections and slow devices this is important.
Here are two pictures of my Firestore:
From the docs:
You do not need to "create" or "delete" collections. After you create the first document in a collection, the collection exists. If you delete all of the documents in a collection, it no longer exists.
Here i faced the same issue and solve with the answere of #Thomas David Kehoe
db.collection("First collection Name").doc("Id of the document").collection("Nested collection Name").add({
//your data
}).then((data) => {
console.log(data.id);
console.log("Document has added")
}).catch((err) => {
console.log(err)
})
too late for an answer but here is what worked for me,
mFirebaseDatabaseReference?.collection("conversations")?.add(Conversation("User1"))
?.addOnSuccessListener { documentReference ->
Log.d(TAG, "DocumentSnapshot written with ID: " + documentReference.id)
mFirebaseDatabaseReference?.collection("conversations")?.document(documentReference.id)?.collection("messages")?.add(Message(edtMessage?.text.toString()))
}?.addOnFailureListener { e ->
Log.w(TAG, "Error adding document", e)
}
add success listener for adding document and use firebase generated ID for a path.
Use this ID for the complete path for a new collection you want to add.
I.E. - dbReference.collection('yourCollectionName').document(firebaseGeneratedID).collection('yourCollectionName').add(yourDocumentPOJO/Object)
Okay so I recently faced a similar problem given the recent update in the firebase/firestore documentation.
And here is a solution that worked for me
const sendMessage = async () => {
await setDoc(doc(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME, nanoid()), {
text:'this is a sample text',
createdAt: serverTimestamp(),
name: currentUser?.firstName + ' ' + currentUser?.lastName,
photoUrl: currentUser?.photoUrl,
userId: currentUser?.id,
});
}
You can find a similar example in the docs
https://firebase.google.com/docs/firestore/data-model#web-version-9_3
chat room
If you wish to listen for live update you can use a similar method as follows
const messagesRef = collection(db, COLLECTION_NAME, projectId, SUB_COLLECTION_NAME)
const liveUpdate = async () => {
const queryObj = query(messagesRef, orderBy("createdAt"), limit(25));
onSnapshot(queryObj, (querySnapshot) => {
const msgArr: any = [];
querySnapshot.forEach((doc) => {
msgArr.push({ id: doc.id, ...doc.data() })
});
console.log(msgArr);
});
}
There is no separate method to add sub-collection into the document.
You can just call the collection method itself.
If the collection exists it will reference that otherwise create a new one.
dbRef.document("example").collection("subCollection")
I want to write a specific query for my Hyperledger Composer app. Below, I have 2 assets and a transaction. Asset1 has a field called contents, which is an array of type Asset2. The associated code is below:
namespace org.acme.biznet
asset Asset1 identified by Asset1Id {
o String Asset1Id
--> Asset2[] contents
}
asset Asset2 identified by Asset2Id {
o String Asset2Id
}
transaction Transact {
--> Asset1 asset1
}
I want to select all instances of Transact where the associated Asset1 has a specified Asset2 inside. The closest solution I came to the query below, which did not work.
query GetTransactionsThatHaveAsset2 {
description: ""
statement:
SELECT org.acme.biznet.Transact
WHERE (asset1.contents CONTAINS (Asset2Id == _$propId))
}
The thing is, I have also written the query below.
query GetAsset1sThatHaveAsset2 {
description: ""
statement:
SELECT org.acme.biznet.Asset1
WHERE (contents CONTAINS (Asset2Id == _$propId))
}
This query behaves as intended, but it's selecting over Asset1. I want to select over Transact. How would I write this query?
no, you can't presently nest the query like you propose, nested named queries are not currently implemented in Composer (CouchDB is not a relational DB, and hence Composer query language can't translate the nested query presently to be translated to CouchDB) 2) Transact is a transaction - it contains a relationship identifier(s) you defined in your model, not the nested data stored in the related asset. You would have to define a query that searches for all transactions that matches the asset1 identifier field you passed to the trxn - then in your code, you can check transact.asset1.contents contains the 'something' (passed in to the trxn too?) using a javascript match - quite straightforward). Alternatively, you could use the REST API filters (loopback filters as opposed to queries) where (form your app code) you can resolve the relationships of transaction (Transact) back to asset1 (and its contents) using the REST call with filter eg
{"where":{"asset1":"resource:org.acme.net.Asset1#1"}, "include":"resolve"} . Hope this helps, maybe its nesting you're looking for exclusively..
In my case, I have these assets
// Define assets
asset Product identified by productId {
o String productId
o String description
o String serialNumber
o String modelNumber
o String status // TRANSFERED, RECEIVED, RECLAMED
o DateTime joinTime
--> Trader previousOwner
--> Trader currentOwner
--> Trader newOwner
}
// Trade, moves product from to a new owner.
transaction Trade {
--> Product product
--> Trader newOwner
o String trade_type
}
Executing a trade transaction results in the record:
{
"$class": "org.sp.network.Trade",
"product": "resource:org.sp.network.Product#123",
"newOwner": "resource:org.sp.network.Trader#6694",
"trade_type": "Trade",
"transactionId": "e39a86ed4748a3ab73b5e9c023f6bb0ca025098af09b8b5b2dca8f5f7ef0db67",
"timestamp": "2019-06-13T12:04:20.180Z"
}
And to query all Trade transactions that contain a product is
query ProductPath{
description: "Selete all Trade transactions for a specific ProductId"
statement:
SELECT org.sp.network.Trade
WHERE (_$productId==product)
}
Using the rest server: the value of _$productId is resource:org.sp.network.Product#123
When I try to make the query:
query PapersFromAPoll
{
description: "retrieve all the papers from a poll"
statement:
SELECT org.acme.democracity.Paper
WHERE(poll.pollId == _$id)
}
I am not able to get any rows, here is the fragment where I make this query:
return query('PapersFromAPoll',{id : count.poll.pollId})
It is strange because when I erase the WHERE statement and I make a simple query without any parameters it works perfectly.
Paper and Poll in model.cto file:
asset Paper identified by paperId {
o String paperId
o String[] fields
--> Poll poll //Paper related with a poll
}
asset Poll identified by pollId {
o String pollId
o Ask[] asks
}
query PapersFromAPoll
{
description: "retrieve all the papers from a poll"
statement:
SELECT org.acme.democracity.Paper
WHERE(poll == _$pollId)
}
So you can filter by the Poll asset by giving a reference to that Poll asset. When you try to query with the pollId it will have to be a reference to the resource resource:org.acme.democracity.Poll#{insert_id}
My problem is the following:
I have one report called Y5000112.
My colleagues always execute it manually once with selection screen variant V1 and then execute it a second time with variant V2 adding the results of the first execution to the selection.
Those results in this case are PERNR.
My goal:
Automate this - execute that query twice with one click and automatically fill the PERNR selection of the second execution with the PERNR results of the first execution.
I found out how to trigger a report execution and after that another one, how to set it to a certain variant and got this far - [EDIT] after the first answer I got a bit further but I still have no idea how to loop through my results and put them into the next Report submit:
DATA: t_list TYPE TABLE OF abaplist.
* lt_seltab TYPE TABLE OF rsparams,
* ls_selline LIKE LINE OF lt_seltab.
SUBMIT Y5000114
USING SELECTION-SET 'MA OPLAN TEST'
EXPORTING LIST TO MEMORY
AND RETURN.
CALL FUNCTION 'LIST_FROM_MEMORY'
TABLES
listobject = t_list
EXCEPTIONS
not_found = 1
OTHERS = 2.
IF sy-subrc <> 0.
WRITE 'Unable to get list from memory'.
ELSE.
* I want to fill ls_seltab here with all pernr (table pa0020) but I haven't got a clue how to do this
* LOOP AT t_list.
* WRITE /t_list.
* ENDLOOP.
SUBMIT Y5000114
* WITH-SELECTION-TABLE ls_seltab
USING SELECTION-SET 'MA OPLAN TEST2'
AND RETURN.
ENDIF.
P.S.
I'm not very familiar with ABAP so if I didn't provide enough Information just let me know in the comments and I'll try to find out whatever you need to know in order to solve this.
Here's my imaginary JS-Code that can express very generally what I'm trying to accomplish.
function submitAndReturnExport(Reportname,VariantName,OptionalPernrSelection)
{...return resultObject;}
var t_list = submitAndReturnExport("Y5000114","MA OPLAN TEST");
var pernrArr = [];
for (var i in t_list)
{
pernrArr.push(t_list[i]["pernr"]);
}
submitAndReturnExport("Y5000114","MA OPLAN TEST2",pernrArr);
It's not that easy as it supposed to, so there won't be any one-line snippet. There is no standard way of getting results from report. Try EXPORTING LIST TO MEMORY clause, but consider that the report may need to be adapted:
SUBMIT [report_name]
WITH SELECTION-TABLE [rspar_tab]
EXPORTING LIST TO MEMORY
AND RETURN.
The result of the above statement should be read from memory and adapted for output:
call function 'LIST_FROM_MEMORY'
TABLES
listobject = t_list
EXCEPTIONS
not_found = 1
others = 2.
if sy-subrc <> 0.
message 'Unable to get list from memory' type 'E'.
endif.
call function 'WRITE_LIST'
TABLES
listobject = t_list
EXCEPTIONS
EMPTY_LIST = 1
OTHERS = 2
.
if sy-subrc <> 0.
message 'Unable to write list' type 'E'.
endif.
Another (and more efficient approach, IMHO) is to gain access to resulting grid via class cl_salv_bs_runtime_info. See the example here
P.S. Executing the same report with different parameters which are mutually-dependent (output pars of 1st iteration = input pars for the 2nd) is definitely a bad design, and those manipulations should be done internally. As for me one'd better rethink the whole architecture of the report.
I have 2 table :
1.message
2.replaytomessage
tables have 2 same filed :
1.title
2.matn
in message store topic and in replaytomessage store replay to topic
replaytomessage link to message with parentmid filed
i want write search in both table for example i search 'test' , select query find in title & matn in message and replaytomessage
i write this query :
$result = mysql_query("SELECT * FROM message inner Join replaytomessage On message.mid = replaytomessage.parentmid WHERE message.matn LIKE '%$qfind%' OR message.title LIKE '%$qfind%' ORDER BY message.mid DESC LIMIT $start, $per_page") or die(mysql_error());
but in result show replaytomessage data or no correct title...
this is my While :
while($row = mysql_fetch_array( $result )) {
echo "<tr>";
echo "<td width = '300'>";
$title = mysql_real_escape_string($row['title']);
$mid = intval($row['mid']);
$member = intval($row['member_id']);
echo "<a href = 'message.php?mid=$mid'>$title</a>";
echo "</td> ";
}
I haven't entirely understood what you are trying to achieve, but my suggestion would be to write the query in a database (MySQL?) client until you get the results you are expecting, and then translate into code (PHP?).
From what I think I understand, you need to give aliases to your columns to retrieve the correct data:
SELECT message.title as messagetitle, replaytomessage.title as replaytitle,
message.mid as messagemid, replaytomessage.mid as replaymid,
message.member_id as messagememberid, replaytomessage.member_id as replaymemberid
FROM message ...
then you can use the aliases to get the data from the table that you want
I have a ticket system with php/mysql , i want search in topics and answer top topic tables.
topic store in message table and answer to topic store in replaytomessage
for example when i searching 'test' word , i want show result from both table(message,replaytomessage) , maybe someone send topic Included 'test' word or someone answer to topic Included 'test' word.
I have 2 table : 1.message 2.replaytomessage
tables have 2 same filed : 1.title 2.matn
in message store topic and in replaytomessage store replay to topic data , replaytomessage link to message with parentmid filed
i dont know i must user UNION OR Join!