如何使用PHP定义SQL函数?

huangapple go评论73阅读模式
英文:

how to define a SQL functions with PHP?

问题

以下是您要翻译的代码部分:

$levenshteinFunction = <<<'LEVENSHTEIN_FUNCTION'
CREATE FUNCTION LEVENSHTEIN(s1 VARCHAR(255) CHARACTER SET utf8, s2 VARCHAR(255) CHARACTER SET utf8)
  RETURNS INT
  DETERMINISTIC
  BEGIN
    DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
    DECLARE s1_char CHAR CHARACTER SET utf8;
    -- max strlen=255 for this function
    DECLARE cv0, cv1 VARBINARY(256);

    SET s1_len = CHAR_LENGTH(s1),
        s2_len = CHAR_LENGTH(s2),
        cv1 = 0x00,
        j = 1,
        i = 1,
        c = 0;

    IF (s1 = s2) THEN
      RETURN (0);
    ELSEIF (s1_len = 0) THEN
      RETURN (s2_len);
    ELSEIF (s2_len = 0) THEN
      RETURN (s1_len);
    END IF;

    WHILE (j <= s2_len) DO
        SET cv1 = CONCAT(cv1, CHAR(j)),
          j = j + 1;
    END WHILE;

    WHILE (i <= s1_len) DO
        SET s1_char = SUBSTRING(s1, i, 1),
          c = i,
          cv0 = CHAR(i),
          j = 1;

      WHILE (j <= s2_len) DO
          SET c = c + 1,
            cost = IF(s1_char = SUBSTRING(s2, j, 1), 0, 1);

        SET c_temp = ORD(SUBSTRING(cv1, j, 1)) + cost;
        IF (c > c_temp) THEN
          SET c = c_temp;
        END IF;

        SET c_temp = ORD(SUBSTRING(cv1, j+1, 1)) + 1;
        IF (c > c_temp) THEN
          SET c = c_temp;
        END IF;

        SET cv0 = CONCAT(cv0, CHAR(c)),
            j = j + 1;
      END WHILE;

      SET cv1 = cv0,
          i = i + 1;
    END WHILE;

    RETURN (c);
  END $$
$levenshteinratioFunction = <<<'LEVENSHTEINRATIO_FUNCTION'
CREATE FUNCTION LEVENSHTEINRATIO( s1 VARCHAR(255), s2 VARCHAR(255) )
  RETURNS INT
  DETERMINISTIC
  BEGIN
    DECLARE s1_len, s2_len, max_len INT;
    SET s1_len = LENGTH(s1), s2_len = LENGTH(s2);
    IF s1_len > s2_len THEN
      SET max_len = s1_len;
    ELSE
      SET max_len = s2_len;
    END IF;
    RETURN ROUND((1 - LEVENSHTEIN(s1, s2) / max_len) * 100);
  END $$
$LS = [
    'show function status;',
    'drop function IF EXISTS levenshtein ;',
    'drop function IF EXISTS levenshteinratio ;',
    'DELIMITER $$',
    $levenshteinFunction,
    'DELIMITER ;',
    'DELIMITER $$',
    $levenshteinratioFunction,
    'DELIMITER ;',
    'show function status;'
];
$mysqliConnection = new \mysqli(
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname']
);
$output = '';
foreach ($LS as  $lsStatement) {
    $output .= '*** '.$lsStatement.' ***'.PHP_EOL;
    $result = $mysqliConnection->query($lsStatement);
    $output .= print_r($result, true). PHP_EOL.PHP_EOL;
}
$mysqliConnection->close();
echo '<pre>'.PHP_EOL.$output.'</pre>';
die();

希望这有所帮助。如果您需要进一步的帮助,请随时告诉我。

英文:

With mySQL/MariaDB you can define your own function for the SQL server (mySQL-manual).
If you create such a function it is available in later queries (until it is dropped / server reset? / only for that user? / ...)

As I want to use such a function in my PHP code I want to make sure the function is available. So I want to define the function from PHP. In this way the function is on every server my PHP code is executed even if I have no access to the DB-server with a CLI.

While I can define the function in a SQL-console (mysql), all attempts from PHP fail.

As I build an extension for TYPO3 I first tried the queryBuilder, which failed with syntax error. (probably the doctrine layer was not prepared for the very special syntax used for creating functions).

Then I used mysqli and all statements returned no error. but the functions were not created. 如何使用PHP定义SQL函数?

$levenshteinFunction = <<<'LEVENSHTEIN_FUNCTION'
CREATE FUNCTION LEVENSHTEIN(s1 VARCHAR(255) CHARACTER SET utf8, s2 VARCHAR(255) CHARACTER SET utf8)
  RETURNS INT
  DETERMINISTIC
  BEGIN
    DECLARE s1_len, s2_len, i, j, c, c_temp, cost INT;
    DECLARE s1_char CHAR CHARACTER SET utf8;
    -- max strlen=255 for this function
    DECLARE cv0, cv1 VARBINARY(256);

    SET s1_len = CHAR_LENGTH(s1),
        s2_len = CHAR_LENGTH(s2),
        cv1 = 0x00,
        j = 1,
        i = 1,
        c = 0;

    IF (s1 = s2) THEN
      RETURN (0);
    ELSEIF (s1_len = 0) THEN
      RETURN (s2_len);
    ELSEIF (s2_len = 0) THEN
      RETURN (s1_len);
    END IF;

    WHILE (j <= s2_len) DO
        SET cv1 = CONCAT(cv1, CHAR(j)),
          j = j + 1;
    END WHILE;

    WHILE (i <= s1_len) DO
        SET s1_char = SUBSTRING(s1, i, 1),
          c = i,
          cv0 = CHAR(i),
          j = 1;

      WHILE (j <= s2_len) DO
          SET c = c + 1,
            cost = IF(s1_char = SUBSTRING(s2, j, 1), 0, 1);

        SET c_temp = ORD(SUBSTRING(cv1, j, 1)) + cost;
        IF (c > c_temp) THEN
          SET c = c_temp;
        END IF;

        SET c_temp = ORD(SUBSTRING(cv1, j+1, 1)) + 1;
        IF (c > c_temp) THEN
          SET c = c_temp;
        END IF;

        SET cv0 = CONCAT(cv0, CHAR(c)),
            j = j + 1;
      END WHILE;

      SET cv1 = cv0,
          i = i + 1;
    END WHILE;

    RETURN (c);
  END $$
LEVENSHTEIN_FUNCTION;
                
$levenshteinratioFunction = <<<'LEVENSHTEINRATIO_FUNCTION'
CREATE FUNCTION LEVENSHTEINRATIO( s1 VARCHAR(255), s2 VARCHAR(255) )
  RETURNS INT
  DETERMINISTIC
  BEGIN
    DECLARE s1_len, s2_len, max_len INT;
    SET s1_len = LENGTH(s1), s2_len = LENGTH(s2);
    IF s1_len > s2_len THEN
      SET max_len = s1_len;
    ELSE
      SET max_len = s2_len;
    END IF;
    RETURN ROUND((1 - LEVENSHTEIN(s1, s2) / max_len) * 100);
  END $$
LEVENSHTEINRATIO_FUNCTION;

$LS = [
    'show function status;',
    'drop function IF EXISTS levenshtein ;',
    'drop function IF EXISTS levenshteinratio ;',
    'DELIMITER $$',
    $levenshteinFunction,
    'DELIMITER ;',
    'DELIMITER $$',
    $levenshteinratioFunction,
    'DELIMITER ;',
    'show function status;'
];

/*
// first attempt
$output ='';
foreach ($LS as  $lsStatement) {
    $output .= '*** '.$lsStatement.' ***'.PHP_EOL;
    $levenshteinQuery = $this->createQuery();
    $levenshteinQuery->statement($lsStatement,[]);
    $result = $levenshteinQuery->execute();
    $output .= print_r($result->toArray(), false). PHP_EOL.PHP_EOL;
    //\TYPO3\CMS\Core\Utility\DebugUtility::debug($result,'result of '.substr($lsStatement,0,30));
    unset($levenshteinQuery);
}
echo $output;
die();
*/

$mysqliConnection = new \mysqli(
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['password'],
    $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname']
);

$output = '';
foreach ($LS as  $lsStatement) {
    $output .= '*** '.$lsStatement.' ***'.PHP_EOL;
    $result = $mysqliConnection->query($lsStatement);
    $output .= print_r($result, true). PHP_EOL.PHP_EOL;
}
$mysqliConnection->close();
echo '<pre>'.PHP_EOL.$output.'</pre>';
die();

This is a debug-output with termination (die) as the later query failed anyway with the error undefined function levenshtein.

as said: the later query works if I define the functions in a SQL CLI and without recreation of the function.

答案1

得分: 2

DELIMITER命令不被服务器端SQL解析器识别,它是MySQL客户端的内置命令之一。它由客户端预解析,而不是发送到服务器。所以在通过API执行SQL时,你不能使用DELIMITER

DELIMITER的目的是澄清SQL语句之间的分隔位置,当像CREATE FUNCTION这样的语句包含分号字符时。分隔符是客户端检测语句结束的方式,因此它不会在你尝试定义的函数体内第一个处结束语句。

如果你使用API,就不需要解决这种歧义。只需提交整个CREATE FUNCTION语句,一直到最后的END,然后执行它。

你的代码显示运行一系列语句,如DROP FUNCTION等,作为多个查询。但在定义存储过程时,你不能这样做。

为什么不行?MySQL的API支持多个查询。

因为MySQL的多查询不支持DELIMITER。多查询模式下的SQL语句仅由分隔,并且这是不可配置的。

https://dev.mysql.com/doc/c-api/8.0/en/c-api-multiple-queries.html

MySQL还支持执行包含多个语句的字符串,这些语句由分号(;)字符分隔。

因此,实际上,你不能使用多查询来定义具有复合语句的存储过程,因为这些语句在存储过程的主体中需要分号,而不是整个CREATE语句的终止符。

无论如何,你不需要多查询,因为你应该只需分别在单独的API调用中执行每个语句。这需要多写几行代码,但这是唯一执行这些语句的方法。

你还问过:

如果创建这样的函数,它是否在后续查询中可用(直到被删除/服务器重置?/仅对该用户可用?/ ...)

存储函数是持久的,就像表一样。它们将保留在你的MySQL实例中,直到你执行DROP FUNCTION,或者如果你删除了定义该函数的整个模式。重新启动MySQL服务器不会删除该函数。

具有访问定义函数的模式的权限的任何用户都可以使用这些函数。

上面的评论说你不应该在应用程序代码中创建函数。我认为这有点夸张。你可以在应用程序代码中创建函数,例如,如果你希望应用程序始终确保函数存在。但你可能会使用CREATE FUNCTION IF NOT EXISTS ...,以便它仅在创建函数一次时才创建它。

通常,大多数应用程序不包括这个。它们被设计成假设在应用程序运行之前你已经创建了表和函数。这通常是一个好主意,因为它减少了应用程序中的代码量。

英文:

The DELIMITER command is not recognized by the server-side SQL parser, it is one of the builtin commands for the MySQL client. It is pre-parsed by the client, not sent to the server. So you can't use DELIMITER when you execute SQL via API.

The purpose of DELIMITER is to clarify where the separation between SQL statements, when a statement such as CREATE FUNCTION contains semicolon characters. The delimiter is how the client detects the end of the statement, so it doesn't end the statement at the first ; it sees within the body of the function you're trying to define.

You don't need to resolve this ambiguity if you use the API. Just submit the whole CREATE FUNCTION statement, up to the last END, and it executes.

Your code shows running a series of statements like DROP FUNCTION and so on as a multi-query. You can't do this when defining stored routines.

Why not? The MySQL API does support multi-query.

Because MySQL's multi-query doesn't support DELIMITER. SQL statements in multi-query mode are separated by ; only, and this is not configurable.

https://dev.mysql.com/doc/c-api/8.0/en/c-api-multiple-queries.html

> MySQL also supports the execution of a string containing multiple statements separated by semicolon (;) characters.

So effectively, you can't use multi-query to define stored routines that have compound statements, because these need semicolons within the body of the routine, not as the terminator of the whole CREATE statement.

You don't need multi-query anyway, because you should just execute each statement individually, in separate API calls. It's a few more lines of code, but it's a moot point because this is the only way to execute these statements.

You also asked:

> If you create such a function it is available in later queries (until it is dropped / server reset? / only for that user? / ...)

Stored functions are persistent, like tables. They will stay in your MySQL instance until you DROP FUNCTION, or if you drop the whole schema in which the function is defined. Restarting the MySQL Server does not drop the function.

Functions are available for any user who has privileges to access the schema in which the function is defined.

A comment above says you shouldn't create functions in your application code. I think this is overstating it. You may create functions in application code, for example if you want an application that always ensures the function exists. But you would probably use CREATE FUNCTION IF NOT EXISTS ... so it only creates the function once.

Typically, most applications don't include this. They are designed to assume you have created tables and functions prior to the application running. This is usually a good idea, because it reduces the amount of code in the application.

答案2

得分: 1

你可以使用连接类来构建自己的查询。这有时很有帮助,特别是如果你想使用QueryBuilder不支持的MySQL函数。
查看示例链接:https://github.com/in2code-de/lux/blob/develop/Classes/Domain/Repository/SearchRepository.php#L53

英文:

You could use the connection class to build your own queries. This is sometimes helpful if you want to use MySQL functions that are not supported by the QueryBuilder.
See an example: https://github.com/in2code-de/lux/blob/develop/Classes/Domain/Repository/SearchRepository.php#L53

huangapple
  • 本文由 发表于 2023年6月12日 18:21:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76455687.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定