How can I delete records in a loop? - siebel

I'm writing a business service which needs to iterate all the records in a BC, and delete some of them:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
}
isRecord = bc.NextRecord();
}
However, DeleteRecord moves the cursor to the next one, so I would be skipping a record for each one being deleted, which is not what I want. Is there any standard-ish way to solve this?
I guess I could just make two loops, one for the checks and another for the deletion... it would work, but it just feels silly to iterate twice over the records, there has to be a better way than this:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
var list = [];
while (isRecord) {
if (...) {
list.push(bc.GetFieldValue("Id"));
}
isRecord = bc.NextRecord();
}
bc.ClearToQuery();
bc.SetSearchSpec("Id", "='" + list.join("' OR ='") + "'"); // "='1-ABCD' OR ='1-1234'"
bc.ExecuteQuery(ForwardBackward);
while (bc.FirstRecord()) {
bc.DeleteRecord();
}

Solution will depend directly on your if (...) condition:
If it's a simple condition, which can be converted to a search
expression, then you just simply can make a query and delete
everything that was found.
If it's a complex condition, which cannot implemented as a searchspec, then you can do this in two ways (both has its own disadvantages):
a) Use awkward combination of DeleteRecord and PreviousRecord.
b) Create second method that will delete single record and call it for each record that satisfy the condition, something like that
function deleteRecord(sId:chars) {
...
bc.SetSearchSpec("Id", sId);
bc.ExecuteQuery(ForwardOnly);
if (bc.FirstRecord()) {
bc.DeleteRecord();
}
}
...
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
deleteRecord(bc.GetFieldValue("Id"));
}
isRecord = bc.NextRecord();
}
...
Your solution can be and option c), but it will not work when Id list will be too big, because such query will lead to a huge SQL statement and databases have some limitations on SQL query size (and DBA won't be happy to see such queries in logs).
I prefer option b). You can optimize it by creating a second instance of the same BC in your main function and pass it by reference to the deleteRecord function.

Since you are doing ForwardBackward, you could just add a PreviousRecord call after deleting:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.FirstRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
bc.PreviousRecord();
}
isRecord = bc.NextRecord();
}
Or simply do the loop backwards:
bc.ExecuteQuery(ForwardBackward);
var isRecord = bc.LastRecord();
while (isRecord) {
if (...) {
bc.DeleteRecord();
}
isRecord = bc.PreviousRecord();
}

Are are storing the value of a first record in var and then in the loop you are checking if records are presents. I suggest you go for forward and backward try.
The value of var is incremented, So, Inside the while loop place the move back an element command and so var will now store the previous element. After executing the loop. You can easily increment to the next element.

Related

Google Script - Removing Else statement properly so it skips/ignores

I have a checklist, column C indicates which test is automated by "enabled" or "disabled" written in the cells.
Further down the row is a column for the the Pass / Empty column for each test.
I have code that looks for if Enabled in column C, in X column on that row, mark as Pass automatically (or 'P' in my case).
The problem: If C column contains "Disabled" but also a Pass, when I run the script it replaces that Pass with an empty cell. How can I change the else statement to just ignore that cell and leave whatever is in it for anything but Enabled condition is met
function autoPassPC() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Aug 2020');
// What to put in the test result
var values1 = "P";
// Where to look for Auto:
var values2 = sheet.getRange("C10:C15" + sheet.getLastRow()).getValues();
// Keyword to look for in Auto: column
var putValues = [];
for (var i = 0; i < values2.length; i++) {
if (values2[i][0] === "Enabled") {
putValues.push([values1]);
} else {
putValues.push([""]);
}
}
// Put value1 inside row, column# for test result
sheet.getRange(10, 25, putValues.length, 1).setValues(putValues);
}
Basically how do I get rid of
} else {
putValues.push([""]);
}
properly? Just deleting this causes the script to put 'P' on every single row. Just want it to ignore the cells instead.
Thanks!
Removing the else statement will actually skip the row, but as soon as you skip a row, all following values in putValues will be on the wrong row. Instead of "skipping", try pushing the already existing value into putValues.
function autoPassPC() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName('Aug 2020');
// What to put in the test result
var values1 = "P";
// Where to look for Auto:
var enabledDisabled = sheet.getRange("C10:C15" + sheet.getLastRow()).getValues();
var testResultsRange = sheet.getRange("X10:X15" + sheet.getLastRow());
var testResults = testResultsRange.getValues();
// Keyword to look for in Auto: column
var putValues = [];
for (var i = 0; i < enabledDisabled.length; i++) {
if (enabledDisabled[i][0] === "Enabled") {
putValues.push([values1]);
} else {
putValues.push([testResults[i][0]]); // Push the existing cell value
}
}
// Put value1 inside row, column# for test result
testResultsRange.setValues(putValues);
}
You could also try getting the entire table at once thus having only one getValues() call.

Map/reduce to get the count and latest date for each document grouped by key

A simple version of my document document is the following structure:
doc:
{
"date": "2014-04-16T17:13:00",
"key": "de5cefc56ff51c33351459b88d42ca9f828445c0",
}
I would like to group my document by key, to get the latest date and the number of documents for each key, something like
{ "Last": "2014-04-16T16:00:00", "Count": 10 }
My idea is to to do a map/reduce view and query setting group to true.
This is what I have so far tried. I get the exact count, but not the correct dates.
map
function (doc, meta) {
if(doc.type =="doc")
emit(doc.key, doc.date);
}
reduce
function(key, values, rereduce) {
var result = {
Last: 0,
Count: 0
};
if (rereduce) {
for (var i = 0; i < values.length; i++) {
result.Count += values[i].Count;
result.Last = values[i].Last;
}
} else {
result.Count = values.length;
result.Last = values[0]
}
return result;
}
You're not comparing dates... Couchbase sorts values by key. In your situation it will not sort it by date, so you should do it manually in your reduce function. Probably it will look like:
result.Last = values[i].Last > result.Last ? values[i].Last : result.Last;
and in reduce function it also can be an array, so I don't think that your reduce function always be correct.
Here is an example of my reduce function that filter documents and leave just one that have the newest date. May be it will be helpful or you can try to use it (seems it looks like reduce function that you want, you just need to add count somewhere).
function(k,v,r){
if (r){
if (v.length > 1){
var m = v[0].Date;
var mid = 0;
for (var i=1;i<v.length;i++){
if (v[i].Date > m){
m = v[i].Date;
mid = i;
}
}
return v[mid];
}
else {
return v[0] || v;
}
}
if (v.length > 1){
var m = v[0].Date;
var mid = 0;
for (var i=1;i<v.length;i++){
if (v[i].Date > m){
m = v[i].Date;
mid = i;
}
}
return v[mid];
}
else {
return v[0] || v;
}
}
UPD: Here is an example of what that reduce do:
Input date (values) for that function will look like (I've used just numbers instead of text date to make it shorter):
[{Date:1},{Date:3},{Date:8},{Date:2},{Date:4},{Date:7},{Date:5}]
On the first step rereduce will be false, so we need to find the biggest date in array, and it will return
Object {Date: 8}.
Note, that this function can be called one time, but it can be called on several servers in cluster or on several branches of b-tree inside one couchbase instance.
Then on next step (if there were several machines in cluster or "branches") rereduce will be called and rereduce var will be set to true
Incoming data will be:
[{Date:8},{Date:10},{Date:3}], where {Date:8} came from reduce from one server(or branch), and other dates came from another server(or branch).
So we need to do exactly the same on that new values to find the biggest one.
Answering your question from comments: I don't remember why I used same code for reduce and rereduce, because it was long time ago (when couchbase 2.0 was in dev preview). May be couchbase had some bugs or I just tried to understand how rereduce works. But I remember that without that if (r) {..} it not worked at that time.
You can try to place return v; code in different parts of my or your reduce function to see what it returns on each reduce phase. It's better to try once by yourself to understand what actually happens there.
I forget to mention that I have many documents for the same key. In fact for each key I can have many documents( message here):
{
"date": "2014-04-16T17:13:00",
"key": "de5cefc56ff51c33351459b88d42ca9f828445c0",
"message": "message1",
}
{
"date": "2014-04-16T15:22:00",
"key": "de5cefc56ff51c33351459b88d42ca9f828445c0",
"message": "message2",
}
Another way to deal with the problem is to do it in the map function:
function (doc, meta) {
var count = 0;
var last =''
if(doc.type =="doc"){
for (k in doc.message){
count += 1;
last = doc.date> last?doc.date:last;
}
emit(doc.key,{'Count':count,'Last': last});
}
}
I found this simpler and it do the job in my case.

How to wait for a promise to be fulfilled before continuing

How can I wait until a Promise is resolved before executing the next line of code?
e.g.
var option = null;
if(mustHaveOption){
option = store.find("option", 1).then(function(option){ return option })
}
//wait until promise is resolved before returning this value
return option;
rallrall provided the correct answer in his comment: you can't
The solution for me was to redesign my code to return promises and then the receiving function must evaluate the result something along the lines of:
function a(){
var option = null;
return mustHaveOption ? store.find("option", 1) : false;
}
}
function b(){
res = a();
if (!res){
res.then(function(option){
// see option here
});
}
}
Another key solution for me was to use a hash of promises. One creates an array of all the promises that must be resolve before executing the next code:
Em.RSVP.Promise.all(arrayOfPromises).then(function(results){
//code that must be executed only after all of the promises in arrayOfPromises is resolved
});
It tooks me a while to wrap my head around this async way of programming - but once I did things work quite nicely.
With ES6, you can now use the async/await syntax. It makes the code much more readable:
async getSomeOption() {
var option = null;
if (mustHaveOption) {
option = await store.find("option", 1)
}
}
return option;
PS: this code could be simplified, but I'd rather keep it close from the example given above.
You can start to show a loading gif, then you can subscribe to the didLoad event for the record, inside which you can continue your actual processing..
record = App.User.find(1);
//show gif..
record.on("didLoad", function() {
console.log("ren loaded!");
});
//end gif; continue processing..

Intersection of promises and dependent computed properties

I have a user model which has 2 relations (myFriends and friendsWithMe). The intersection is the Array of users which represents the real friends. I have solved this Computatation with RSVP.all :
friends: function() {
var ret = [];
Ember.RSVP.all([this.get('myFriends'), this.get('friendsWithMe')]).then(function(results) {
ret.pushObjects(_.intersection(results[0].get('content'), results[1].get('content'))) ;
});
return ret;
}.property('myFriends.#each', 'friendsWithMe.#each'),
The Problem is now I have another computed property that depends on this one:
/**
* Gives the relation between two User
* 4: has requested your friendship
* 3: Yourself
* 2: Friends
* 1: FriendShip Request
*/
myFriendshipStatus: function() {
if(this.get('friends').contains(this.container.lookup('user:current'))){
return 2;
} else if(this.get('friendsWithMe').contains(this.container.lookup('user:current'))){
return 4;
} else if(this.get('myFriends').contains(this.container.lookup('user:current'))){
return 1;
} else if (this.get('id') === this.container.lookup('user:current').get('id')){
return 3;
} else {
return 0;
}
}.property('friends.#each')
When I now debug myFriendShipStatus the promises are not resolved and the "friends" array has no entries yet.
I have also tried to change my friends function to the ember.computed.intersect, which would then look like this:
friends: function() {
return Ember.computed.intersect('myFriends', 'friendsWithMe')
}.property('myFriends.#each', 'friendsWithMe.#each'),
But then I get an exception from this line:
if(this.get('friends').contains(this.container.lookup('user:current'))){
Because the ArrayComputedProperty has no function contains.
How can get my friends function together with myFriendShipStatus working? I would prefer to use Ember.computed.intersect, but I don't know how I check then for it's values.
The reason it returns an empty array in the first example is as follows. Immediately after the Ember.RSVP.all() call, the return statement will be executed, returning an empty ret array. At some point in the future the RSVP promise will fulfill, but since the friends function has already returned the empty array, this will have no effect.
Here is what you could do:
// See http://emberjs.com/api/#method_A
friends: Ember.A,
recalculateFriends: function() {
Ember.RSVP.all([this.get('myFriends'), this.get('friendsWithMe')]).then(function(results) {
var myFriends = results[0], friendsWithMe = results[1];
this.set('friends', _.intersection(myFriends.get('content'), friendsWithMe.get('content')));
});
}.property('myFriends', 'friendsWithMe'), // #each is redundant here
myFriendshipStatus: function() {
// Will be recalculated when the friends array changes (which will in turn recalculate when myFriends or friendsWithMe changes
}.property('friends'),
And... I'm just now noticing you're using Ember.computed.intersect wrong :P It shouldn't be wrapped inside a function:
friends: Ember.computed.intersect('myFriends', 'friendsWithMe')
(See example: http://emberjs.com/api/#method_computed_intersect),

Creating a cached counter path for large data lists in Firebase

Using Firebase to count the total records is done this way:
var table = new Firebase('http://beta.firebase.com/user/tablename');
table.on('value', function(snapshot) {
var count = 0;
snapshot.forEach(function() {
count++;
});
//count is now safe to use.
});
Is there a way to avoid enumeration by having a cached counter in a different path?
I was thinking in some "counter" object which keeps the history of changes and the last computed value.
counter:
{
value: 672,
history:
{
+2, -4, +1, +1, +1
}
}
in a transaction then:
pick one history item, update the value, remove the history item.
Also who would be responsible of doing this?
Here's an example that combines the idea of a counter with an incremental, numeric ID. For your use case, you could skip the ID portion, but the principles are still the same.
The core of this is a transaction that, when you create a new record, adds one to your counter:
var fb = new Firebase('http://beta.firebase.com');
// stores incremental id before adding record
function incRecord(data) {
// increment the counter
fb.child('counter/value').transaction(function(currentValue) {
return (currentValue||0) + 1
}, function(err, committed, ss) {
if( err ) {
console.error(err);
}
else if( committed ) {
// if you want to pass the counter into the data,
// just use ss.val() here to fetch it
addRecord(data);
// could also store an audit history about changes to the counter, assuming we had a user ID or something to that effect with this:
// but you don't need this history to increment it
// fb.child('counter/history/'+ss.val()).set(userId);
}
});
}
// creates new record
function addRecord(data) {
// you could pass the record value here, I just set the value to "record #<id>"
fb.child('records').push(data, function(err) {
err && console.error(err);
});
}
Then invoke it by calling something like this:
incRecord({ hello: 'world' });