MySQL 查询错误“非法小时值”导致 Google Apps 脚本中出现循环和写入问题

Posted

技术标签:

【中文标题】MySQL 查询错误“非法小时值”导致 Google Apps 脚本中出现循环和写入问题【英文标题】:MySQL query error "Illegal hour value" causing loop and write issues in Google Apps Script 【发布时间】:2020-01-29 08:27:21 【问题描述】:

我对这两种语言都不精通,所以请多多包涵。我正在尝试将包含 100 行的完整表从远程 mysql 数据库中提取到 Google 表格中。我已经设法解决了我一直遇到的所有问题,但最终感觉卡住了。看来我在循环期间从 SQL 查询中得到的“非法小时值”错误是主要问题。

MySQL 数据库中的一列是“持续时间”,不幸的是,它包含的持续时间可能超过 23:59:59。我只能调用该过程,不能对表进行任何更改。我明白了

“非法小时值”

行的持续时间超过 24 小时(例如,70:00:00)时出错。我试图通过使用 try-catch 来跳过错误并继续在 Google 表格上书写来简化事情,但后来我得到了

“数据中的列数与范围内的列数不匹配。数据有X但范围有Y”

最后 sheet.getRange() 行中的错误。

我也无法弄清楚在执行 MySQL 查询时如何传递多个语句。我试图理解 addBatch 和其他一些东西,但它对我来说太复杂了。也许查询有一个更简单的解决方案,或者这可能是唯一的解决方案,因为如果我还可以在 CALL 查询之后添加一个 CONCAT 查询以在数据进入循环之前将“持续时间”列转换为字符串,它可能会起作用。

以下代码已更新以包含解决方案:

function getData3(query, sheetName) 
  
  //MySQL (MariaDB) connection and statements.
  var user = '';
  var userPwd = '';
  var url = 'jdbc:mysql://remote.server.example.com/database_name';
  var conn = Jdbc.getConnection(url, user, userPwd);
  var stmt2 = conn.createStatement();
  stmt2.setMaxRows(100);
  var rs2 = stmt2.executeQuery('CALL spdAdminGetPIREPS(api_key)');
  //Logger.log(rs2)

  //Function to convert raw binary data to string to be used for durations >23:59:59.
  function byteArrToString(byteArr)
    return Utilities.newBlob(byteArr).getDataAsString();
  

  //Setting up spreadsheet, results array, and cell range.
  var doc = SpreadsheetApp.openById("id");
  var sheet = doc.getSheetByName("sheet_name");
  var results = [];
  var cell = doc.getRange('a1');
  var row = 0;

  //Loop to get column names.
  cols = rs2.getMetaData();
  colNames = [];
  for (i = 1; i <= cols.getColumnCount(); i++ ) 
    //Logger.log(cols.getColumnName(i));
    colNames.push(cols.getColumnName(i));
  
  results.push(colNames);

  //Loop to get row data, catch type errors due to duration >23:59:59 and fix it.
  var rowCount = 1;
  while(rs2.next()) 
    curRow = rs2.getMetaData();
    rowData = [];
    for (i = 1; i <= curRow.getColumnCount(); i++) 
      try 
      rowData.push(rs2.getString(i));
       catch (e)
      var bytes = rs2.getBytes(i);
      rowData.push(byteArrToString(bytes)); //Pushes converted raw binary data as string using function defined above.
      //Logger.log(JSON.stringify(rs2.getBytes(i))); //To see the raw binary data returned by getBytes() for durations >23:59:59 that throw an error.
        continue;
      
    
    results.push(rowData);
    rowCount++;
  

  //Write data to sheet.
  sheet.getRange(1, 1, rowCount, cols.getColumnCount()).clearContent();
  sheet.getRange(1, 1, rowCount, cols.getColumnCount()).setValues(results);

  //Close result set, conn, and statement.
  //Logger.log(results);
  rs2.close();
  stmt.close();
  conn.close();

我知道这两个单独的语句,看起来都很荒谬,但它们似乎有效,因为我不再收到查询的“无数据库”错误。更简单的单行 JDBC 连接器不适合我,因此连接到 MySQL 服务器 (Mariadb) 的当前格式。

如果表格中没有超过 24 小时的持续时间,则代码会运行并成功将整个表格写入 Google 表格。

总而言之:

如果我不使用 try-catch,循环会因错误而停止。如果我使用 try-catch 并继续,则会收到列数不匹配错误。

最终目标是调用该过程并将整个表格写入 Google 表格。跳过我可能可以使用的有问题的单元格,但我绝对喜欢获取所有数据。我可能在这里遗漏了一些琐碎的事情,但任何方向或帮助将不胜感激。提前致谢!

更新: 我在这里找到了this 问题的答案,但无法弄清楚如何在我的情况下使用它。我认为如果我可以传递多个查询,我应该能够在CALL 查询之后发送CONCAT 查询,以将“持续时间”列从Datetime(我相信)转换为string

更新 2: @TheMaster 针对try-catch 的解决方案有助于跳过有问题的单元格并继续编写其余部分。我很想找到一种方法将所有持续时间(整列或&gt;23:59:59)转换为string 以捕获所有数据。

更新 3: 根据@TheMaster 的建议,使用getInt() 而不是getString() 部分可以通过返回小时而不是分钟来工作(例如,如果持续时间为34:22:00,则返回34)。在使用getString() 时需要一种转换的方法。

更新 4(已编辑): 使用getBytes(),返回的值为:[51,52,58,52,56,58,48,48] for 34:48:00[51,55,58,48,48,58,48,48] for 37:00:00[56,55,58,48,48,58,48,48] for 87:00:00[49,53,49,58,51,53,58,48,48] for 151:35:00 这意味着:[48,49,50,51,52,53,54,55,56,57,58] 对应于[0,1,2,3,4,5,6,7,8,9,:]。如何合并此转换? 使用getLong(),返回的值为:34 for 34:48:00,转换为持续时间 -> 816:0037 for 37:00:00,转换为持续时间 -> 888:008787:00:00,转换为持续时间 -> 2088:00

更新最终: @TheMaster 修改后的答案通过获取原始二进制数据并将持续时间转换为字符串 &gt;23:59:59 解决了该问题。上述代码已更新以反映所有修改;它的工作原理如上所述。

【问题讨论】:

有什么理由不想在本专栏中使用text @ϻᴇᴛᴀʟ 我想从查询结果中转换该列的类型,但我不知道如何。这是你的意思还是有其他方法? 我也尝试添加getMaxColumns() 以避免列数错误。我在代码底部的两行 sheet.getRange() 之前添加了它。不知道如何最好地将它添加到任何地方以及是否可以解决问题。正在努力。 能否添加您创建的CREATE PROCEDURE spdAdminGetPIREPS @TheMaster,不幸的是,我只能看到连接到数据库时的过程,而不是他们查询的表。我想知道我是否能找到使用的CREATE PROCEDURE 【参考方案1】:

您当前使用 MySQL 连接器,即使 TIME 值可以从 '-838:59:59.999999' 到 '838:59:59.999999',MySQL 驱动程序在获取 TIME 类型的值时抛出异常不是在 0-24 小时范围内。 这在使用Resultset.getTime(i) 时可能有意义,但在使用Resultset.getString(i) 时则不然。

这可以使用noDatetimeStringSync=true 禁用,因此将URL 更改为jdbc:mysql://remote.server.example.com?noDatetimeStringSync=true

免责声明:我是 MariaDB java 驱动程序的维护者之一,但我建议将 MariaDB 驱动程序与 MariaDB 服务器一起使用(以及 MySQL 与 MySQL 服务器一起使用)。你会避免这个问题:)

顺便说一句,您可以直接在 URL jdbc:mysql://remote.server.example.com/database?noDatetimeStringSync=true 中设置数据库,避免查询。

【讨论】:

谢谢你,迭戈。我将数据库添加到 URL 中,并且可以正常工作 - 在它不起作用之前一定是做错了。但是,当我刚刚在最后添加 ?noDatetimeStringSync=true 时,我收到一条错误消息“以下连接属性不受支持:noDatetimeStringSync。”我按照您的建议更改了网址。我还查看了here 以了解如何使用 MariaDB 连接器 (jdbc:mariadb://),但是在尝试使用 Jdbc.getConnection(url) 时我得到了“不受支持的 JDBC 协议”。我错过了什么? 这是我试过的网址:jdbc:mariadb://remote.server.example.com/database?user=xxx&amp;password=xxx&amp;noDatetimeStringSync=true 刚刚找到this。它说 GAS 不支持 mariadb,我需要编写自己的 REST API 层。 嗯,谷歌应用有一个支持选项的白名单,并且不允许使用 noDatetimeStringSync。我建议创建一个问题issuetracker.google.com/bookmark-groups/78025 是的,会产生问题。我也无法传递其他参数。我将检查白名单并创建一个问题。谢谢,迭戈!【参考方案2】:

如果你想跳过失败的getString(),应该很容易:

try 
      rowData.push(rs2.getString(i));
       catch (e)
      rowData.push("");//Keeps array straight
        continue;
      

如果要将时间列转换为字符串,则需要在用于创建spdAdminGetPIREPSCREATE_PROCEDURE查询上使用CAST(time AS char)CONCAT('',time)

或者,您可以使用resultSet.getBytes() 获取原始二进制数据,然后使用Utilities 将其更改回stringblob

function byteArrToString(byteArr)
 return Utilities.newBlob(byteArr).getDataAsString();

把它当做

var bytes = rs2.getBytes();
rowData.push(byteArrToString(bytes));

如果可以直接获取blob,获取appsScriptBlob会更容易

var jdbcBlob = rs2.getBlob();
var blob = jdbcBlob.getAppsScriptBlob();
rowData.push(blob.getDataAsString());
jdbcBlob.free();

【讨论】:

谢谢你,@TheMaster!添加rowData.push(""); 会跳过有问题的单元格并继续写入其余部分。当然,我更愿意将有问题的单元格/列转换为字符串,但我没有对CREATE_PROCEDURE 的编辑访问权限——只有对CALL 存储过程的访问权限。无论如何,我可以在 GAS 中的 CALL 之后添加 CONCAT 吗? 到目前为止,我尝试在同一行使用 "\" 、 ";" 和 "," 分隔查询,但所有这些都会出现语法错误,即使我可以使用 @987654346 @当直接连接到数据库并发送简单查询时。我也尝试添加stmt2.executeUpdate('CONCAT("",time');,但仍然出现语法错误。我可能做错了,但无法在 GAS 中找出正确的方法。 @Bears 您需要了解 CREATE_PROCEDURE 语法并能够对其进行修改。如果没有,您可以直接使用SELECT bla,bla,time FROM.. 而不是调用存储过程吗?正如here 所写,我认为不可能从 CALL 中获取表,因此我认为不可能使用 CAST/CONCAT。免责声明:不是 sql 专家。 看来即使他们告诉我他们使用了什么CREATE_PROCEDURE,我也无权进行任何修改。我只能CALL 并使用该结果集。我什至没有table_name。我唯一的选择似乎能够将该结果集存储在一个数组中并在写入工作表之前进行修改,但这意味着我不能使用带有getString() 的循环来遍历 CALL 结果集,因为这会引发错误.也许是另一种循环方法来遍历没有getString() 的行? 我尝试了所有列出的get* 方法。 getInt() 确实有效,但只占用了小时(例如,在 35:23:00 返回 35)——所以我失去了分钟。 getLong() 也是如此。其他get* 方法不起作用。我正在尝试在使用 getString() 时找到一种 convert 的方法,但可能根本不可能。

以上是关于MySQL 查询错误“非法小时值”导致 Google Apps 脚本中出现循环和写入问题的主要内容,如果未能解决你的问题,请参考以下文章

为啥在 MySQL 查询中添加 '*' 会导致语法错误?

MySQL 视图中 LONGTEXT 字段的 JPA 本机查询导致错误

如何解决我的查询中缺少数组项导致的 MySQL 语法错误?

information_schema 查询期间 MySQL 致命错误(软件导致连接中止)

由于表名'CASE'导致MySQL查询中的问题

由于在 MySQL 中使用保留字作为表名或列名导致的语法错误