How to represent non unique parent-child relationship as graph? - neo4j

I want to see if there is a way to represent/model a nested parent-child relationship in a graph db platform like neo4j or arangodb.
In particular, I am trying to model the contractor/subcontractor relations over multiple contracts:
example contract relations image link
I can see how this can be done using a table where both the parent and the contract are represented. I can't see how to do this in a graph since there can be multiple A-B relations but for different contracts.

Using ArangoDB
The best thing to do here is create three collections, and I've created some sample data and sample queries to show you how it can work.
contracts: A document collection that contains contracts
companies: A document collection that contains companies
company_contracts: An edge collection that contains connections between contracts and companies
The goal is to store your contracts and companies in their respective collections and then store the relationshps in the company_contracts edge collection.
Because the companies are reused across multiple contracts, it will therefore be necessary to be able to filter on the relationships, based on the contract code.
Each contract has a key called code which contains an identifier for that contract (e.g. 'Contract 1' has a code of 1).
Note: I've also added a code field to each company, but that's not necessarily required for this example.
Each relationship that is added to the company_contracts edge collection will have a key added to it to identify what contract that edge is for, and this key is called contract_code.
This will be used in your AQL query to ensure you only select edges related to your contract in question.
To create the base data, you run this script in the arangodsh tool, just start it and then once you've provided your password and are connected, just paste this block of text in to create the sample collections and load some base data.
var contracts = db._create("contracts");
var companies = db._create("companies");
var company_contracts = db._createEdgeCollection("company_contracts");
var contract_1 = contracts.save({_key: "1", title:"Contract 1", code: 1})._id;
var contract_2 = contracts.save({_key: "2", title:"Contract 2", code: 2})._id;
var contract_3 = contracts.save({_key: "3", title:"Contract 3", code: 3})._id;
var company_a = companies.save({_key: "a", title:"Company A", code: "A"})._id;
var company_b = companies.save({_key: "b", title:"Company B", code: "B"})._id;
var company_c = companies.save({_key: "c", title:"Company C", code: "C"})._id;
var company_d = companies.save({_key: "d", title:"Company D", code: "D"})._id;
var company_e = companies.save({_key: "e", title:"Company E", code: "E"})._id;
company_contracts.save(contract_1, company_a, { contract_code: 1});
company_contracts.save(company_a, company_c, { contract_code: 1});
company_contracts.save(company_a, company_b, { contract_code: 1});
company_contracts.save(company_c, company_d, { contract_code: 1});
company_contracts.save(company_c, company_e, { contract_code: 1});
company_contracts.save(contract_2, company_c, { contract_code: 2});
company_contracts.save(contract_2, company_a, { contract_code: 2});
company_contracts.save(company_a, company_b, { contract_code: 2});
company_contracts.save(company_c, company_d, { contract_code: 2});
company_contracts.save(contract_3, company_b, { contract_code: 3});
company_contracts.save(company_b, company_c, { contract_code: 3});
company_contracts.save(company_b, company_a, { contract_code: 3});
Once you've done that, this is an example AQL query you could use to find all relationships for a given contract code:
LET contract_id = FIRST(FOR d IN contracts FILTER d.code == #contract_code RETURN d._id)
FOR v, e, p IN 1..10 OUTBOUND contract_id company_contracts
FILTER p.edges[*].contract_code ALL == #contract_code
RETURN p
If you pass a value of 1 as the value for the contract_code parameter, you'll get the result as shown by your sample document, and if you provide the values 2 or 3 it will show those results.
The query works by doing two things:
The LET query finds the _id of the contract you're interested in
The GRAPH query then finds all outbound connections from that contract, and it applies a filter to ALL edges in each path coming out of that contract, ensuring every single edge has a company_code key that matches the contract code you're working with
This FILTER ... ALL condition ensures you only get edges related to your contract.
The view of the results looks like this in the ArangoDB graph viewer for the results for Contract 1:

Related

How to create array of objects in firebase

I am working on a project developing a car rental app. The app can be accessed by admin and user both with different roles. So at the moment i'm saving the car added by the admin using api / manually in firebase
let vehicle = Vehicle(make: make, model: modell, price: price, mileage: mileage)
db.collection("Admin").document("car").setData(from: vehicle)
where vehicle is an object with properties like make model mileage ect. Now with this funtionality i always save one object. If i add a new object it overwrites it how can i store a multiple objects in car document.
The reason why it is "updating" instead of "adding" when you are running above statements for second time because you are using the same DocumentID, which you specify as "car".
If you are running a for-loop function to add multiple vehicle documents, you can modify your codes similar to below example.
Method 1
let vehicleDocID: Int = 0
let vehicle = Vehicle(make: make, model: modell, price: price, mileage: mileage)
for vehicle in vehicleS {
vehicleDocID += 1
db.collection("Admin").document("\(vehicleDocID)").setData(from: vehicle)
}
Alternatively, you can simply use .add() function to get defaultID, which I personally recommend.
Method 2
var ref: DocumentReference? = nil
ref = db.collection("Admin").addDocument(data: [
"make": "make",
"model": "modell",
"price": "price",
"mileage": "mileage"
]) { err in
if let err = err {
print("Error adding document: \(err)")
} else {
print("Document added with ID: \(ref!.documentID)")
}
}
Lastly, in case, if the number of vehicles are large (>100), I suggest you use batch writes to add the documents to Firestore, as it ensures better performance. For batch writes, you may refer to below Firestore documentation.
https://firebase.google.com/docs/firestore/manage-data/transactions

Flink - Join same stream in order to filter some events

I have a stream of data that looks like this:
impressionId | id | name | eventType | timestamp
I need to filter (ignore) event of type "click" that don't have a matching 'impressionId' of type 'impression' (so basically ignore clicks event that don't have an impression)
and then count how many impressions in total I have and how many clicks I have (for an id/name pair) for a particular time window.
This is how I approached the solution:
[...]
Table eventsTable = tEnv.fromDataStream(eventStreamWithTimeStamp, "impressionId, id, name, eventType, eventTime.rowtime");
tEnv.registerTable("Events", eventsTable);
Table clicksTable = eventsTable
.where("eventType = 'click'")
.window(Slide.over("24.hour").every("1.minute").on("eventTime").as("minuteWindow"))
.groupBy("impressionId, id, name, eventType, minuteWindow")
.select("impressionId as clickImpressionId, eventType as clickEventType, concat(concat(id,'_'), name) as concatClickId, id as clickId, name as clickName, minuteWindow.rowtime as clickMinute");
Table impressionsTable = eventsTable
.where("eventType = 'impression'")
.window(Slide.over("24.hour").every("1.minute").on("eventTime").as("minuteWindow"))
.groupBy("impressionId, id, name, eventType, minuteWindow")
.select("impressionId as impressionImpressionId, eventType as impressionEventType, concat(concat(id,'_'), name) as concatImpId, id as impId, name as impName, minuteWindow.rowtime as impMinute");
Table filteredClickCount = clicksTable
.join(impressionsTable, "clickImpressionId = impressionImpressionId && concatClickId = concatImpId && clickMinute = impMinute")
.window(Slide.over("24.hour").every("1.minute").on("clickMinute").as("minuteWindow"))
.groupBy("concatClickId, clickMinute")
.select("concatClickId, concatClickId.count as clickCount, clickMinute as eventTime");
DataStream<Test3> result = tEnv.toAppendStream(filteredClickCount, Test3.class);
result.print();
What I'm trying to do is simply create two tables, one with clicks and one with impressions, 'inner' join clicks to impressions and the one that are joined means they are the clicks that have a matching impression.
Now this doesn't work and I don't know why!?
the count produced by the last joint table are not correct. It works for the first minute but after that the counts are off by almost double.
I have then tried to modify the last table like this:
Table clickWithMatchingImpression2 = clicksTable
.join(impressionsTable, "clickImpressionId = impressionImpressionId && concatClickId = concatImpId && clickMinute = impMinute")
.groupBy("concatClickId, clickMinute")
.select("concatClickId, concatClickId.count as clickCount, clickMinute as eventTime");
DataStream<Tuple3<Boolean, Tuple3>> result2 = tEnv.toRetractStream(clickWithMatchingImpression2, Test3.class);
result2.print();
And.... this works !? However I don't know why and I don't know what to do with this DataStream<Tuple3<Boolean, Test3>> format... Flink refuse to use toAppendStream when the table don't have a window.
I would like a simply structure with only the final numbers.
1 ) Is my approach correct? Is there an easier way of filtering click that don't have impressions ?
2 ) Why does the counts are not correct in my solution ?
I am not entirely sure if I understood your use case correctly, an example with some data points would definitely help here.
Let me explain what your code is doing. First the two tables calculate how many clicks/impressions there were in the last 24 hours.
For an input
new Event("1", "1", "ABC", "...", 1),
new Event("1", "2", "ABC", "...", 2),
new Event("1", "3", "ABC", "...", 3),
new Event("1", "4", "ABC", "...", 4)
You will get windows (array<eventId>, window_start, window_end, rowtime):
[1], 1969-12-31-01T00:01:00.000, 1970-01-01T00:01:00.000, 1970-01-01T00:00:59.999
[1, 2], 1969-12-31-01T00:02:00.000, 1970-01-01T00:02:00.000, 1970-01-01T00:01:59.999
[1, 2, 3], 1969-12-31-01T00:03:00.000, 1970-01-01T00:03:00.000, 1970-01-01T00:02:59.999
...
Therefore when you group both on id and name you get sth like:
1, '...', '1_ABC', 1, 'ABC', 1970-01-01T00:00:59.999
1, '...', '1_ABC', 1, 'ABC', 1970-01-01T00:01:59.999
1, '...', '1_ABC', 1, 'ABC', 1970-01-01T00:02:59.999
...
which if you group again in 24 hours windows you will count each event with the same id multiple times.
If I understand your use case correctly and you are looking for how many impressions happened in a 1 minute period around an occurrence of a click, an interval join might be what you are looking for. You could implement your case with a following query:
Table clicks = eventsTable
.where($("eventType").isEqual("click"))
.select(
$("impressionId").as("clickImpressionId"),
concat($("id"), "_", $("name")).as("concatClickId"),
$("id").as("clickId"),
$("name").as("clickName"),
$("eventTime").as("clickEventTime")
);
Table impressions = eventsTable
.where($("eventType").isEqual("impression"))
.select(
$("impressionId").as("impressionImpressionId"),
concat($("id"), "_", $("name")).as("concatImpressionId"),
$("id").as("impressionId"),
$("name").as("impressionName"),
$("eventTime").as("impressionEventTime")
);
Table table = impressions.join(
clicks,
$("clickImpressionId").isEqual($("impressionImpressionId"))
.and(
$("clickEventTime").between(
$("impressionEventTime").minus(lit(1).minutes()),
$("impressionEventTime"))
))
.select($("concatClickId"), $("impressionEventTime"));
table
.window(Slide.over("24.hour").every("1.minute").on("impressionEventTime").as("minuteWindow"))
.groupBy($("concatClickId"), $("minuteWindow"))
.select($("concatClickId"), $("concatClickId").count())
.execute()
.print();
As for why Flink sometimes cannot produce append stream, but only retract stream see. Very briefly, if an operation does not work based on a time attribute, there is not single point in time, when the result is "valid". Therefore it must emit stream of changes instead of a single appended value. The first field in the tuple tells you if the record is an insertion(true) or retraction/deletion(false).

How do I query all documents in a Firestore collection for all strings in an array? [duplicate]

From the docs:
You can also chain multiple where() methods to create more specific queries (logical AND).
How can I perform an OR query?
Example:
Give me all documents where the field status is open OR upcoming
Give me all documents where the field status == open OR createdAt <= <somedatetime>
OR isn't supported as it's hard for the server to scale it (requires keeping state to dedup). The work around is to issue 2 queries, one for each condition, and dedup on the client.
Edit (Nov 2019):
Cloud Firestore now supports IN queries which are a limited type of OR query.
For the example above you could do:
// Get all documents in 'foo' where status is open or upcmoming
db.collection('foo').where('status','in',['open','upcoming']).get()
However it's still not possible to do a general OR condition involving multiple fields.
With the recent addition of IN queries, Firestore supports "up to 10 equality clauses on the same field with a logical OR"
A possible solution to (1) would be:
documents.where('status', 'in', ['open', 'upcoming']);
See Firebase Guides: Query Operators | in and array-contains-any
suggest to give value for status as well.
ex.
{ name: "a", statusValue = 10, status = 'open' }
{ name: "b", statusValue = 20, status = 'upcoming'}
{ name: "c", statusValue = 30, status = 'close'}
you can query by ref.where('statusValue', '<=', 20) then both 'a' and 'b' will found.
this can save your query cost and performance.
btw, it is not fix all case.
I would have no "status" field, but status related fields, updating them to true or false based on request, like
{ name: "a", status_open: true, status_upcoming: false, status_closed: false}
However, check Firebase Cloud Functions. You could have a function listening status changes, updating status related properties like
{ name: "a", status: "open", status_open: true, status_upcoming: false, status_closed: false}
one or the other, your query could be just
...where('status_open','==',true)...
Hope it helps.
This doesn't solve all cases, but for "enum" fields, you can emulate an "OR" query by making a separate boolean field for each enum-value, then adding a where("enum_<value>", "==", false) for every value that isn't part of the "OR" clause you want.
For example, consider your first desired query:
Give me all documents where the field status is open OR upcoming
You can accomplish this by splitting the status: string field into multiple boolean fields, one for each enum-value:
status_open: bool
status_upcoming: bool
status_suspended: bool
status_closed: bool
To perform your "where status is open or upcoming" query, you then do this:
where("status_suspended", "==", false).where("status_closed", "==", false)
How does this work? Well, because it's an enum, you know one of the values must have true assigned. So if you can determine that all of the other values don't match for a given entry, then by deduction it must match one of the values you originally were looking for.
See also
in/not-in/array-contains-in: https://firebase.google.com/docs/firestore/query-data/queries#in_and_array-contains-any
!=: https://firebase.googleblog.com/2020/09/cloud-firestore-not-equal-queries.html
I don't like everyone saying it's not possible.
it is if you create another "hacky" field in the model to build a composite...
for instance, create an array for each document that has all logical or elements
then query for .where("field", arrayContains: [...]
you can bind two Observables using the rxjs merge operator.
Here you have an example.
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/merge';
...
getCombinatedStatus(): Observable<any> {
return Observable.merge(this.db.collection('foo', ref => ref.where('status','==','open')).valueChanges(),
this.db.collection('foo', ref => ref.where('status','==','upcoming')).valueChanges());
}
Then you can subscribe to the new Observable updates using the above method:
getCombinatedStatus.subscribe(results => console.log(results);
I hope this can help you, greetings from Chile!!
We have the same problem just now, luckily the only possible values for ours are A,B,C,D (4) so we have to query for things like A||B, A||C, A||B||C, D, etc
As of like a few months ago firebase supports a new query array-contains so what we do is make an array and we pre-process the OR values to the array
if (a) {
array addObject:#"a"
}
if (b) {
array addObject:#"b"
}
if (a||b) {
array addObject:#"a||b"
}
etc
And we do this for all 4! values or however many combos there are.
THEN we can simply check the query [document arrayContains:#"a||c"] or whatever type of condition we need.
So if something only qualified for conditional A of our 4 conditionals (A,B,C,D) then its array would contain the following literal strings: #["A", "A||B", "A||C", "A||D", "A||B||C", "A||B||D", "A||C||D", "A||B||C||D"]
Then for any of those OR combinations we can just search array-contains on whatever we may want (e.g. "A||C")
Note: This is only a reasonable approach if you have a few number of possible values to compare OR with.
More info on Array-contains here, since it's newish to firebase docs
If you have a limited number of fields, definitely create new fields with true and false like in the example above. However, if you don't know what the fields are until runtime, you have to just combine queries.
Here is a tags OR example...
// the ids of students in class
const students = [studentID1, studentID2,...];
// get all docs where student.studentID1 = true
const results = this.afs.collection('classes',
ref => ref.where(`students.${students[0]}`, '==', true)
).valueChanges({ idField: 'id' }).pipe(
switchMap((r: any) => {
// get all docs where student.studentID2...studentIDX = true
const docs = students.slice(1).map(
(student: any) => this.afs.collection('classes',
ref => ref.where(`students.${student}`, '==', true)
).valueChanges({ idField: 'id' })
);
return combineLatest(docs).pipe(
// combine results by reducing array
map((a: any[]) => {
const g: [] = a.reduce(
(acc: any[], cur: any) => acc.concat(cur)
).concat(r);
// filter out duplicates by 'id' field
return g.filter(
(b: any, n: number, a: any[]) => a.findIndex(
(v: any) => v.id === b.id) === n
);
}),
);
})
);
Unfortunately there is no other way to combine more than 10 items (use array-contains-any if < 10 items).
There is also no other way to avoid duplicate reads, as you don't know the ID fields that will be matched by the search. Luckily, Firebase has good caching.
For those of you that like promises...
const p = await results.pipe(take(1)).toPromise();
For more info on this, see this article I wrote.
J
OR isn't supported
But if you need that you can do It in your code
Ex : if i want query products where (Size Equal Xl OR XXL : AND Gender is Male)
productsCollectionRef
//1* first get query where can firestore handle it
.whereEqualTo("gender", "Male")
.addSnapshotListener((queryDocumentSnapshots, e) -> {
if (queryDocumentSnapshots == null)
return;
List<Product> productList = new ArrayList<>();
for (DocumentSnapshot snapshot : queryDocumentSnapshots.getDocuments()) {
Product product = snapshot.toObject(Product.class);
//2* then check your query OR Condition because firestore just support AND Condition
if (product.getSize().equals("XL") || product.getSize().equals("XXL"))
productList.add(product);
}
liveData.setValue(productList);
});
For Flutter dart language use this:
db.collection("projects").where("status", whereIn: ["public", "unlisted", "secret"]);
actually I found #Dan McGrath answer working here is a rewriting of his answer:
private void query() {
FirebaseFirestore db = FirebaseFirestore.getInstance();
db.collection("STATUS")
.whereIn("status", Arrays.asList("open", "upcoming")) // you can add up to 10 different values like : Arrays.asList("open", "upcoming", "Pending", "In Progress", ...)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots, #Nullable FirebaseFirestoreException e) {
for (DocumentSnapshot documentSnapshot : queryDocumentSnapshots) {
// I assume you have a model class called MyStatus
MyStatus status= documentSnapshot.toObject(MyStatus.class);
if (status!= null) {
//do somthing...!
}
}
}
});
}

How to get a group of objects from Firebase?

In my React-Native app, I have an array of specific users whose values I want to pull from Firebase. What is the most efficient way to go about this? Currently I am looping through the array and making a new request for each (relevant code below):
const usersRef = new Firebase(`${ config.FIREBASE_ROOT }/users`)
for (var key in usersArray) {
var userRef = usersRef.child(key);
//do stuff here
}
However, I feel this isn't very efficient and it makes several requests to the database. Is there a way I can pass in the array and get those items from Firebase, all in one call? Thanks.
Firebase data structure:
{
"items" : [ {
"description" : "fuzzy socks",
"type" : "toy"
}, {
"description" : "bouncy ball",
"type" : "toy"
}, {
"description" : "scrabble",
"type" : "game"
}, {
"description" : "construction paper",
"type" : "crafts"
} ],
"users" : [ {
"itemList" : [ 1, 2, 3, 4 ],
"description" : "brown",
}, {
"itemList" : [ 5, 6, 7 ],
"description" : "green",
}, {
"itemList" : [ 8, 9, 10 ],
"description" : "blue",
}, {
"itemList" : [ 11, 12, 13 ],
"description" : "yellow",
} ]
}
Simplified use case: In one use case, I only want to get information about 2 of the users (out of all the users I have stored in Firebase--assume it's many more than just the 4 in the structure above). So, I have the array importantUsers:
var importantUsers = [0, 3]
Then, I want to send a request to Firebase that only queries the database for the values associated with these userID values (so somehow pass in the array to Firebase for a result). Return values would be something like this:
0: itemList: [1,2,3,4], description: brown
3: itemList: [11,12,13], description: yellow
My motivation for querying the database for multiple users at once (rather than creating a separate ref for user 0 and user 3) is to not have multiple calls made to Firebase. Is there any way to go about this?
So what you are after is an sql 'in' type query. Select in [0,2]. To select a number of users from a list.
The additional challenge in your question is that users you are interested in are random so you can't use .startAt and .endAt, and there is no other relation between the users.
Firebase does not have direct support for 'in', 'and' or 'or' kinds of query but there are a number of ways to make it happen.
How about this: flag the users you want and then with a single query, read them in.
First, you'll start with a typical Firebase /users node with the addition of a 'selected' child node (this can be omitted initially but I am showing it here as a placeholder)
users
uid_0
name: "Bud"
selected: false
uid_1
name: "Henry"
selected: false
uid_2
name: "Billy"
selected: false
Then, we need some random uid's, say uid_0 and uid_2 and store those in an array. Keep in mind that we would be using the Firebase generated uid but we'll use uid_0, uid_1 etc for simplicity.
With just two users, you could just observeSingleEventOfType on each of the two nodes, no big deal.
However, if we needed 100 random users or 1000, doing 1000 separate queries or observeSingleEvent's should be avoided. But, setValue is blisteringly fast (no returned data) so....
Get our users ref
let usersRef = myRootRef.ChildbyAppendingPath("users")
We know the path to each of the 100 users we want by iterating over the array to build those refs and set selected to true
for uid in uidArray {
let thisUserRef = usersRef.childByAppendingPath(uid)
let selectedRef = thisUserRef.childByAppendingPath("selected")
selectedRef.setValue(true)
}
Then, you can query for all users in the usersRef where the selected child = true.
Once you have them, to clean up, iterate over the returned users and set the selected to false or nil
The cool thing about this is that setValue can blast through 100 or 1000 users very quickly with no overhead, setting their selected child to true. Then a single query can return the values you want.
Seems almost reverse in logic to write out to then read back in but I am pretty sure it's considerably better than iterating over an array and generating 1000 queries or observers.
(Firebase folks can check me on that one)
One other thought is that if a user is say, clicking on other users in a list, you could set selected = true as they are clicking and then query for those when the user is done.
You could use bindAsArray or bindAsObject from ReactFire
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/items");
this.bindAsArray(ref, "items");
Then you could loop over that array/object as needed without extra queries

Concatenate certain rows into a cell ignoring duplicates

I have a google form and I would like to sort it's responses in a separate sheet on google sheets. The results of the form look sort of like this.
Id Job
1 Shelving, Sorting
2 Sorting
1 Cleaning, Shelving
3 Customer Service
2 Shelving, Sorting
which I would like to format into
Id Jobs
1 Cleaning, Shelving, Sorting
2 Shelving, Sorting
3 Customer Service
Is there a formula I can use to accomplish this, noting that it ignores duplicates and groups the different ids? Ordering of the jobs does not matter.
Working example here.
The code is like:
=unique(transpose(split(join(", ",filter(B1:B10,A1:A10=1)),", ")))
where
filter(B1:B10,A1:A10=1) gives you all the B values where A = 1
join(",", filter(...)) joins the list with the ", " separator (e.g. "apple, orange" and "kiwi" becomes "apple, orange, kiwi"
split(join(...)) splits the list into an array (e.g. back to [apple, orange, kiwi]
transpose(split(...)) converts the horizontal list to vertical list
unique(transpose(...)) gives you the unique values (unique() only works with vertical list)
After this, you need to transpose then join the list
Note you must keep the separator consistent (e.g. always "," or ", ")
This is Apps Script code instead of a function. To use it, you will need to use the Tools menu, and open the script editor. Then select the function name from the drop down list, and then click the "Run" button.
To use this code, you need to have a source and a destination sheet. You will need to change the sheet names in the code to your sheet names. In this code, the source sheet is named 'Data'. You will need to change that to the name of your source sheet. In this code, the destination sheet is named 'Output', and is at the bottom of the code. This code gets data starting in row two, and writes the output data starting in row two. I tested it with your values and it works.
function concatCellData() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName('Data');
var colOneData = sh.getRange(2, 1, sh.getLastRow()-1, 1).getValues();
var colTwoData = sh.getRange(2, 2, sh.getLastRow()-1, 1).getValues();
var newData = [],
newDataColOne = [],
colOneValue,
existInNewData = false,
colB_One,
colB_Two,
dataPosition,
thisValue,
combinedArrays = [],
arrayTemp = [];
for (var i=0;i<colOneData.length;i+=1) {
colOneValue = colOneData[i][0];
dataPosition = newDataColOne.indexOf(colOneValue);
existInNewData = dataPosition !== -1;
if (!existInNewData) {//If it doesn't exist in the new data, just write the values
newDataColOne.push(colOneValue);
newData.push([colOneValue, colTwoData[i][0]]);
continue;
};
colB_One = [];
colB_Two = [];
combinedArrays = []
arrayTemp = [];
colB_One = colTwoData[i][0].split(",");
colB_Two = newData[dataPosition][1];
colB_Two = colB_Two.split(",");
var combinedArrays = colB_One.concat(colB_Two);
//Get unique values
for (var j=0;j<combinedArrays.length;j+=1) {
thisValue = combinedArrays[j].trim();
if (arrayTemp.indexOf(thisValue) === -1) {
arrayTemp.push(thisValue);
};
};
newData[dataPosition] = [colOneValue, arrayTemp.toString()]; //Overwrite existing data
};
ss.getSheetByName('Output').getRange(2, 1, newData.length, newData[0].length).setValues(newData);
};

Resources