Is there a way that I can "join" the contents of two javascript arrays, just as I would do a SQL join

I have two arrays: Question and UserProfile

  • The userProfiles : [] array contains { id, name } objects
  • The questions : [] array contains { id, text, createdBy }

The createdBy in questions is always one of the id values ​​in userProfiles .

Is there a way I could β€œjoin” arrays in the same way as I would join two SQL tables if I were using a database.

What I need as the final result is an array containing

 { id, text, name } 
+43
javascript
Jul 06 '13 at 6:22
source share
11 answers

This is apparently an important general-purpose question, and although there are many answers, some of them have borderline behavior, such as modifying existing data, solving a completely different problem than a problem using up to 93,057 bytes of JavaScript (not to mention to produce the wrong result), producing an overly complex additional nesting of data structures, requiring a lot of code for each call, and, most seriously, is not a self-sufficient solution to the important more general problem underlying it t question.

So, better or worse, I wrote a pad that extends the JavaScript Array object using the .joinWith method, which should be used to attach the this array to objects with an array of objects that by common indexing field. It is possible to select list of fields desired in the output (useful for merging arrays of objects with many fields when only a few are required) or omit list of fields in the outputs (useful for merging arrays of objects when most fields are desirable, but some of them are not).

The strip code is not made to look beautiful, so it will be at the end, with an example of how to use it for an OP of a certain type of data, the first one:

 /* this line will produce the array of objects as desired by the OP */ joined_objects_array = userProfiles.joinWith(questions, 'id', ['createdBy'], 'omit'); /* edit: I just want to make 100% sure that this solution works for you, ie, * does exactly what you need. I haven't seen your actual data, so it's * possible that your IDs are are not in common, (ie, your createdBy * is in common like you said, but not the IDs, and if so you could * morph your data first like this: * questions.map(function(x) { x.id = x.createdBy; }); * before joining the arrays of objects together. * */ 

The following code is for demonstration purposes:

 var array1 = [{ id: 3124, name: 'Mr. Smith' }, { id: 710, name: 'Mrs. Jones' }]; var array2 = [{ id: 3124, text: 'wow', createdBy: 'Mr. Jones' }, { id: 710, text: 'amazing' }]; var results_all = array1.joinWith(array2, 'id'); // [{id:3124, name:"Mr. Smith", text:"wow", createdBy:"Mr. Jones"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* var results_selected = array1.joinWith(array2, 'id', ['id', 'text', 'name']); // [{id:3124, name:"Mr. Smith", text:"wow"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* /* or equivalently, */ var results_omitted = array1.joinWith(array2, 'id', ['createdBy'], 1); // [{id:3124, name:"Mr. Smith", text:"wow"}, // {id:710, name:"Mrs. Jones", text:"amazing"}]* 

There are other nice things that this solution does (one of them retains the ability to access the resulting data using the index key, despite the return of the array).

Enjoy it!

 /* Array.joinWith - shim by Joseph Myers 7/6/2013 */ if (!Array.prototype.joinWith) { +function () { Array.prototype.joinWith = function(that, by, select, omit) { var together = [], length = 0; if (select) select.map(function(x){select[x] = 1;}); function fields(it) { var f = {}, k; for (k in it) { if (!select) { f[k] = 1; continue; } if (omit ? !select[k] : select[k]) f[k] = 1; } return f; } function add(it) { var pkey = '.'+it[by], pobj = {}; if (!together[pkey]) together[pkey] = pobj, together[length++] = pobj; pobj = together[pkey]; for (var k in fields(it)) pobj[k] = it[k]; } this.map(add); that.map(add); return together; } }(); } 

Documentation:

  /* this and that both refer to an array of objects, each containing object[by] as one of their fields */ /* NB It is the responsibility of the user of this method to ensure that the contents of the [by] fields are consistent with each other between the two arrays! */ /* select is an array of field names to be included in the resulting objects--all other fields will be excluded, or, if the Boolean value of omit evaluates to true, then select is an array of field names to be excluded from the resulting objects--all others will be included. */ 
+13
Jul 06 '13 at 16:28
source share

I think you want an inner join that is fairly simple to implement in JavaScript:

 const innerJoin = (xs, ys, sel) => xs.reduce((zs, x) => ys.reduce((zs, y) => // cartesian product - all combinations zs.concat(sel(x, y) || []), // filter out the rows and columns you want zs), []); 

For demonstration purposes, we will use the following dataset (thanks @AshokDamani):

 const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; 

Here's how you use it:

 const result = innerJoin(userProfiles, questions, ({id: uid, name}, {id, text, createdBy}) => createdBy === uid && {id, text, name}); 

In terms of SQL, it will look like:

 SELECT questions.id, questions.text, userProfiles.name FROM userProfiles INNER JOIN questions ON questions.createdBy = userProfiles.id; 

Putting it all together:

 const innerJoin = (xs, ys, sel) => xs.reduce((zs, x) => ys.reduce((zs, y) => // cartesian product - all combinations zs.concat(sel(x, y) || []), // filter out the rows and columns you want zs), []); const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; const result = innerJoin(userProfiles, questions, ({id: uid, name}, {id, text, createdBy}) => createdBy === uid && {id, text, name}); console.log(result); 



Edit: However, this is not the best solution. Since the above solution passes through the Potato product , it takes O(m Γ— n) time to start. With a little change, we can get it to work at O(m + n) time - @pebbl find it first :

 const equijoin = (xs, ys, primary, foreign, sel) => { const ix = xs.reduce((ix, row) => // loop through m items ix.set(row[primary], row), // populate index for primary table new Map); // create an index for primary table return ys.map(row => // loop through n items sel(ix.get(row[foreign]), // get corresponding row from primary row)); // select only the columns you need }; 

Now you can use it as follows:

 const result = equijoin(userProfiles, questions, "id", "createdBy", ({name}, {id, text}) => ({id, text, name})); 

Putting it all together:

 const equijoin = (xs, ys, primary, foreign, sel) => { const ix = xs.reduce((ix, row) => ix.set(row[primary], row), new Map); return ys.map(row => sel(ix.get(row[foreign]), row)); }; const userProfiles = [ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"}, ]; const questions = [ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3}, ]; const result = equijoin(userProfiles, questions, "id", "createdBy", ({name}, {id, text}) => ({id, text, name})); console.log(result); 
+38
Jul 06 '13 at 7:38
source share

I just always use underscore.js because it has such good support for arrays and "map reduction" with which this problem can be solved with.

here is a fiddle with a solution for your question (it assumes there is only one question for the user, as your original post suggests)

http://jsfiddle.net/x5Z7f/

(open the browser console to see the result)

  var userProfiles = [{ id:'1', name:'john' }, { id:'2', name:'mary' }]; var questions =[ { id:'1', text:'question john', createdBy:'1' }, { id:'2', text:'question mary', createdBy:'2' }]; var rows = _.map(userProfiles, function(user){ var question = _.find(questions, function(q){ return q.createdBy == user.id }); user.text = question? question.text:''; return user; }) _.each(rows, function(row){ console.log(row) }); 

the above answer assumes you are using id == createdBy as the join column.

+6
Jul 06 '13 at 7:01
source share

If it were me, I would approach this as follows:

Setup:

 var userProfiles = [], questions = []; userProfiles.push( {id:1, name:'test'} ); userProfiles.push( {id:2, name:'abc'} ); userProfiles.push( {id:3, name:'def'} ); userProfiles.push( {id:4, name:'ghi'} ); questions.push( {id:1, text:'monkey', createdBy:1} ); questions.push( {id:2, text:'Monkey', createdBy:1} ); questions.push( {id:3, text:'big', createdBy:2} ); questions.push( {id:4, text:'string', createdBy:2} ); questions.push( {id:5, text:'monKey', createdBy:3} ); 

First, it would create a search object where the binding identifier is used as the key

 var createObjectLookup = function( arr, key ){ var i, l, obj, ret = {}; for ( i=0, l=arr.length; i<l; i++ ) { obj = arr[i]; ret[obj[key]] = obj; } return ret; }; var up = createObjectLookup(userProfiles, 'id'); 

Now that you have this, you should easily go through the questions and find your custom object to combine:

 var i, l, question, user, result = []; for ( i=0, l=questions.length; i<l; i++ ) { if ( (question = questions[i]) && (user = up[question.createdBy]) ) { result.push({ id: question.id, text: question.text, name: user.name }); } } 

Now you should have everything you need in result

 console.log(result); 
+6
Jul 6 '13 at 7:24
source share

all u want is a ResultArray calculated below:

  var userProfiles1= new Array(1, "ashok"); var userProfiles2= new Array(2, "amit"); var userProfiles3= new Array(3, "rajeev"); var UArray = new Array(userProfiles1, userProfiles2, userProfiles3); var questions1= new Array(1, "text1", 2); var questions2= new Array(2, "text2", 2); var questions3= new Array(3, "text3", 1); var questions4= new Array(4, "text4", 2); var questions5= new Array(5, "text5", 3); var questions6= new Array(6, "text6", 3); var QArray = new Array(questions1, questions2, questions3, questions4, questions5, questions6); var ResultArray = new Array(); for (var i=0; i<UArray.length; i++) { var uid = UArray[i][0]; var name = UArray[i][1]; for(var j=0; j<QArray.length; j++) { if(uid == QArray[j][2]) { var qid = QArray[j][0] var text = QArray[j][1]; ResultArray.push(qid +"," + text +","+ name) } } } for(var i=0; i<ResultArray.length; i++) { document.write(ResultArray[i] + "<br>") } 

demo: http://jsfiddle.net/VqmVv/

+3
Jul 06 '13 at 7:05
source share

This is my attempt to make some kind of general decision. I use the methods Array.map and Array.index here:

 var arr1 = [ {id: 1, text:"hello", oid:2}, {id: 2, text:"juhu", oid:3}, {id: 3, text:"wohoo", oid:4}, {id: 4, text:"yeehaw", oid:1} ]; var arr2 = [ {id: 1, name:"yoda"}, {id: 2, name:"herbert"}, {id: 3, name:"john"}, {id: 4, name:"walter"}, {id: 5, name:"clint"} ]; function merge(arr1, arr2, prop1, prop2) { return arr1.map(function(item){ var p = item[prop1]; el = arr2.filter(function(item) { return item[prop2] === p; }); if (el.length === 0) { return null; } var res = {}; for (var i in item) { if (i !== prop1) { res[i] = item[i]; } } for (var i in el[0]) { if (i !== prop2) { res[i] = el[0][i]; } } return res; }).filter(function(el){ return el !== null; }); } var res = merge(arr1, arr2, "oid", "id"); console.log(res); 

Thus, you can define two arrays and one property for each array, so that prop1 will be replaced by all the properties of the element in array2, whose prop2 is equal to prop1.

The result in this case will be:

 var res = [ {id: 1, text:"hello", name:"herbert"}, {id: 2, text:"juhu", name:"john"}, {id: 3, text:"wohoo", name:"walter"}, {id: 4, text:"yeehaw", name:"yoda"} ]; 

Note that if there is more than one match, the first element will be used, and if there is no match, the object will be removed from the resulting array.

Fiddle

+2
Jul 6 '13 at 8:31
source share

Just wanted to share some common code:

 // Create a cartesian product of the arguments. // product([1,2],['a','b'],['X']) => [[1,"a","X"],[1,"b","X"],[2,"a","X"],[2,"b","X"]] // Accepts any number of arguments. product = function() { if(!arguments.length) return [[]]; var p = product.apply(null, [].slice.call(arguments, 1)); return arguments[0].reduce(function(r, x) { return p.reduce(function(r, y) { return r.concat([[x].concat(y)]); }, r); }, []); } 

Your problem:

 result = product(userProfiles, questions).filter(function(row) { return row[0].id == row[1].createdBy; }).map(function(row) { return { userName: row[0].name, question: row[1].text } }) 
+2
Jul 06 '13 at 9:56 on
source share

I do not know the built-in function to do this.

You can program your own function, something similar to this jsFiddle :

 var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}]; var questions = [ {id:1, text:'text1', createdBy:'foo'}, {id:1, text:'text2', createdBy:'bar'}, {id:2, text:'text3', createdBy:'foo'}]; merged = mergeMyArrays(userProfiles,questions); console.log(merged); /** * This will give you an array like this: * [{id:1, name:name1, text:text1}, {...] * params : 2 arrays to merge by id */ function mergeMyArrays(u,q){ var ret = []; for(var i = 0, l = u.length; i < l; i++){ var curU = u[i]; for(var j = 0, m = q.length; j<m; j++){ if(q[j].id == curU.id){ ret.push({ id: curU.id, name: curU.name, text: q[j].text }); } } } return ret; } 

Or, if you want to improve join (SQL-y):

 var userProfiles = [{id:1, name:'name1'},{id:2,name:'name2'}]; var questions = [ {id:1, text:'text1', createdBy:'foo'}, {id:1, text:'text2', createdBy:'bar'}, {id:2, text:'text3', createdBy:'foo'}]; merged = mergeMyArrays(userProfiles,questions); console.log(merged); /** * This will give you an array like this: * [{id:1, name:name1, questions:[{...}]] * params : 2 arrays to merge by id */ function mergeMyArrays(u,q){ var ret = []; for(var i = 0, l = u.length; i < l; i++){ var curU = u[i], curId = curU.id, tmpObj = {id:curId, name:curU.name, questions:[]}; for(var j = 0, m = q.length; j<m; j++){ if(q[j].id == curId){ tmpObj.questions.push({ text: q[j].text, createdBy: q[j].createdBy }); } } ret.push(tmpObj); } return ret; } 

Like in this jsFiddle

+1
Jul 06 '13 at 7:02
source share

You can do this using reduce and map .

First create a mapping from identifiers to names:

 var id2name = userProfiles.reduce(function(id2name, profile){ id2name[profile.id] = profile.name; return id2name; }, {}); 

Secondly, create a new array of questions, but with the name of the user who created the question instead of his ID:

 var qs = questions.map(function(q){ q.createdByName = id2name[q.createdBy]; delete q.createdBy; return q; }); 
+1
Jul 6. '13 at 11:05
source share

This is easy to do with StrelkiJS.

 var userProfiles = new StrelkiJS.IndexedArray(); userProfiles.loadArray([ {id: 1, name: "Ashok"}, {id: 2, name: "Amit"}, {id: 3, name: "Rajeev"} ]); var questions = new StrelkiJS.IndexedArray(); questions.loadArray([ {id: 1, text: "text1", createdBy: 2}, {id: 2, text: "text2", createdBy: 2}, {id: 3, text: "text3", createdBy: 1}, {id: 4, text: "text4", createdBy: 2}, {id: 5, text: "text5", createdBy: 3}, {id: 6, text: "text6", createdBy: 3} ]); var res=questions.query([{ from_col: "createdBy", to_table: userProfiles, to_col: "id", type: "outer" }]); 

The result will be:

 [ [ {"id":1,"text":"text1","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":2,"text":"text2","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":3,"text":"text3","createdBy":1}, {"id":1,"name":"Ashok"} ], [ {"id":4,"text":"text4","createdBy":2}, {"id":2,"name":"Amit"} ], [ {"id":5,"text":"text5","createdBy":3}, {"id":3,"name":"Rajeev"} ], [ {"id":6,"text":"text6","createdBy":3}, {"id":3,"name":"Rajeev"} ] ] 
+1
May 22 '16 at 5:12
source share

You can use jQuery.merge () first and then jQuery.unique () to achieve this. merge () will add all the elements to a single array, and unique () will remove duplicates from this array.

http://api.jquery.com/jQuery.merge/

http://api.jquery.com/jQuery.unique/

-2
Jul 06 '13 at 6:31
source share



All Articles