SOQL Group by IN Salesforce - salesforce

String query = 'SELECT id,CreatedById,Product.Id,POCGrades__c ,fm_pocname__c,product.Name ,Visit.placeId, fm_poccode__c, createdby.LastName,Visit.LastModifiedDate, createddate, ActualBooleanValue'
+ ' FROM retailvisitkpi'
+ ' WHERE createddate = last_month and '
+ ' ( Product.Id = \'01t5j000003tszWAAQ\''
+ ' OR Product.Id = \'01t5j000003tszWAAQ\''
+ ' OR Product.Id = \'01t5j000003tt5nAAA\''
+ ' OR Product.Id = \'01t5j000003tsznAAA\''
+ ' OR Product.Id = \'01t5j000003tt1zAAA\''
+ ' OR Product.Id = \'01t5j000003tt7AAAQ\''
+ ' )';
I am having trouble to removes duplicate records based on Visit.placeID, can anybody help me out with soql

Just use hashmaps with the resulting query?
Map<String, retailvisitkpi__c> mapOfItems = new Map<String, retailvisitkpi__c>();
That will remove all the duplicates based on the Visit Place ID

Related

NestJS: Raw SQL query execution with SQL Server too slow. Configuration problems?

APP in Nestjs that is connected to a SQL Server database. All the queries are written on the database side, so the connection between them is with simple raw SQL and the use of mssql package.
Here is the thing: when I run in SSMS, a very small query (let's say returning < 20 records) is executed in milliseconds (even larger and complex queries or stored procedures have good performance).
When I run in the app, with a local database connection, queries start having some delay (let's say 1 second for the same query).
But when I start using the database on Azure, the same small query takes 3 to 5 seconds (for 20 records).
I read some of causes could be related to parameter sniffing, but I don't think it is the case.
What I guess, is that my backend is restarting the database connection every time a new query arrives.
Here is the logic of the app: one centralized CRUD service to be used by the controllers.
In main.ts is the connection:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = new Logger('Bootstrap', { timestamp: true });
const configService = app.get(ConfigService);
// Database configuration
const sqlConfig = {
user:
configService.get('DB_USELOCAL') === 'false'
? configService.get('DB_USERNAME')
: configService.get('DB_USERNAME_LOCAL'),
password:
configService.get('DB_USELOCAL') === 'false'
? configService.get('DB_PASSWORD')
: configService.get('DB_PASSWORD_LOCAL'),
server:
configService.get('DB_USELOCAL') === 'false'
? configService.get('DB_SERVER')
: configService.get('DB_SERVER_LOCAL'),
database:
configService.get('DB_USELOCAL') === 'false'
? configService.get('DB_DATABASE')
: configService.get('DB_DATABASE_LOCAL'),
pool: {
max: 10,
min: 0,
idleTimeoutMillis: 30000,
},
requestTimeout: 180000, //3 minutes to wait for a request to the database.
options: {
// encrypt: false, // for azure
encrypt: configService.get('DB_USELOCAL') === 'false' ? true : false,
trustServerCertificate: false, // change to true for local dev / self-signed certs
},
};
sql.connect(sqlConfig);
logger.log('App connected to SQL Server database');
// CORS: Cross-origin resource sharing (CORS) is a mechanism that allows resources to be requested from another domain.
app.enableCors();
// App running
await app.listen(configService.get('PORT') || 3000);
logger.log(`App running on port ${configService.get('PORT') || 3000}`);
}
bootstrap();
At the CRUD Service the queries requested
import { HttpException, HttpStatus, Injectable, Logger } from '#nestjs/common';
import { fxSQLerrorMsg } from './function/SLQerrorMsg.fx';
import * as sql from 'mssql';
import { FxArrayObjectStr } from './function/arrayObjectStr.fx';
import { FxObjectStr } from './function/objectStr.fx';
import { FindBodyDTO } from './findBody.dto';
#Injectable()
export class CrudService {
private logger = new Logger('Crud Service', { timestamp: true });
async find(
sp: string,
DB: string,
body?: FindBodyDTO | null,
query?: Record<string, any> | null,
email?: string,
filter?: string,
): Promise<Record<string, any>[]> {
const method = "'" + 'find' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = body
? "'" + JSON.stringify(body).replace('%20', ' ') + "'"
: null;
const queryParam = FxObjectStr(query);
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
',' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (await sql.query<Record<string, any>[]>(spScript))
.recordset as unknown as Record<string, any>[];
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Find'),
HttpStatus.BAD_REQUEST,
);
}
}
async post(
sp: string,
DB: string,
body: Record<string, any>[],
email?: string,
filter?: string,
): Promise<Record<string, string>> {
const method = "'" + 'post' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = FxArrayObjectStr(body);
const queryParam = null;
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
', ' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (
(await sql.query<string>(spScript)).recordset as any[]
)[0] as Record<string, string>;
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Post'),
HttpStatus.BAD_REQUEST,
);
}
}
async updateOne(
sp: string,
DB: string,
body: Record<string, any>[],
query?: Record<string, any>,
email?: string,
filter?: string,
): Promise<Record<string, string>> {
const method = "'" + 'updateOne' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = FxArrayObjectStr(body);
const queryParam = FxObjectStr(query);
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
', ' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (
(await sql.query<string>(spScript)).recordset as any[]
)[0] as Record<string, string>;
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Update'),
HttpStatus.BAD_REQUEST,
);
}
}
async updateMany(
sp: string,
DB: string,
body: Record<string, any>[],
query?: Record<string, any>,
email?: string,
filter?: string,
): Promise<Record<string, string>> {
const method = "'" + 'updateMany' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = FxArrayObjectStr(body);
const queryParam = FxObjectStr(query);
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
', ' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (
(await sql.query<string>(spScript)).recordset as any[]
)[0] as Record<string, string>;
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Update'),
HttpStatus.BAD_REQUEST,
);
}
}
async deleteOne(
sp: string,
DB: string,
query?: Record<string, any>,
email?: string,
filter?: string,
): Promise<Record<string, string>> {
const method = "'" + 'deleteOne' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = null;
const queryParam = FxObjectStr(query);
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
', ' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (
(await sql.query<string>(spScript)).recordset as any[]
)[0] as Record<string, string>;
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Delete'),
HttpStatus.BAD_REQUEST,
);
}
}
async deleteMany(
sp: string,
DB: string,
body: Record<string, any>[],
query: Record<string, any>,
email?: string,
filter?: string,
): Promise<Record<string, string>> {
const method = "'" + 'deleteMany' + "'";
const storedProcedure =
process.env.SPECIFYDB == 'true'
? 'EXECUTE [' + DB + '].[ml_sp].[' + sp + ']'
: 'EXECUTE [ml_sp].[' + sp + ']';
const bodyParam = FxArrayObjectStr(body);
const queryParam = FxObjectStr(query);
const emailScript = email ? "'" + email + "'" : null;
const filterScript = filter ? "'" + filter + "'" : null;
const spScript =
storedProcedure +
' ' +
method +
', ' +
bodyParam +
', ' +
queryParam +
', ' +
emailScript +
',' +
filterScript;
this.logger.verbose(spScript);
try {
return (
(await sql.query<string>(spScript)).recordset as any[]
)[0] as Record<string, string>;
} catch (error) {
this.logger.error(error);
throw new HttpException(
fxSQLerrorMsg(error.message, 'Delete'),
HttpStatus.BAD_REQUEST,
);
}
}
}
Additional information: the query that I am testing (what I called a very small query) is:
ALTER VIEW [ml_view].[User2Role] AS
(SELECT [ml_users].[User2Role].[id] as [id],
[User_user_Aux].[email] as [user],
[PortfolioRole_portfoliorole_Aux].[name] as [portfoliorole],
[ml_users].[User2Role].[editiondate] as [editiondate],
[User_editedbyuser_Aux].[email] as [editedbyuser]
FROM [ml_users].[User2Role]
LEFT JOIN [ml_users].[User] as [User_user_Aux] ON [User_user_Aux].[id] = [ml_users].[User2Role].[userid]
LEFT JOIN [ml_setup].[PortfolioRole] as [PortfolioRole_portfoliorole_Aux] ON [PortfolioRole_portfoliorole_Aux].[id] = [ml_users].[User2Role].[portfolioroleid]
LEFT JOIN [ml_users].[User] as [User_editedbyuser_Aux] ON [User_editedbyuser_Aux].[id] = [ml_users].[User2Role].[editedbyuser])
Actually, it is stored as view, and run through a stored procedure. But we tested executing the view directly (Select * from [viewName]), and the result is the same.
Solution: the problem was not in the NestJS configuration to the SQL Server, was precisely in the resources assigned in Azure to the database (DTU or vCores)
I summarize next some of the actions that we undertook, but in the end, it was a silly mistake. However, guess is useful to keep the post.
Data sniffing controlling.
Rebuild index.
Reset statistics.
Trying different SQL configurations in Nestjs (typeorm, tedious, etc.)
Assigned resources to the (DTU or Vcore model). See in Azure "Compute + Storage" section.

Build a dynamic string to filter a rest call

I'm trying to build a dynamic string but I'm facing a problem, so I kindly ask help to the community.
A have a string to build a rest call filter, but I'm lost with the and's
This query only works if only one filter condition is provided, but I have to provide as AND
var query;
var queryDefault = "IsLastForLevelAndParticipant eq 1";
this.state.participantFirstName
? query += "substringof('" + this.state.participantFirstName + "',Participant/FirstName)"
: queryDefault
this.state.participantLastName
? query += "substringof('" + this.state.participantLastName + "',Participant/LastName)"
: queryDefault
So let´s see.
If I start building this and only one filter is provided I'll have a plus and
var query;
var queryDefault = "IsLastForLevelAndParticipant eq 1";
this.state.participantFirstName
? query += "substringof('" + this.state.participantFirstName + "',Participant/FirstName) and "
: queryDefault
this.state.participantLastName
? query += "substringof('" + this.state.participantLastName + "',Participant/LastName)"
: queryDefault
query += " and " + queryDefault
I have 12 filters and I must know how many have values in order to provide the and clause
This is my state
//Filters Certificates
startEmission: string;
endEmission: string;
startValidity: string;
endValidity: string;
participantFirstName: string;
participantLastName: string;
paticipantCertNumber: string;
selectYesNo: string;
selectLevel: string;
Any help is a bonus.
Add all possible filters to an array, filter the falsy values and finally join all items with " and ".
var query = [
queryDefault,
this.state.participantFirstName && "substringof('" + this.state.participantFirstName + "',Participant/FirstName)"
this.state.participantLastName && "substringof('" + this.state.participantLastName + "',Participant/LastName)"
]
.filter(item => !!item)
.join(" and ");
BTW. are you using OData? the structure of the filter looks familiar. If you do, I would recommend you to use this library: https://github.com/techniq/odata-query#readme
Since I'm using datetime also, here it is the final solution
var queryDefault = "IsLastForLevelAndParticipant eq 1";
var query = [
queryDefault,
this.state.startEmission && "(Date1 ge datetime'" + moment.utc(this.state.startEmission).toISOString() + "') and (Date1 le datetime'"
+ moment.utc(this.state.endEmission).toISOString() + "')",
this.state.startValidity && "(Validto ge datetime'" + moment.utc(this.state.startValidity).toISOString() + "') and (Validto le datetime'"
+ moment.utc(this.state.endValidity).toISOString() + "')",
this.state.participantFirstName && "(substringof('" + this.state.participantFirstName + "',Participant/FirstName))",
this.state.participantLastName && "(substringof('" + this.state.participantLastName + "',Participant/LastName))",
this.state.selectYesNo && "IsPrinted eq " + this.state.selectYesNo,
this.state.selectLevel && "Level/Title eq '" + this.state.selectLevel + "'"
]
.filter(filter => !! filter)
.join(" and ");
var q = JSON.stringify(query).substring(1).slice(0, -1);
console.log(JSON.stringify(query));

Snowflake Pandas connection issue

I'm trying to insert a dataframe into a snowflake table using the pandas connector and am getting permission issues, but using the "normal" snowflake connector works fine.
import snowflake.connector as snow
from snowflake.connector.pandas_tools import *
cur = self.conn.cursor()
my_schema = "my_schema"
my_table = "my_table"
cur.execute(f"""INSERT INTO {my_schema}.{my_table}(load_date, some_id)
values (current_timestamp, 'xxx')""")
write_pandas(self.conn, daily_epc_df, table_name=my_table, schema=my_schema)
But I'm getting
File "/Users/abohr/virtualenv/peak38/lib/python3.8/site-packages/snowflake/connector/errors.py", line 85, in default_errorhandler
raise error_class(
snowflake.connector.errors.ProgrammingError: 001757 (42601): SQL compilation error:
Table '"my_schema"."my_table"' does not exist
the same connection can insert and then doesn't work on the same table.
I also tried
df.to_sql(..., method=pd_writer)
and get
pandas.io.sql.DatabaseError: Execution failed on sql 'SELECT name FROM sqlite_master WHERE type='table' AND name=?;': not all arguments converted during string formatting
Why is it talking about sqlite_master if I'm trying to connect to Snowflake? Do the pandas functions require different connections?
my libs:
name = "snowflake-connector-python"
version = "2.3.3"
description = "Snowflake Connector for Python"
category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "pandas"
version = "1.0.5"
description = "Powerful data structures for data analysis, time series, and statistics"
category = "main"
optional = false
python-versions = ">=3.6.1"```
on Python 3.8
I found my issue - I added quote_identifiers=False to
write_pandas(self.conn, daily_epc_df, table_name=my_table, schema=my_schema, quote_identifiers=False)
and now it works.
But seems very wrong that the default behavior would break, I'm still suspicious.
Hmm I've done something superior I think to pandas ...
Tell me if you agree ...
CREATE OR REPLACE PROCEDURE DEMO_DB.PUBLIC.SNOWBALL(
db_name STRING,
schema_name STRING,
snowball_table STRING,
max_age_days FLOAT,
limit FLOAT
)
RETURNS VARIANT
LANGUAGE JAVASCRIPT
COMMENT = 'Collects table and column stats.'
EXECUTE AS OWNER
AS
$$
var validLimit = Math.max(LIMIT, 0); // prevent SQL syntax error caused by negative numbers
var sqlGenerateInserts = `
WITH snowball_tables AS (
SELECT CONCAT_WS('.', table_catalog, table_schema, table_name) AS full_table_name, *
FROM IDENTIFIER(?) -- <<DB_NAME>>.INFORMATION_SCHEMA.TABLES
),
snowball_columns AS (
SELECT CONCAT_WS('.', table_catalog, table_schema, table_name) AS full_table_name, *
FROM IDENTIFIER(?) -- <<DB_NAME>>.INFORMATION_SCHEMA.COLUMNS
),
snowball AS (
SELECT table_name, MAX(stats_run_date_time) AS stats_run_date_time
FROM IDENTIFIER(?) -- <<SNOWBALL_TABLE>> table
GROUP BY table_name
)
SELECT full_table_name, aprox_row_count,
CONCAT (
'INSERT INTO IDENTIFIER(''', ?, ''') ', -- SNOWBALL table
'(table_name,total_rows,table_last_altered,table_created,table_bytes,col_name,',
'col_data_type,col_hll,col_avg_length,col_null_cnt,col_min,col_max,col_top,col_mode,col_avg,stats_run_date_time)',
'SELECT ''', full_table_name, ''' AS table_name, ',
table_stats_sql,
', ARRAY_CONSTRUCT( ', col_name, ') AS col_name',
', ARRAY_CONSTRUCT( ', col_data_type, ') AS col_data_type',
', ARRAY_CONSTRUCT( ', col_hll, ') AS col_hll',
', ARRAY_CONSTRUCT( ', col_avg_length, ') AS col_avg_length',
', ARRAY_CONSTRUCT( ', col_null_cnt, ') AS col_null_cnt',
', ARRAY_CONSTRUCT( ', col_min, ') AS col_min',
', ARRAY_CONSTRUCT( ', col_max, ') AS col_max',
', ARRAY_CONSTRUCT( ', col_top, ') AS col_top',
', ARRAY_CONSTRUCT( ', col_MODE, ') AS col_MODE',
', ARRAY_CONSTRUCT( ', col_AVG, ') AS col_AVG',
', CURRENT_TIMESTAMP() AS stats_run_date_time ',
' FROM ', quoted_table_name
) AS insert_sql
FROM (
SELECT
tbl.full_table_name,
tbl.row_count AS aprox_row_count,
CONCAT ( '"', col.table_catalog, '"."', col.table_schema, '"."', col.table_name, '"' ) AS quoted_table_name,
CONCAT (
'COUNT(1) AS total_rows,''',
IFNULL( tbl.last_altered::VARCHAR, 'NULL'), ''' AS table_last_altered,''',
IFNULL( tbl.created::VARCHAR, 'NULL'), ''' AS table_created,',
IFNULL( tbl.bytes::VARCHAR, 'NULL'), ' AS table_bytes' ) AS table_stats_sql,
LISTAGG (
CONCAT ('''', col.full_table_name, '.', col.column_name, '''' ), ', '
) AS col_name,
LISTAGG ( CONCAT('''', col.data_type, '''' ), ', ' ) AS col_data_type,
LISTAGG ( CONCAT( ' HLL(', '"', col.column_name, '"',') ' ), ', ' ) AS col_hll,
LISTAGG ( CONCAT( ' AVG(ZEROIFNULL(LENGTH(', '"', col.column_name, '"','))) ' ), ', ' ) AS col_avg_length,
LISTAGG ( CONCAT( ' SUM( IFF( ', '"', col.column_name, '"',' IS NULL, 1, 0) ) ' ), ', ') AS col_null_cnt,
LISTAGG ( IFF ( col.data_type = 'NUMBER', CONCAT ( ' MODE(', '"', col.column_name, '"', ') ' ), 'NULL' ), ', ' ) AS col_MODE,
LISTAGG ( IFF ( col.data_type = 'NUMBER', CONCAT ( ' MIN(', '"', col.column_name, '"', ') ' ), 'NULL' ), ', ' ) AS col_min,
LISTAGG ( IFF ( col.data_type = 'NUMBER', CONCAT ( ' MAX(', '"', col.column_name, '"', ') ' ), 'NULL' ), ', ' ) AS col_max,
LISTAGG ( IFF ( col.data_type = 'NUMBER', CONCAT ( ' AVG(', '"', col.column_name,'"',') ' ), 'NULL' ), ', ' ) AS col_AVG,
LISTAGG ( CONCAT ( ' APPROX_TOP_K(', '"', col.column_name, '"', ', 100, 10000)' ), ', ' ) AS col_top
FROM snowball_tables tbl JOIN snowball_columns col ON col.full_table_name = tbl.full_table_name
LEFT OUTER JOIN snowball sb ON sb.table_name = tbl.full_table_name
WHERE (tbl.table_catalog, tbl.table_schema) = (?, ?)
AND ( sb.table_name IS NULL OR sb.stats_run_date_time < TIMESTAMPADD(DAY, - FLOOR(?), CURRENT_TIMESTAMP()) )
--AND tbl.row_count > 0 -- NB: also excludes views (table_type = 'VIEW')
GROUP BY tbl.full_table_name, aprox_row_count, quoted_table_name, table_stats_sql, stats_run_date_time
ORDER BY stats_run_date_time NULLS FIRST )
LIMIT ` + validLimit;
var tablesAnalysed = [];
var currentSql;
try {
currentSql = sqlGenerateInserts;
var generateInserts = snowflake.createStatement( {
sqlText: currentSql,
binds: [
`"${DB_NAME}".information_schema.tables`,
`"${DB_NAME}".information_schema.columns`,
SNOWBALL_TABLE, SNOWBALL_TABLE,
DB_NAME, SCHEMA_NAME, MAX_AGE_DAYS, LIMIT
]
} );
var insertStatements = generateInserts.execute();
// loop over generated INSERT statements and execute them
while (insertStatements.next()) {
var tableName = insertStatements.getColumnValue('FULL_TABLE_NAME');
currentSql = insertStatements.getColumnValue('INSERT_SQL');
var insertStatement = snowflake.createStatement( {
sqlText: currentSql,
binds: [ SNOWBALL_TABLE ]
} );
var insertResult = insertStatement.execute();
tablesAnalysed.push(tableName);
}
return { result: "SUCCESS", analysedTables: tablesAnalysed };
}
catch (err) {
return {
error: err,
analysedTables: tablesAnalysed,
sql: currentSql
};
}
$$;

Many Updates -> One Update?

Once a month we receive a file from another company and we need to adapt it to our database (in SQL Server). For this we run several updates that take a long time.
Is there any way to "convert" all these updates into a single statement so it runs through the table only once? (it's a really big table!).
We are using something like this right now:
update Table1 set column01 = 0 where column01 = ' ' or column01 = '';
update Table1 set column02 = 0 where column02 = ' ' or column02 = '';
update Table1 set column03 = 0 where column03 = ' ' or column03 = '';
update Table1 set column04 = 0 where column04 = ' ' or column04 = '';
update Table1 set column05 = 0 where column05 = ' ' or column05 = '';
update Table1 set column06 = 0 where column06 = ' ' or column06 = '';
update Table1 set column07 = 0 where column07 = ' ' or column07 = '';
update Table1 set column08 = 0 where column08 = ' ' or column08 = '';
update Table1 set column09 = 0 where column09 = ' ' or column09 = '';
update Table1 set column10 = 0 where column10 = ' ' or column10 = '';
update Table1 set column11 = 0 where column11 = ' ' or column11 = '';
update Table1 set column12 = 0 where column12 = ' ' or column12 = '';
update Table1 set column13 = 0 where column13 = ' ' or column13 = '';
update Table1 set column14 = 0 where column14 = ' ' or column14 = '';
update Table1 set column15 = 0 where column15 = ' ' or column15 = '';
update Table1 set column16 = 0 where column16 = ' ' or column16 = '';
update Table1 set column17 = 0 where column17 = ' ' or column17 = '';
update Table1 set column18 = 0 where column18 = ' ' or column18 = '';
update Table1 set column19 = 0 where column19 = ' ' or column19 = '';
You could use CASE ... WHEN like this
update Table1 set
column01 = CASE when column01 = ' ' or column01 = '' then 0 ELSE column01 END,
column02 = CASE when column02 = ' ' or column02 = '' then 0 ELSE column02 END,
......
column19 = CASE when column19 = ' ' or column19 = '' then 0 ELSE column19 END

Last row writes twice

I had a broken code which I have managed to get working (The original question is here https://stackoverflow.com/questions/29127005/multiple-spreadsheet-rows-from-multiple-forms), but the loop writes the last line twice and my coding skills are not good enough for me to work out why...yet!
Can anyone shed any light on what I have/haven't included?
function InsertDataInSheet(e) //Function to insert data into spreadsheet on clicking submit
{
var app = UiApp.getActiveApplication();
//get number of rows to input
var num = parseInt(e.parameter.table_tag);
var num = num+1;
//set increment step through
for (var i = 1; i < num ; i++ ) {
//Declare varialbe fields to collect data from
var user = Session.getActiveUser().getEmail();
var date = e.parameter['DateBox'+i];
var location = e.parameter['LocationListBox'+i];
var source = e.parameter['SourceListBox'+i];
var reporter = e.parameter['ReporterTextBox'+i];
var priority = e.parameter['PriorityListBox'+i];
var hazard = e.parameter['HazardListBox'+i];
var details = e.parameter['DetailsTextBox'+i];
var description = e.parameter['DescriptionTextBox'+i];
var timeStamp = new Date();
//Decide date that this needs to be closed by
if (priority === '02 - WITHIN 24-48 HOURS') {
var dateTemp = new Date(date);
dateTemp.setDate(dateTemp.getDate()+2);
var actiondate = dateTemp;
}
if (priority === '03 - WITHIN 1 WEEK') {
var dateTemp = new Date(date);
dateTemp.setDate(dateTemp.getDate()+7);
var actiondate = dateTemp;
}
//establish email addresses
//Declare correct range to obtain values
var LocationSheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName("LocationCodes")
//Start with central maintenance department
var email00 = LocationSheet.getRange(33,5).getValue()
//followed by other emails as they appear
var email01 = LocationSheet.getRange(3,5).getValue();
var email02 = LocationSheet.getRange(4,5).getValue();
//declare the correct Depots to check
var LocationSheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName("LocationCodes");
var depot01 = LocationSheet.getRange(3,4).getValue();
var depot02 = LocationSheet.getRange(4,4).getValue();
//if source is recorded as '08 - Maitenance Request System', the recipient is maintenance deparment
if (source === "08 - Maintenance Request System"){
var recipient = email00;
//or depots as listed
} else if(location === depot01){
var recipient = email01;
} else if(location === depot02){
var recipient = email02; }
else {
//and send an email to the error catch all if no code supplied
var recipient = email00; //change as necessary
}
var sheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName('LOG');
var lastRow = sheet.getLastRow();
var lrp1 = lastRow+1
var targetRange = sheet.getRange(lastRow+1, 1, 1, 12).setValues([[timeStamp,date,source,location,reporter,user,hazard,details,description,priority,recipient,actiondate]]);
//Notification Page
app = UiApp.getActiveApplication().remove(0);
app.createVerticalPanel()
.setId('info')
.setVisible(true)
.setStyleAttribute('left', 10)
.setStyleAttribute('top', 10)
.setStyleAttribute('zIndex', '1')
.setStyleAttribute('position', 'fixed')
.setStyleAttribute('background', 'white')
.setHeight('400px')
.setStyleAttribute('text-align', 'center')
.setBorderWidth(1)
.setWidth('500px');
app.add(app.createLabel('Your submission has been added to the database & the Site Coordinator will be emailed with notification.'));
// app.add(app.createLabel('The details you entered were recorded as:'));
// app.add(app.createLabel('Username: [' +user+ '] Timestamp: [' +timeStamp+ '] Reporter: [' +reporter+ ']'));
// app.add(app.createLabel('Location: [' +location+ '] Hazard: [' +hazard+ '] Source: [' +source+ ']'));
// app.add(app.createLabel('You listed the priority as: [' +priority+ '], please be aware this may not be the same priority given by the Site Coordinator responsible.'));
app.add(app.createLabel('Please refresh this page to submit more entries'));
return app.close();
}
var sheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName("LOG");
var lastRow = sheet.getLastRow();
var lrp1 = lastRow+1
//Amend [getRange(lastRow+1, 1, 1, **)] integer to reflet number of headers being written if more added
var targetRange = sheet.getRange(lastRow+1, 1, 1, 12).setValues([[timeStamp,date,source,location,reporter,user,hazard,details,description,priority,recipient,actiondate]]);
var Body = 'A new [' +source+ '] log entry has been recorded at [' +location+ '], listed as [' + hazard+ ']. This form was submitted by [' +user+ '] with the timestamp [' +timeStamp+ '].'
}
The reason why it is adding last row twice is that because the following code is repeated after the loop:
var sheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName("LOG");
var lastRow = sheet.getLastRow();
var lrp1 = lastRow+1
//Amend [getRange(lastRow+1, 1, 1, **)] integer to reflet number of headers being written if more added
var targetRange = sheet.getRange(lastRow+1, 1, 1, 12).setValues([[timeStamp,date,source,location,reporter,user,hazard,details,description,priority,recipient,actiondate]]);
var Body = 'A new [' +source+ '] log entry has been recorded at [' +location+ '], listed as [' + hazard+ ']. This form was submitted by [' +user+ '] with the timestamp [' +timeStamp+ '].'
Hope that helps!
Thanks KRR, I sorted it with that and the moving of one of the "}" from after the notification page to just before it. The corrected code is:
var sheet = SpreadsheetApp.openById(itemSpreadsheetKey).getSheetByName('LOG');
var lastRow = sheet.getLastRow();
var lrp1 = lastRow+1
var targetRange = sheet.getRange(lastRow+1, 1, 1, 12).setValues([[timeStamp,date,source,location,reporter,user,hazard,details,description,priority,recipient,actiondate]]);
}
///// TRAIL NOTIFICATION PAGE FROM WITHIN SYSTEM FROM HERE /////
app = UiApp.getActiveApplication().remove(0);
app.createVerticalPanel()
.setId('info')
.setVisible(true)
.setStyleAttribute('left', 10)
.setStyleAttribute('top', 10)
.setStyleAttribute('zIndex', '1')
.setStyleAttribute('position', 'fixed')
.setStyleAttribute('background', 'white')
.setHeight('400px')
.setStyleAttribute('text-align', 'center')
.setBorderWidth(1)
.setWidth('500px');
app.add(app.createLabel('Your submission has been added to the database & the Site Coordinator will be emailed with notification.'));
// app.add(app.createLabel('The details you entered were recorded as:'));
// app.add(app.createLabel('Username: [' +user+ '] Timestamp: [' +timeStamp+ '] Reporter: [' +reporter+ ']'));
// app.add(app.createLabel('Location: [' +location+ '] Hazard: [' +hazard+ '] Source: [' +source+ ']'));
// app.add(app.createLabel('You listed the priority as: [' +priority+ '], please be aware this may not be the same priority given by the Site Coordinator responsible.'));
app.add(app.createLabel('Please refresh this page to submit more entries'));
return app.close();
}

Resources