如何将参数传递给使用PL/pgSQL编写的查询?

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

How to pass parameters to a query written in PL/pgSQL?

问题

我想知道是否可以将参数传递给使用PL/pgSQL编写的查询?

我尝试了这个,但是出现了错误pq: got 1 parameters but the statement requires 0

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "host=localhost dbname=db user=user sslmode=disable password=pw")
	if err != nil {
		log.Fatal(err)
	}
	row := db.QueryRow(`
	DO $$
	BEGIN
		IF true THEN
			SELECT $1;
		END IF;
	END$$
	`, 1)
	var num int
	err = row.Scan(&num)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(num)
}

另一个相关的问题是,我想使用事务,但是sql包提供的API似乎在每次执行查询时都会连接到数据库。如果可能的话,我希望所有操作都在一个事务中执行。例如,使用Go语言,你应该像这样使用事务:

tx, err := db.Begin()
rows, err := tx.Query(sql1)
result, err := tx.Exec(sql2)
tx.Commit()

问题是,调用tx.Querytx.Exec会导致两次与PostgreSQL服务器的通信,如果我没记错的话。我想要实现的是将sql1sql2合并,将它们包装在BEGINEND之间,并在一次通信中执行它们。我的问题是:

  1. 你认为这是必要的吗?我想象中,在足够的流量下,性能差异可能是明显的,但我不确定。
  2. 如果是必要的,最佳的执行合并事务的方法是什么?创建一个函数,在PL/pgSQL中运行事务(因为我可能需要使用条件语句等)?
英文:

I wonder if it's possible to pass parameters to a query written in PL/pgSQL?

I tried this, but it failed with pq: got 1 parameters but the statement requires 0

package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "host=localhost dbname=db user=user sslmode=disable password=pw")
	if err != nil {
		log.Fatal(err)
	}
	row := db.QueryRow(`
DO $$
BEGIN
	IF true THEN
		SELECT $1;
	END IF;
END$$
`, 1)
	var num int
	err = row.Scan(&num)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(num)
}

Another related question is that I want to use transactions, but the APIs provided by the sql package seems to connect to db every time a query is executed in a tx. I'd like everything to be executed in one go if that's possible. For example, with go you are supposed to use transactions like this

tx, err := db.Begin()
rows, err := tx.Query(sql1)
result, err := tx.Exec(sql2)
tx.Commit()

The problem is that calling tx.Query and tx.Exec makes two trips to PostgreSQL server if I'm not wrong. What I want to achieve is to merge sql1 and sql2, wrap them inside BEGIN and END and execute them in one trip. And my question is that:

  1. Do you think it's necessary? I imagine that with enough traffic, the performance difference might be obvious, but I'm not sure.
  2. If so, what's the best way to execute this merged transaction? Create a function and run the transaction inside PL/pgSQL (since I might need to use conditional statements, etc)?

答案1

得分: 3

你之所以出错是因为 PL/pgSQL 应该在服务器端定义为“函数”或“过程”,但在你的情况下,它是从客户端调用的。下面是一个简单的示例,展示了如何定义和调用带有参数的函数:

CREATE OR REPLACE FUNCTION myadd(a integer, b integer) RETURNS integer AS $$
    BEGIN
        RETURN a + b;
    END;
$$ LANGUAGE plpgsql;

然后,你可以在客户端使用 SELECT 查询来调用带有参数的函数。请注意,即使你的函数包含 INSERT/UPDATE 操作,也必须使用 SELECT 语句来调用函数。

//...
a := 10
row := db.QueryRow(`SELECT * FROM myadd($1, $2)`, a, 130)
//...

关于事务和 PL/pgSQL 的问题。是的,使用 PL/pgSQL 可以减少网络流量。服务器端语言(PL/pgSQL)的几个优点包括:

  1. 消除客户端与服务器之间的往返通信。
  2. 不需要将中间结果传输到客户端,只传输最终结果。
  3. 避免多次解析查询(发送查询到服务器 -> 服务器解析查询 -> 执行数据库操作 -> 将结果返回给客户端,等等)。

在处理数据库(大数据)时,规则是“尽量避免在数据之间移动”,而 PL/pgSQL 符合这个规则。然而,有些情况下你不能(需要避免)使用 PL/pgSQL,例如数据库管理员/服务器所有者不允许服务器端编程(出于安全性/性能等原因)。

关于“函数”和“事务”的关系在手册中有明确说明:

不要将 PL/pgSQL 中的 BEGIN/END 用于与 SQL 命令中的事务控制相混淆。PL/pgSQL 的 BEGIN/END 只用于分组语句,它们不会开始或结束事务。函数和触发器过程总是在由外部查询建立的事务中执行 - 它们不能开始或提交该事务,因为它们没有执行的上下文。然而,包含 EXCEPTION 子句的块实际上形成了一个可以回滚的子事务,而不会影响外部事务。

总之,使用 PL/pgSQL 可能会提高性能。具体提升多少取决于情况。请记住,在使用 PL/pgSQL 后,你需要管理多个代码库,有时很难进行调试。

英文:

You got error because PL/pgSQL is supposed to be defined in server side as function or procedure, but in your case, its being called from client side. Below is a simple example on how to define and call the function with parameter(s):

CREATE OR REPLACE FUNCTION myadd(a integer, b integer) RETURNS integer AS $$
    BEGIN
            RETURN a + b;
    END;
$$ LANGUAGE plpgsql;

Then, from client side you can call the function with parameters using SELECT query. Please note, even though your function contains INSERT/UPDATE, the function must be called using SELECT statement.

//...
a := 10
row := db.QueryRow(`SELECT * FROM myadd($1, $2)`, a, 130)
//...

Next question, about transaction and PL/pgSQL. Yes, using PL/pgSQL you can reduce network traffic. Several advantages of server side language (PL/pgSQL) are:

  1. Eliminate client-server round trip
  2. No need to transfer intermediate result to client, only the final result will be transferred.
  3. Avoid parsing queries multiple times (Send query to server --> server parsing query --> perform database operation --> return result to client, etc...)

The rule when dealing with database (large data) is You need to avoid to move your data around and PL/pgSQL fits this rule. However, there are some circumstances in which you can not (need to avoid) use PL/pgSQL, e.g. DB admin/server owner does not allow server side programming (security/performance reason etc).

Relation between function and transaction is clearly stated in the manual :

> It is important not to confuse the use of BEGIN/END for grouping statements in PL/pgSQL with the similarly-named SQL commands for transaction control. PL/pgSQL's BEGIN/END are only for grouping; they do not start or end a transaction. Functions and trigger procedures are always executed within a transaction established by an outer query — they cannot start or commit that transaction, since there would be no context for them to execute in. However, a block containing an EXCEPTION clause effectively forms a subtransaction that can be rolled back without affecting the outer transaction

In summary, using PL/pgSQL you may get performance improvement. How much? It's depend. Please keep in mind, after using PL/pgSQL you need to manage more than one codebase, and sometimes it's difficult to debug.

huangapple
  • 本文由 发表于 2017年4月25日 18:21:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/43608002.html
匿名

发表评论

匿名网友

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

确定