Prevent MySQL query from timing out in Apps Script - mysql

Our client's online shop uses Opencart, for marketing purposes they wanted to sync Opencart orders automatically with Google Sheets. I modified a script in google sheets to extract customer orders from the past year (based on https://www.transpacific-software.com/blog/opencart-to-google-docs-pull-and-sync-data-auto-through-scripting and https://gist.github.com/pradeepbheron/e6129c9fd9bc74e814d0)
The SQL query is:
SELECT order_id, firstname AS first_name, lastname AS last_name,
email, date_added AS order_date, CONCAT("£", FORMAT(total,2))
order_value, payment_address_1 AS billing_address_1,
payment_address_2 AS billing_address_2, payment_city AS
billing_city, payment_postcode AS billing_postcode,
payment_country AS billing_country, items_purchased FROM
(
SELECT order_id, firstname, lastname, email, date_added,
total, payment_address_1, payment_address_2, payment_city,
payment_postcode, payment_country
FROM ocbw_order
GROUP BY order_id
) AS A
JOIN (
SELECT order_id AS product_order_id, GROUP_CONCAT(name
SEPARATOR ", ") AS items_purchased
FROM ocbw_order_product
GROUP BY product_order_id
) AS B
ON A.order_id=B.product_order_id
WHERE date_added >= DATE_SUB(NOW(),INTERVAL 1 YEAR)
AND firstname != ''
It runs fine in phpMyAdmin but Google Script Editor generates an "Exceeded maximum execution time" error.
It looks like there are 7357 rows (exported from myPhpAdmin). Is there a better way to write the query? Also I am trying to rename the column headers but can only two works, i.e:
GROUP_CONCAT(name SEPARATOR ", ") AS items_purchased
and
CONCAT("£", FORMAT(total,2)) order_value
Any thoughts
QUICK UPDATE: Fri Nov 9 11:54:03 2018
1) As request by #ThisGuyHasTwoThumbs, here is a screenshot of the explain table result
2) I looked into the Best Practices doc mentioned by #bcperth. I tried to rewrite the google sheets script but ran into issues.
Here is the amended script.
function p1MySQLFetchData() {
// Change it as per your database credentials
var conn =
Jdbc.getConnection('jdbc:mysql://[dbHostIp]:3306/[dbName]',
'[dbUsername]', '[dbPassword]');
var stmt = conn.createStatement();
var start = new Date(); // Get script starting time
//change table name as per your database structure
var rs = stmt.executeQuery('[sqlQuery]');
// It sets the limit of the
// maximum nuber of rows in a ResultSet object
// Returns the currently active spreadsheet
var doc = SpreadsheetApp.getActiveSpreadsheet();
var cell = doc.getRange('a1');
var row = 0;
// Mysql table column name count.
var getCount = rs.getMetaData().getColumnCount();
// Create array to hold mysql data
var tempArray = [];
// get row and column count for later
var colCount = getCount;
// ATTEMPT TO GET ROW COUNT 1
//rs.last();
//var rowCount = rs.getRow();
//rs.beforeFirst(); // resets rs cursor
//Logger.log(RowCount);
// DOESN'T WORK! result => ReferenceError: "RowCount" is not
// defined. (line 28, file "Code")
// ATTEMPT TO GET ROW COUNT 2
//var rsCount = stmt.executeQuery('SELECT COUNT(*) AS rowcount FROM
//[sqlQuery]);
// It sets the limit of the maximum number of rows in a ResultSet
// object
//rsCount.next();
//var rowCount = rsCount.getString("rowcount");
//Logger.log(rowCount);
// DOESN'T WORK! result => 0
// Build TempArray using MySQL data
for (var i = 0; i < getCount; i++){
tempArray[0][i] = rs.getMetaData().getColumnName(i+1);
// DOESNT WORK => ERROR
// TypeError: Cannot set property "0.0" of undefined to "order_id".
// (line 39, file "Code")
//Logger.log(rs.getMetaData().getColumnName(i+1));
}
var row = 1;
while (rs.next()) {
for (var col = 0; col < rs.getMetaData().getColumnCount();
col++) {
tempArray[row][col] = rs.getString(col + 1);
//Logger.log(rs.getString(col + 1));
}
row++;
}
// BELOW DOESNT AS I CANT GET A ROW COUNT (SEE ABOVE)
// Fill Spreadsheet from tempArray
//for (var row = 0; row < rowCount; row++) {
//for (var col = 0; col < colCount; col++) {
//cell.offset(row, col).setValue(tempArray[row][col + 1]);
//}
// }
rs.close();
stmt.close();
conn.close();
var end = new Date(); // Get script ending time
Logger.log('Time elapsed: ' + (end.getTime() - start.getTime()));
// To generate script log. To view log click on View -> Logs.
}
But as you can see from the comments, I get loads of errors. Am not sure what to do next.
UPDATE Fri Nov 30 15:26:14 2018
In answer to #Tedinoz comment below.
PhpMyAdmin generates 6862 results and the query took 13.3074 seconds.
When I ran the script in Googles Script Editor, it took around 2 minutes 30 to complete and only pulls 6348 records (92.5%). The records stop after 3rd October 2018.
Regarding your suggestions:
1) I tried running a modified script, setting the INTERVAL to:
1 MONTH => I get no results in Google (it should be 529)
2 MONTH => I get 14 results (should be 1029)
3 MONTH => I get 299 results (should be 1669).
They all took about 4-7 second in myPhpAdmin vs 5 - 20 seconds for Google Script Editor
2) Do you mean exporting a csv from phpMyAdmin and importing to Google Sheets? Well I did that and it works fine.
Another thing I have noticed is that the order_id's in Google Sheets don't match that from phpMyAdmin. Weird.

Related

Google Script MYSQL QUERY with Variables from google sheets

Ok so I've been pretty much learning on my own and I ran into a problem that I cant seem to find a solution for.
The main goal is I have a google sheet with a start and end date that the user can change, the script pulls those dates and uses them in a MySQL query to pull the data between that date range.
Example: start date = 10/1/2021, end date = 10/22/21
Query: "select * from Table, where table.date >= start date AND table.dat <= end date"
See my code example below:
======================================================
var spreadsheet = SpreadsheetApp.getActive();
var sheet = spreadsheet.getSheetByName('DR_Campaign_Report');
var getStartDate = sheet.getRange(1,2).getValue();
var startDate = Utilities.formatDate(getStartDate,"GTM","MM/dd/yyyy");
var getEndDate = sheet.getRange(1,5).getValue();
var endDate = Utilities.formatDate(getEndDate,"GTM","MM/dd/yyyy");
var conn = Jdbc.getConnection(url, username, password);
var stmt = conn.createStatement();
var results = stmt.executeQuery
( 'SELECT m.Campaign as "Campaign",\n' +
'count(m.Campaign) as "Leads",\n' +
'count(m.Duplicate) as "Dups",\n' +
'count(m.Campaign) - count(m.Duplicate) as "Valid Leads",\n' +
'count(m.AppSet) as "Appts",\n' +
'SUM(IF(m.ZepID != "",1,0)) as "Transferred Appts"\n' +
'FROM intakeMani m\n' +
'WHERE date(m.IncomingDate) >= date('startDate') and date(m.IncomingDate) <= date('endDate')\n' +
'OR date(m.AppSet) >= date('startDate') and date(m.AppSet) <= date('endDate')\n' +
'GROUP BY m.Campaign with rollup'
);
The Error is here in the WHERE clause when its attempting to pull the google script variables startDate and endDate.
'WHERE date(m.IncomingDate) >= date('startDate') and date(m.IncomingDate) <= date('endDate')\n' +
'OR date(m.AppSet) >= date('startDate') and date(m.AppSet) <= date('endDate')\n' +
I attempted the double "'startDate'" and is still errors. see attached pic.
I fixed it, I had double " in the var getStartDate/GetEndDate fields and need to add " to the query "'+startdate+'"
Thank you for your help.

Script copies row data to another sheet, is only partially working

I have a script to move a row of data in an employee schedule spreadsheet (thanks to help from the Sheets reddit) it is working but has stopped finding one part of the data.
Spreadsheet here: https://docs.google.com/spreadsheets/d/10yLJ_NyFasGhlRt2LsXO2CC804Zf5JLvwUisgytQOhM/edit?usp=sharing
Once the Finished column 63 is set to "YES" it copies the data (only from the required columns), locates the employee's name based on whose week is marked "x" and moves them to the Finished Sheet.
The code used to work fully but now it isn't finding "x" or grabbing the employee's name.
I've had to update the column numbers in the script a few times as we've gotten more employees, it's possible I've broken it doing that.
(Any blank columns do have data in my real spreadsheet I've just left them blank here as it's not relevant to the script)
Would really appreciate any advice! Thank you!
[Full code in spreadsheet]
if (ecol == 63 && ssh.getName() == 'SCHEDULE' && rng.getValue() == 'YES') { //if Schedule!63 is YES
var data = []; //output data
var rowN = rng.getRow();
var rowV = ssh.getRange(rowN,1,1,63).getDisplayValues();
var row = rowV[0]; //get edited row
var colX = row.indexOf('x')+1; //find X
var offset = colX - ((colX-2) % 5 ) // offset to the first column
var emp = ssh.getRange(1,offset).getValue(); //get employee name
var dd = Utilities.formatDate(new Date(), "GMT+10", "dd/MM/yyyy") //generate date finished
data.push(row[1],row[2],row[3],dd,row[5],emp); //get row
dsh.appendRow(data); //move row
ssh.deleteRow(e.range.rowStart); //delete from schedule
}
}
I believe this var offset = colX - ((colX-2) % 5 ) // offset to the first column
should be this var offset = colX - ((colX+2) % 5 ) // offset to the first column
This is what's causing you to not find employees because the employees are in a merge group of cells which always puts it's value in the left most cell for horizontal merging.

how to make duplicate rows appear in mysql query results node.js

I am working on a card game to use on Discord that allows up to 3 copies of any one card to be used. I can add the first copy of a card to my mysql database and get the result without a problem, but as soon as I add another copy of a card that already exists in the deck, those copies do not appear in row results when the results from the first query are queried in a second query.
var sql1 = `SELECT * FROM deck_cards WHERE deckid = '${deckid}'`;
con.query(sql1, function (err, rows, fields) {
console.log(rows)
if (!err) {
var count = rows.length;
if (count) {
var arr_cardid = rows.map(i => i.cardid);
var sql2 = `SELECT cardname FROM card_info WHERE cardid in ('${arr_cardid.join("','")}') `;
console.log(sql2)
con.query(sql2, function (err, rows, fields) {
if (!err) {
var count = rows.length;
for (i = 0; i < count; i++) {
arr_cardname = []
arr_cardname.push(rows[i].cardname)
// list = arr_cardname.join(", ")
console.log(arr_cardname)
}
}
})
}
}
})
Table in mysql: https://i.gyazo.com/fb165c1f402f5234a3501e09d715b965.png
Deckid 1 is the deck I query for in the first query. Notice cardid 2 should have 3 total copies of it in the deck. So, I should see 3 copies of cardid 2 when I display the list of cards in that deck. The copies are put into the second query (https://i.gyazo.com/8b6c8426daee2611766242961883ec37.png) However, I only see 1 result for those copies in the output (list), the first one found in row 3 (index 2) of the table. The other 2 copies added at the very end of the table do not appear from the second query results. Is there a way to make the duplicate rows appear in the results of the second query? I have seen union, inner joins, count and such that would be used for a workaround, but I figured I'd ask if there is a simpler and more efficient way to make this work.
Is this an issue with the for loop or the second .join(", ") maybe?

update mysql row if timestamp is within last 5 mins

i'm trying to create a stats bot for discord but i am having some issues trying to get part of it to work.
What i am trying to do is record the number of messages sent per channel at 5 min intervals. In my bot i have the following code:
// on message events
bot.on("message", async message => {
if(message.author.bot) return;
if(message.channel.type === "dm") return;
//update channel stats
connection.query(`SELECT * FROM channel_stats WHERE channel_id = '${message.channel.id}' AND date BETWEEN timestamp(DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AND timestamp(NOW())`, (err, rows) => {
if(err) throw err;
let sql;
if(rows.length < 1) {
sql = `INSERT INTO channel_stats (channel_id, date, channel_name, channel_message_count) VALUES ('${message.channel.id}', NOW(), '${message.channel.name}', 1)`;
} else {
let channel_message_count = rows[0].channel_message_count;
sql = `UPDATE channel_stats SET channel_message_count = ${channel_message_count + 1}, channel_name = '${message.channel.name}' WHERE channel_id = '${message.channel.id}'`;
};
connection.query(sql)
});
however, the bot always inserts a new row and never updates an existing one.
I ran the sql query SELECT * FROM channel_stats WHERE channel_id = 'xxxxxxxxxxxxx' AND date BETWEEN timestamp(DATE_SUB(NOW(), INTERVAL 5 MINUTE)) AND timestamp(NOW()) manually via phpmyadmin and this seems to be working correctly - only returns rows created within the last 5 mins.
I'm struggling to understand why the bot is constantly adding new rows. Any help would be appreciated.
Thanks!
managed to resolve this myself, had the channel_id row as int(11) which was causing the channel_id to be truncated.
changed it to VARCHAR(30) and it's working perfectly now.

Query Large Data from Fusion Tables from Google Apps Script

I've loaded a 66 MB csv file to Fusion Tables. It's about 475k rows long and 12 columns wide.
I'm using Google Apps Script and trying to query the data within there.
One of the columns is the name of the person who that data belongs to, for instance, Joe.
If I want to pull all of Joe's data out so I can display it to him in a nice format, I'm using this query:
var tableId = my_table_id;
var sql1 = "SELECT * FROM " + tableId + " WHERE 'User' = 'Joe'";
var result = FusionTables.Query.sql(sql1,{hdrs : false});
The issue is that Joe has about 52k lines of data. I want to return it so I can load it to a datable and the user can sort through it and view all of the data. I get one of two errors:
If I run the query as above I get:
Response Code: 413. Message: response too large.
If I just try to select it all (SELECT * FROM tableId), I get:
Response size is larger than 10 MB. Please use media download
For media download, I've tried specifying alt : 'media' in the parameters, but I don't think that works within Google Apps script (I can't find documentation on it anywhere).
I have also tried looping through the queries, so select * limit 0,1000, then select * limit 1001,2000, ect. However, fusion tables SQL doesn't seem to support that either.
At this point, I may just leave the CSV in my drive, parse it in on the fly, but that's my last resort. Any advice would be appreciated!
So I think I figured this out. I'm sure it's not the most elegant solution, but here goes:
I run a quick query to check the count() for Joe to see how many records there are and only run loops if needed. I set the max to 40,000 records:
var total_rows_query = "SELECT COUNT() FROM " + tableId + " WHERE 'User' = " + username;
var total_rows = FusionTables.Query.sql(total_rows_query,{hdrs : false}).rows[0][0];
If the total rows are greater than I want, I use the OFFSET and LIMIT parameters to structure the queries:
max_rows = 40000;
if(total_rows > max_rows){
var counter = 0;
//adding in a zero to the ranges since the last query will be the offset of 0, meaning all of them
var ranges = [0]
while(counter + chunk_size < total_rows){
counter = counter + chunk_size;
ranges.push(counter)
}
ranges.push(total_rows)
//Now ranges is an array with zero at the beginning, and counting up by the chunk size I want, ending with the total_rows for the user as the last oen
//This is the array that will be output after concating
var output = []
//looping through the array, setting the offset to the first item, and the limit to the next item minus the first
for(i=0;i<ranges.length-1;i++){
var offset = ranges[i]
var limit = ranges[i+1] - offset
var query = "SELECT * FROM " + tableId + " WHERE 'User' = '" + username + "' OFFSET " + offset + " LIMIT " + limit;
output = output.concat(FusionTables.Query.sql(query,{hdrs : false}).rows)
}
}else{
//if the count is less or equal to the chunk size, just run the one query
var query = "SELECT * FROM " + tableId + " WHERE 'User' = " + username;
var output = FusionTables.Query.sql(query,{hdrs : false}).rows
}
The last thing to note is that if the username is two words, for instance 'John Smith', you may need to add in quotes around your username, so instead of
var total_rows_query = "SELECT COUNT() FROM " + tableId + " WHERE 'User' = " + username;
It would be:
var total_rows_query = "SELECT COUNT() FROM " + tableId + " WHERE 'User' = '" + username + "'";
I spend the last two days trying to figure this out, so I hope it helps someone out there!