Example
I have two collections, one for Posts and one for Labels that look like this:
Post {
"_id": "WZTEGgknysdfXcQBi",
"title": "ASD",
"labels": {},
"author": "TMviRL8otm3ZsddSt",
"createdAt": "2016-01-14T08:42:42.343Z",
"date": "2016-01-14T08:42:42.343Z"
}
Label {
"_id": "9NCNPGH8F5MWNzjkA",
"color": "#333",
"name": "Grey Label",
"author": "TMviRL8otm3ZsddSt"
}
What I want to achieve is to have Posts with multiple Labels.
Problem is I cannot insert label data into the post.
I have a template to add new post and in there I am repeating over the labels. Then in the helpers I check which label is checked and store it into an array, but I cannot insert that array in the Posts collection.
'submit .add-entry': function(event) {
var title = event.target.title.value;
var description = event.target.description.value;
var checkedLabels = $('.label-checkbox:checked');
//initiate empty array
var labelsArray = [];
//go over the checked labels
for(i = 0; i < checkedLabels.length; i++){
var label = checkedLabels[i].value;
// store ids into array
labelsArray.push(label)
};
Posts.insert({
title: title,
description: description,
labels: labelsArray
});
Q1: Should I insert all the tags data or only the ID fetch more details from the Tags collection based on that ID?
Q2: How can I insert that array of labels into a Post? The code above does not work because it needs an Object
Q3 What is the best way to achieve such relationship between collections?
(Q1) You can insert all tag IDs to that labels list and keep that updated (Q3) I think that's a good practice; in your development process try to publish using composite collections with this package: https://github.com/englue/meteor-publish-composite
(Q2) Inserting is easy:
Post.insert({title:'Abc', labels:['one', 'two']});
You would need to use $addToSet to update. Here's an example of a possible method:
let setModifier = {
$addToSet: {
labels: myArrayOfLabelIDs
}
};
return Post.update({
_id: id
}, setModifier);
Note: Never use inserts on the client (like you did). My example can be added as a method on the server (use check() inside that) and can be called from the client upon submit like:
Meteor.call('myMethod', id, labelsArray)
Q1: Should I insert all the tags data or only the ID fetch more
details from the Tags collection based on that ID?
It depends: does the label change? If it mutable, then storing the ID only is best so that fetching the Label by ID will always return the proper data. Otherwise you need to update every Post with the new label info.
Q2: How can I insert that array of labels into a Post? The code above
does not work because it needs an Object
If the code does not work, it is likely that you are using some sort of schema that is not matched: to insert an array in place of an object, you need to modify the schema to accept an Array. You did not post details of a schema.
Q3 What is the best way to achieve such relationship between
collections?
You should look at 'denormalization': this is the term used for this type of things in a NoSQL database like MongoDB.
There is no straight answer, it depends on the relationship (1-1, 1-many, many-1) and the ratio of each 'many': in some cases it is best to nest data into objects if they are immutable or if dealing with updates is not too much of a pain.
this series of blog posts should help you get a better understanding:
http://blog.mongodb.org/post/87200945828/6-rules-of-thumb-for-mongodb-schema-design-part-1
Related
I am using Sequelize query() method as follows:
const sequelize = new Sequelize(...);
...
// IMPORTANT: No changed allowed on this query
const queryFromUser = "SELECT table1.colname, table2.colname FROM table1 JOIN table2 ON/*...*/";
const result = await sequelize.query(queryFromUser);
Because I am selecting two columns with identical names (colname), in the result, I am getting something like:
[{ "colname": "val1" }, { "colname": "val2" }...], and this array contains values only from the column table2.colname, as it is overwriting the table1.colname values.
I know that there is an option to use aliases in the SQL query with AS, but I don't have control over this query.
I think it would solve the issue, if there was a way to return the result as a 2D array, instead of the array of objects? Are there any ways to configure the Sequelize query that way?
Im afraid this will not be possible without changes in the library directly connecting to the database and parsing its response.
The reason is:
database returns BOTH values
then in javascript, there is mapping of received rows values to objects
This mapping would looks something like that
// RETURNED VALUE FROM DB: row1 -> fieldName:value&fieldName:value2
// and then javascript code for parsing values from database would look similar to that:
const row = {};
row.fieldName = value;
row.fieldName = value2;
return row;
As you see - unless you change the inner mechanism in the libraries, its impossible to change this (javascript object) behaviour.
UNLESS You are using mysql... If you are using mysql, you might use this https://github.com/mysqljs/mysql#joins-with-overlapping-column-names but there is one catch... Sequelize is not supporting this option, and because of that, you would be forced to maintain usage of both libraries at ones (and both connected)
Behind this line, is older answer (before ,,no change in query'' was added)
Because you use direct sql query (not build by sequelize, but written by hand) you need to alias the columns properly.
So as you saw, one the the colname would be overwritten by the other.
SELECT table1.colname, table2.colname FROM table1 JOIN table2 ON/*...*/
But if you alias then, then that collision will not occur
SELECT table1.colname as colName1, table2.colname as colName2 FROM table1 JOIN table2 ON/*...*/
and you will end up with rows like: {colName1: ..., colName2: ...}
If you use sequelize build in query builder with models - sequelize would alias everything and then return everything with names you wanted.
PS: Here is a link for some basics about aliasing in sql, as you may aliast more than just a column names https://www.w3schools.com/sql/sql_alias.asp
In my case I was using:
const newVal = await sequelize.query(query, {
replacements: [null],
type: QueryTypes.SELECT,
})
I removed type: QueryTypes.SELECT, and it worked fine for me.
This is a document from my profiles collection:
{
_id: ObjectId("5f2ba3a43feccd0004b8698c")
userID: "238906554584137728",
serverID: "533691583845892100",
username: "W.M.K",
money: 15775,
__v: 4,
items: [...],
serverName: "W-15i: Overworld",
gladium: 7959.33,
stocks: [{
_id: {...},
stockID: "605b26d309a48348e05d88c9",
name: "GOOGL",
amount: 1,
desc: "Alphabet Inc's (Google) Stock."
}]
}
I'm using const profile = await Profile.findOne({userID: userID, 'stocks.stockName': stock.name})
EDIT: For one stock, it's as easy as profile.stocks[0].amount -=1. But when I have multiple stocks, how do I get the amount and manipulate it like amount -= 1?
there are few ways you can go through an array of objects in mongoose... you can map through the array, you can also use dot notation in an update. Either way one thing you'll need to do is mark the document modified because mongo doesn't detect changes in arrays of objects. To do this, make the changes and then use the markModified() function on the document. It takes a parameter of the array field name that was modified. in this case.... profile.markModified('stocks')
This is done after changes and before the save.
You can use dot notation with mongoose and you can also iterate through the array via map or forEach
The benefit of using map is that you can double it up with .filter() to filter the array for certain stocks
As already stated by Jeremy, it is basically a matter of array manipulations.
Here is one example:
for(let i = 0; i < profile.stocks.length; i++) {
profile.stocks[i].amount -= 1;
}
If you just want to update the profile on your database, you could also look into some very advanced mongodb querys (I think it's called "aggregation"), but that is probably overkill.
I am working on my CouchDB project, where I want to create a specific view for my database.
It has proven that one of my key parameters has an ID as part of its name, but the other part is unique. ex "unique_ID_unique":"Value".
So brute force solutions like changing the name and/or how it is saved is not preferred.
To make it clear the ID is different for every entry (date).
I tried to modify it by using regex rules but it returns NULL for the key part.
emit(doc[/_unique$/], doc['something.else']);
Does someone have any idea why it is like that?
P.S: I already had a question like this yesterday, but due to the insufficient information that I gave, it led to wrong answers and I had to delete it.
Let's say you have a function that extract the unique key from a particular one:
var key = "blabla_120391029301923_blabla";
var keySplitted = key.split("_"); //Value: ["blabla","120391029301923","blabla"]
var uniqueKey = keySplitted[2]; //Value: blabla
From this, you can create a view what will map each documents and index them with your key.
function(doc){
var keySplitted = doc._id.split("_");
if(keySplitted.length == 3){
emit(keySplitted[2]);
}
}
The previous map ids from this:
evening_01012018_food
morning_02012018_musc
evening_01022018_food
To this:
food
musc
food
UPDATE
From offline discussion, I was able to understand that the objects to index had this content:
{
"_id": "doc_1",
"_rev": "1-89d017d9e5c82ee56d9beec2756fec99",
"type": "googrtt",
"ssrc_3685071425_send.bytesSent": "33621"
}
So with this document, the properties had to be split.
The final view content looks like this:
function (doc) {
for(key in doc){
var splitKey= key.split('_');
if(splitKey.length === 3){
emit(splitKey[2],doc[key]);
}
}
}
I was wondering if there's an easy way to pull out a value from one array based on its corresponding name.
Here, I've obtained the user email from the "coupons" collection. Now I would like to search through the "users" collection, find similar email, and output its corresponding "Name" ("Wes Haque Enterprises") to a $scope variable.
I already have references to both collections and $scope objects which have those references stored.
I just wanted to know if there's an easy way to traverse through the $scope.users object looking for the string "wes#wes.com" and then extracting "Wes Haque Enterprises" from it. Thanks.
Assuming that you don't really want to iterate (traverse?) over a bunch of arrays but instead query for the data you need...
You can query the users node for the data you need. In MacOS:
FQuery *allUsers = [usersRef queryOrderedByChild:#"emailAddress"];
FQuery *thisUser = [allUsers queryEqualToValue:#"wes#wes.com"];
[thisUser observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
for ( FDataSnapshot *child in snapshot.children) {
NSLog(#"%#", child);
}
}]
The result of the query will contain "Wes Haque Enterprises"
Or
ref.orderByChild("emailAddress").equalTo("wes#wes.com").on("child_added", function(snapshot) {
console.log(snapshot.key());
});
I need to store data temporarily at the client-side to allow users to add, edit or delete items without having to query the server for each of these actions; just when the user finishes adding items and clicks on the Add button, the list is sent to the server to be saved permanently.
This image describes what I want to achieve.
I know I have to use arrays in JavaScript, but I don't know how to create one to store objects (in this case Detail which contains :id, price and description).
I hope you can help me out.
Thanks in advance.
PS: I'm using JSP and... sorry for my English
Sure, since it's a table it makes sense to have an array of objects. Note that an object is surrounded by curly braces and an array is surrounded by brackets:
var myArray = []; // Initialize empty array
var myObject = {}; // Initialize empty object
This should accomplish what you need:
// Initialize variables
var newEntry, table = [];
// Create a new object
newEntry = {
id: '',
price: '',
description: ''
};
// Add the object to the end of the array
table.push(newEntry);
Which is the same as this:
// Initialize array
var table = [];
// Create object and add the object to the end of the array
table.push({
id: '22',
price: '$222',
description: 'Foo'
});
You can now access properties like this:
table[0].id; // '22'
On modern browsers, if you want the data to persist across sessions (like cookies) you could use the sessionStorage or localStorage objects.
When you want to send the data to the server, you'll send a JSON version of the table across the wire:
var data = JSON.stringify(table);
You can create an Array of your custom Detail objects pretty easily with object literals:
var details = [];
details.push({id:'abc1234', price:999.99, description:'Tesla Roadster'});
details.push({id:'xyz5678', price:129.99, description:'Land Rover'});
Then you can post your data to the server when the user clicks "Add."
Sounds like a good job for JSON.