创建一个 java 过程来生成 idempiere 报告
create a java process to generate a idempiere report
我有 Postgres,它在单独运行时可以运行,但在集成到 java 中并在 idempiere 上调用时无法运行。我正在寻找建议。
我收到 ff 错误:
caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "00"
Position: 1055; State=42601; ErrorCode=0
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:143)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:120)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:384)
at jdk.internal.reflect.GeneratedMethodAccessor48.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.compiere.db.StatementProxy.invoke(StatementProxy.java:130)
at com.sun.proxy.$Proxy11.executeUpdate(Unknown Source)
at org.compiere.util.DB.executeUpdate(DB.java:1039)
at org.compiere.util.DB.executeUpdate(DB.java:898)
at org.compiere.util.DB.executeUpdate(DB.java:885)
at ice.enterprise.base.report.StockAgingReport.createDetailLines(StockAgingReport.java:145)
at ice.enterprise.base.report.StockAgingReport.doIt(StockAgingReport.java:72)
at org.compiere.process.SvrProcess.process(SvrProcess.java:201)
at org.compiere.process.SvrProcess.startProcess(SvrProcess.java:147)
at org.adempiere.util.ProcessUtil.startJavaProcess(ProcessUtil.java:173)
at org.compiere.apps.AbstractProcessCtl.startProcess(AbstractProcessCtl.java:467)
at org.compiere.apps.AbstractProcessCtl.run(AbstractProcessCtl.java:235)
15:21:49.994===========> DataEngine.loadPrintData: null - ERROR: column "levelno" does not exist
Position: 924
SQL=SELECT T_ReportStockAgeing.CurrentCost,T_ReportStockAgeing.Date1,T_ReportStockAgeing_ICE.Description,T_ReportStockAgeing.OnHand,(SELECT NVL(AD_PInstance.Name,'-1') ||'_'|| NVL(CAST (AD_PInstance.AD_PInstance_ID AS Text),'-1') ||'_'|| NVL((SELECT NVL(AD_Process.Name,'-1') ||'_'|| NVL(AD_Process.Value,'-1') FROM AD_Process WHERE AD_PInstance.AD_Process_ID=AD_Process.AD_Process_ID),'-1') FROM AD_PInstance WHERE T_ReportStockAgeing.AD_PInstance_ID=AD_PInstance.AD_PInstance_ID) AS AAD_PInstance_ID,T_ReportStockAgeing.AD_PInstance_ID AS AD_PInstance_ID,T_ReportStockAgeing.ProductCode,T_ReportStockAgeing.Qty1,T_ReportStockAgeing.Qty2,T_ReportStockAgeing.Qty3,T_ReportStockAgeing.Qty4,T_ReportStockAgeing.Value1,T_ReportStockAgeing.Valu2,T_ReportStockAgeing.Value3,T_ReportStockAgeing.Value4,T_ReportStockAgeing_ICE.T_ReportStockAgeing_ICE_UU,LevelNo FROM T_ReportStockAgeing_ICE WHERE T_ReportStockAgeing_ICE.AD_PInstance_ID=2109500 [136]
15:21:50.030===========> AbstractProcessDialog.doRun: org.postgresql.util.PSQLException: ERROR: column "levelno" does not exist
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.logging.Level;
import org.compiere.print.MPrintFormat;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Ini;
public class StockAgingReport extends SvrProcess {
private int p_AD_Org_ID = 0;
private int p_AD_Client_ID = 0;
private int p_C_AcctSchema_ID = 0;
private int p_M_Product_Category_ID = 0;
private Timestamp p_Date = null;
private long m_start = System.currentTimeMillis();
@Override
protected void prepare() {
StringBuffer sb = new StringBuffer ("Record_ID=")
.append(getRecord_ID());
// Parameter
ProcessInfoParameter[] para = getParameter();
for (int i = 0; i < para.length; i++)
{
String name = para[i].getParameterName();
if (para[i].getParameter() == null && para[i].getParameter_To() == null)
;
else if (name.equals("Date"))
{
p_Date = (Timestamp)para[i].getParameter();
}
else if (name.equals("AD_Org_ID"))
p_AD_Org_ID = ((BigDecimal)para[i].getParameter()).intValue();
else if (name.equals("AD_Client_ID"))
p_AD_Client_ID = para[i].getParameterAsInt();
else if (name.equals("C_AcctSchema_ID"))
p_C_AcctSchema_ID = para[i].getParameterAsInt();
else if (name.equals("M_Product_Category_ID"))
p_M_Product_Category_ID = para[i].getParameterAsInt();
else
log.log(Level.SEVERE, "Unknown Parameter: " + name);
}
log.fine(sb.toString());
}
@Override
protected String doIt() throws Exception {
createDetailLines();
int AD_PrintFormat_ID = DB.getSQLValue(get_TrxName(), "Select AD_PrintFormat_ID from AD_PrintFormat Where name = 'Stock_Ageing'");
if (AD_PrintFormat_ID > 0) {
if (Ini.isClient())
getProcessInfo().setTransientObject (MPrintFormat.get (getCtx(), AD_PrintFormat_ID, false));
else
getProcessInfo().setSerializableObject(MPrintFormat.get (getCtx(), AD_PrintFormat_ID, false));
}
if (log.isLoggable(Level.FINE)) log.fine((System.currentTimeMillis() - m_start) + " ms");
return "";
}
private void createDetailLines() {
StringBuffer sb = new StringBuffer ("INSERT INTO T_ReportStockAgeing "
+ "(AD_PInstance_ID, AD_Client_ID, AD_Org_ID, ProductCode, Description, CurrentCost,"
+ " Qty1, Qty2, Qty3, Qty4, OnHand,"
+ " Value1, Valu2, Value3 ,Value4) ");
sb.append("SELECT ").append(getAD_PInstance_ID()).append(", ").append(Env.getAD_Client_ID(getCtx())).append(", ").append(Env.getAD_Org_ID(getCtx())).append(", ")
.append( "ProductCode, Description, Case OnHand WHEN 0 THEN 0 ELSE CurrentCost*OnHand END AS CurrentCost,"
+ "Qty1, Qty2, Qty3, Qty4, OnHand,"
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty1,2) END as Value1, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty2,2) END as Valu2, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty3,2) END as Value3, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty4,2) END as Value4 "
+ "FROM ( "
+ "SELECT "
+ "prod.value as ProductCode, "
+ "prod.description as Description, "
+ "M_Product_Category_ID, "
+ "(SELECT COALESCE (max(c.CurrentCostPrice),0) FROM M_Cost c WHERE c.AD_Org_ID =" +p_AD_Org_ID +" AND c.C_AcctSchema_ID= "+p_C_AcctSchema_ID+" AND prod.M_Product_ID = c.M_Product_ID AND prod.ProductType !='A' ) as CurrentCost, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ( ("+p_Date+"::date) - interval '3 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date) "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty1, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ("+p_Date+"::date- interval '6 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date - interval '3 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A') "
+ "as Qty2, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ("+p_Date+"::date - interval '12 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date- interval '6 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty3, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy < ("+p_Date+"::date- interval '12 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty4, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as OnHand "
+ "FROM M_Product prod "
+ "LEFT JOIN AD_Org org ON org.AD_Org_ID = "+p_AD_Org_ID+" "
+ "LEFT JOIN M_Product_Category prodcat ON prodcat.M_Product_Category_ID = "+p_M_Product_Category_ID+" AND prodcat.AD_Client_ID = "+p_AD_Client_ID+" "
+ "WHERE prod.M_Product_Category_ID = "+ p_M_Product_Category_ID+" AND prod.ProductType !='A' "
+ ")temp ");
sb.append(" ) as temp ");
int no = DB.executeUpdate(sb.toString(), get_TrxName());
log.fine("#" + no);
log.finest(sb.toString());
}
}
我试图在 iDempiere 中集成您的 class 示例进行测试,但正如 GhostCat 指出的那样,最好创建一个最小的可重现示例,而且也很重要,请 post 也请指出错误这是系统抛出的,在 UI and/or 控制台日志中。
class 没有一些导入,所有变量都没有被使用,私有方法 createDetailLines 也从未被调用,我假设你提到的错误是在那个方法中,但是有无法知道是否未调用。
现在,回顾 SQL,有两点需要注意:
1 - 它没有为 java 格式化,你使用 "AND c.C_AcctSchema_ID= $P{C_AcctSchema_ID}" 而这不是 java 管理变量的方式,这听起来像 jasper 报告语法而不是 java.对于 JDBC 你必须使用 ?作为绑定变量的替代
2 - iDempiere是多数据库的,系统的设计方式是写oracle兼容的SQL语法,并且有一个翻译层将oracle语句转换成postgresql句法。因此,最好避免使用 postgresql 特定语法,如“::date”或间隔“3 个月”——它可以工作,但转换层也可能有问题。如果你想使用特定的 postgresql 语法并避免转换层被混淆,你可以用 NATIVE_PostgreSQL_KEYWORK 包围 postgresql 特定的语法 - 请不要因为这个错误而责备我常量:-)
我有 Postgres,它在单独运行时可以运行,但在集成到 java 中并在 idempiere 上调用时无法运行。我正在寻找建议。
我收到 ff 错误:
caused by: org.postgresql.util.PSQLException: ERROR: syntax error at or near "00"
Position: 1055; State=42601; ErrorCode=0
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:143)
at org.postgresql.jdbc.PgPreparedStatement.executeUpdate(PgPreparedStatement.java:120)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:384)
at jdk.internal.reflect.GeneratedMethodAccessor48.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.compiere.db.StatementProxy.invoke(StatementProxy.java:130)
at com.sun.proxy.$Proxy11.executeUpdate(Unknown Source)
at org.compiere.util.DB.executeUpdate(DB.java:1039)
at org.compiere.util.DB.executeUpdate(DB.java:898)
at org.compiere.util.DB.executeUpdate(DB.java:885)
at ice.enterprise.base.report.StockAgingReport.createDetailLines(StockAgingReport.java:145)
at ice.enterprise.base.report.StockAgingReport.doIt(StockAgingReport.java:72)
at org.compiere.process.SvrProcess.process(SvrProcess.java:201)
at org.compiere.process.SvrProcess.startProcess(SvrProcess.java:147)
at org.adempiere.util.ProcessUtil.startJavaProcess(ProcessUtil.java:173)
at org.compiere.apps.AbstractProcessCtl.startProcess(AbstractProcessCtl.java:467)
at org.compiere.apps.AbstractProcessCtl.run(AbstractProcessCtl.java:235)
15:21:49.994===========> DataEngine.loadPrintData: null - ERROR: column "levelno" does not exist
Position: 924
SQL=SELECT T_ReportStockAgeing.CurrentCost,T_ReportStockAgeing.Date1,T_ReportStockAgeing_ICE.Description,T_ReportStockAgeing.OnHand,(SELECT NVL(AD_PInstance.Name,'-1') ||'_'|| NVL(CAST (AD_PInstance.AD_PInstance_ID AS Text),'-1') ||'_'|| NVL((SELECT NVL(AD_Process.Name,'-1') ||'_'|| NVL(AD_Process.Value,'-1') FROM AD_Process WHERE AD_PInstance.AD_Process_ID=AD_Process.AD_Process_ID),'-1') FROM AD_PInstance WHERE T_ReportStockAgeing.AD_PInstance_ID=AD_PInstance.AD_PInstance_ID) AS AAD_PInstance_ID,T_ReportStockAgeing.AD_PInstance_ID AS AD_PInstance_ID,T_ReportStockAgeing.ProductCode,T_ReportStockAgeing.Qty1,T_ReportStockAgeing.Qty2,T_ReportStockAgeing.Qty3,T_ReportStockAgeing.Qty4,T_ReportStockAgeing.Value1,T_ReportStockAgeing.Valu2,T_ReportStockAgeing.Value3,T_ReportStockAgeing.Value4,T_ReportStockAgeing_ICE.T_ReportStockAgeing_ICE_UU,LevelNo FROM T_ReportStockAgeing_ICE WHERE T_ReportStockAgeing_ICE.AD_PInstance_ID=2109500 [136]
15:21:50.030===========> AbstractProcessDialog.doRun: org.postgresql.util.PSQLException: ERROR: column "levelno" does not exist
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.logging.Level;
import org.compiere.print.MPrintFormat;
import org.compiere.process.ProcessInfoParameter;
import org.compiere.process.SvrProcess;
import org.compiere.util.DB;
import org.compiere.util.Env;
import org.compiere.util.Ini;
public class StockAgingReport extends SvrProcess {
private int p_AD_Org_ID = 0;
private int p_AD_Client_ID = 0;
private int p_C_AcctSchema_ID = 0;
private int p_M_Product_Category_ID = 0;
private Timestamp p_Date = null;
private long m_start = System.currentTimeMillis();
@Override
protected void prepare() {
StringBuffer sb = new StringBuffer ("Record_ID=")
.append(getRecord_ID());
// Parameter
ProcessInfoParameter[] para = getParameter();
for (int i = 0; i < para.length; i++)
{
String name = para[i].getParameterName();
if (para[i].getParameter() == null && para[i].getParameter_To() == null)
;
else if (name.equals("Date"))
{
p_Date = (Timestamp)para[i].getParameter();
}
else if (name.equals("AD_Org_ID"))
p_AD_Org_ID = ((BigDecimal)para[i].getParameter()).intValue();
else if (name.equals("AD_Client_ID"))
p_AD_Client_ID = para[i].getParameterAsInt();
else if (name.equals("C_AcctSchema_ID"))
p_C_AcctSchema_ID = para[i].getParameterAsInt();
else if (name.equals("M_Product_Category_ID"))
p_M_Product_Category_ID = para[i].getParameterAsInt();
else
log.log(Level.SEVERE, "Unknown Parameter: " + name);
}
log.fine(sb.toString());
}
@Override
protected String doIt() throws Exception {
createDetailLines();
int AD_PrintFormat_ID = DB.getSQLValue(get_TrxName(), "Select AD_PrintFormat_ID from AD_PrintFormat Where name = 'Stock_Ageing'");
if (AD_PrintFormat_ID > 0) {
if (Ini.isClient())
getProcessInfo().setTransientObject (MPrintFormat.get (getCtx(), AD_PrintFormat_ID, false));
else
getProcessInfo().setSerializableObject(MPrintFormat.get (getCtx(), AD_PrintFormat_ID, false));
}
if (log.isLoggable(Level.FINE)) log.fine((System.currentTimeMillis() - m_start) + " ms");
return "";
}
private void createDetailLines() {
StringBuffer sb = new StringBuffer ("INSERT INTO T_ReportStockAgeing "
+ "(AD_PInstance_ID, AD_Client_ID, AD_Org_ID, ProductCode, Description, CurrentCost,"
+ " Qty1, Qty2, Qty3, Qty4, OnHand,"
+ " Value1, Valu2, Value3 ,Value4) ");
sb.append("SELECT ").append(getAD_PInstance_ID()).append(", ").append(Env.getAD_Client_ID(getCtx())).append(", ").append(Env.getAD_Org_ID(getCtx())).append(", ")
.append( "ProductCode, Description, Case OnHand WHEN 0 THEN 0 ELSE CurrentCost*OnHand END AS CurrentCost,"
+ "Qty1, Qty2, Qty3, Qty4, OnHand,"
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty1,2) END as Value1, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty2,2) END as Valu2, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty3,2) END as Value3, "
+ "CASE OnHand WHEN 0 THEN 0 ELSE round((CurrentCost) * Qty4,2) END as Value4 "
+ "FROM ( "
+ "SELECT "
+ "prod.value as ProductCode, "
+ "prod.description as Description, "
+ "M_Product_Category_ID, "
+ "(SELECT COALESCE (max(c.CurrentCostPrice),0) FROM M_Cost c WHERE c.AD_Org_ID =" +p_AD_Org_ID +" AND c.C_AcctSchema_ID= "+p_C_AcctSchema_ID+" AND prod.M_Product_ID = c.M_Product_ID AND prod.ProductType !='A' ) as CurrentCost, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ( ("+p_Date+"::date) - interval '3 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date) "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty1, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ("+p_Date+"::date- interval '6 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date - interval '3 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A') "
+ "as Qty2, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy >= ("+p_Date+"::date - interval '12 month' ) "
+ "AND st.DateMaterialPolicy <= ("+p_Date+"::date- interval '6 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty3, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.DateMaterialPolicy < ("+p_Date+"::date- interval '12 month') "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as Qty4, "
+ "(SELECT COALESCE (SUM (st.qtyonhand),0)FROM M_Storage st "
+ "WHERE prod.M_Product_ID = st.M_Product_ID "
+ "AND st.AD_Org_ID = "+p_AD_Org_ID+" AND prod.ProductType !='A' "
+ ") as OnHand "
+ "FROM M_Product prod "
+ "LEFT JOIN AD_Org org ON org.AD_Org_ID = "+p_AD_Org_ID+" "
+ "LEFT JOIN M_Product_Category prodcat ON prodcat.M_Product_Category_ID = "+p_M_Product_Category_ID+" AND prodcat.AD_Client_ID = "+p_AD_Client_ID+" "
+ "WHERE prod.M_Product_Category_ID = "+ p_M_Product_Category_ID+" AND prod.ProductType !='A' "
+ ")temp ");
sb.append(" ) as temp ");
int no = DB.executeUpdate(sb.toString(), get_TrxName());
log.fine("#" + no);
log.finest(sb.toString());
}
}
我试图在 iDempiere 中集成您的 class 示例进行测试,但正如 GhostCat 指出的那样,最好创建一个最小的可重现示例,而且也很重要,请 post 也请指出错误这是系统抛出的,在 UI and/or 控制台日志中。
class 没有一些导入,所有变量都没有被使用,私有方法 createDetailLines 也从未被调用,我假设你提到的错误是在那个方法中,但是有无法知道是否未调用。
现在,回顾 SQL,有两点需要注意:
1 - 它没有为 java 格式化,你使用 "AND c.C_AcctSchema_ID= $P{C_AcctSchema_ID}" 而这不是 java 管理变量的方式,这听起来像 jasper 报告语法而不是 java.对于 JDBC 你必须使用 ?作为绑定变量的替代
2 - iDempiere是多数据库的,系统的设计方式是写oracle兼容的SQL语法,并且有一个翻译层将oracle语句转换成postgresql句法。因此,最好避免使用 postgresql 特定语法,如“::date”或间隔“3 个月”——它可以工作,但转换层也可能有问题。如果你想使用特定的 postgresql 语法并避免转换层被混淆,你可以用 NATIVE_PostgreSQL_KEYWORK 包围 postgresql 特定的语法 - 请不要因为这个错误而责备我常量:-)