【问题标题】:How to perform a select query in a DO block?如何在 DO 块中执行选择查询?
【发布时间】:2013-01-17 03:07:21
【问题描述】:

我想将下面的 SQL 代码从 MS SQL-Server 移植到 PostgreSQL。

DECLARE @iStartYear integer
DECLARE @iStartMonth integer

DECLARE @iEndYear integer
DECLARE @iEndMonth integer

SET @iStartYear = 2012
SET @iStartMonth = 4

SET @iEndYear = 2016
SET @iEndMonth = 1


;WITH CTE 
AS
(
    SELECT 
         --@iStartYear AS TheStartYear 
         @iStartMonth AS TheRunningMonth 
        ,@iStartYear AS TheYear  
        ,@iStartMonth AS TheMonth 

    UNION ALL 

    SELECT 
         --CTE.TheStartYear AS TheStartYear 
         --@iStartYear AS TheStartYear 
         CTE.TheRunningMonth + 1 AS TheRunningMonth 
         --,CTE.TheStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
        ,@iStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
        ,(CTE.TheMonth + 1 -1) % 12 + 1 AS TheMonth
    FROM CTE 
    WHERE (1=1) 

    AND
    (
        CASE 
            --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) < @iEndYear 
            WHEN (@iStartYear + (CTE.TheRunningMonth / 12) ) < @iEndYear 
                THEN 1 
            --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) = @iEndYear 
            WHEN (@iStartYear + (CTE.TheRunningMonth / 12) ) = @iEndYear 
                THEN 
                    CASE 
                        WHEN ( (CTE.TheMonth + 1 -1) % 12 + 1 ) <= @iEndMonth 
                            THEN 1 
                        ELSE 0 
                    END 
            ELSE 0 
        END = 1 
    )
)
SELECT * FROM CTE 

这是我目前所拥有的。

DO $$
    DECLARE r record;
    DECLARE i integer;

    DECLARE __iStartYear integer;
    DECLARE __iStartMonth integer;

    DECLARE __iEndYear integer;
    DECLARE __iEndMonth integer;

    DECLARE __mytext character varying(200);
BEGIN
    i:= 5;

    --RAISE NOTICE  'test'
    --RAISE NOTICE  'test1' || 'test2';

    __mytext := 'Test message';
    --RAISE NOTICE __mytext;
    RAISE NOTICE '%', __mytext;
    RAISE NOTICE '% %', 'arg1', 'arg2';

    --SQL Standard:  "CAST( value AS text )" [or varchar]
    --PostgreSQL short-hand:  "value::text"
    __mytext := 'Test ' || i::text;
    RAISE NOTICE '%', __mytext;

    __mytext := 'mynumber: ' || CAST(i as varchar(33)) || '%';
    RAISE NOTICE '%', __mytext;

    __iStartYear := 2012;
    __iStartMonth := 4;

    __iEndYear := 2016;
    __iEndMonth := 1;

    --PERFORM  'abc';
    SELECT 'abc';

    -- SELECT  __iStartMonth AS TheRunningMonth; 


    -- RAISE NOTICE  'The raise_test() function began.' + CAST( i AS text ) ;
    -- FOR r IN SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'VIEW' AND table_schema = 'public'
    -- LOOP
    --  EXECUTE 'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser';
    --END LOOP;
END$$;

如您所见,当我想使用加薪通知功能“打印”时遇到了一些问题。但我设法通过 Google 解决了这个问题。

根据以前的经验,我可以看出 Postgres 语法与 CTE 非常相似,我只需要在 CTE 之前添加一个递归,所以唯一真正的问题是我必须定义一些变量,为此我需要做块。

由此得出一个简单的问题:
如何在 do 块中“执行”选择查询? 我想在 pgAdmin3 的“数据输出”选项卡中查看结果。
而且我不想创建函数。

【问题讨论】:

    标签: sql postgresql plpgsql postgresql-9.1 generate-series


    【解决方案1】:

    DO 命令与 PL/pgSQL 函数

    DO 命令不返回行。您可以发送 NOTICESRAISE 其他消息(使用语言 plpgsql),或者您可以写入(临时)表,稍后再写入 SELECT 以解决此问题。

    但实际上,create a (plpgsql) function 可以通过多种方式使用RETURNS clause or OUT / INOUT parametersreturn from the function 定义返回类型。

    如果您不希望某个函数被保存且对其他连接可见,请考虑使用“临时”函数,这是一个未记录但已确立的特性:

    generate_series()手头的问题

    对于手头的问题,您似乎不需要任何。改用这个简单的查询:

    SELECT row_number() OVER ()    AS running_month
         , extract('year'  FROM m) AS year
         , extract('month' FROM m) AS month
    FROM   generate_series(timestamp '2012-04-01'
                         , timestamp '2016-01-01'
                         , interval '1 month') m;
    

    db小提琴here

    为什么?

    【讨论】:

      【解决方案2】:

      这里有更多关于 Erwin 建议的临时表解决方法的详细信息,这应该是问题的真正答案,因为这个问题更适合“在开发过程中,我怎样才能快速编写一个带有 select 和查看结果”而不是解决这个实际查询(从一开始的基本问题是“如何快速开发/调试表值函数”)。

      虽然我必须说我想对 generate_series 部分投票 100 次;)

      可以将结果选择到临时表中,
      并从 do 块外的临时表中选择,
      像这样:

      DO $$
          DECLARE r record;
          DECLARE i integer;
      
          DECLARE __iStartYear integer;
          DECLARE __iStartMonth integer;
      
      
          DECLARE __iEndYear integer;
          DECLARE __iEndMonth integer;
      
          DECLARE __mytext character varying(200);
      BEGIN
          i:= 5;
      
          -- Using Raise:
          -- http://www.java2s.com/Code/PostgreSQL/Postgre-SQL/UsingRAISENOTICE.htm
      
          --RAISE NOTICE  'test'
          --RAISE NOTICE  'test1' || 'test2';
      
      
          __mytext := 'Test message';
          --RAISE NOTICE __mytext;
          RAISE NOTICE '%', __mytext;
          RAISE NOTICE '%', 'arg1' || 'arg2';
          RAISE NOTICE '% %', 'arg1', 'arg2';
      
          --SQL Standard:  "CAST( value AS text )" [or varchar]
          --PostgreSQL short-hand:  "value::text"
          __mytext := 'Test ' || i::text;
          RAISE NOTICE '%', __mytext;
      
          __mytext := 'mynumber: ' || CAST(i as varchar(33)) || '%';
          RAISE NOTICE '%', __mytext;
      
          __iStartYear := 2012;
          __iStartMonth := 4;
      
           __iEndYear := 2016;
           __iEndMonth := 1;
      
           --PERFORM  'abc';
      
      
           --CREATE TEMP TABLE mytable AS SELECT * FROM orig_table;
      
           --DROP TABLE table_name CASCADE;
           --DROP TABLE IF EXISTS table_name CASCADE;
      
           --DROP TABLE IF EXISTS tbl;
           --CREATE TEMP TABLE tbl AS SELECT 1 as a,2 as b,3 as c;
      
      DROP TABLE IF EXISTS mytable;
      CREATE TEMP TABLE mytable AS
      
      
      WITH RECURSIVE CTE 
      AS
      (
      
              SELECT 
                   --__iStartYear AS TheStartYear 
                   __iStartMonth AS TheRunningMonth 
                  ,__iStartYear AS TheYear  
                  ,__iStartMonth AS TheMonth 
      
          UNION ALL 
      
              SELECT 
                   --CTE.TheStartYear AS TheStartYear 
                   --__iStartYear AS TheStartYear 
                   CTE.TheRunningMonth + 1 AS TheRunningMonth 
                  --,CTE.TheStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
                  ,__iStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
                  ,(CTE.TheMonth + 1 -1) % 12 + 1 AS TheMonth
              FROM CTE 
              WHERE (1=1) 
      
              AND
              (
                  CASE 
                      --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) < __iEndYear 
                      WHEN (__iStartYear + (CTE.TheRunningMonth / 12) ) < __iEndYear 
                          THEN 1 
                      --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) = __iEndYear 
                      WHEN (__iStartYear + (CTE.TheRunningMonth / 12) ) = __iEndYear 
                          THEN 
                              CASE 
                                  WHEN ( (CTE.TheMonth + 1 -1) % 12 + 1 ) <= __iEndMonth 
                                      THEN 1 
                                  ELSE 0 
                              END 
                      ELSE 0 
                  END = 1 
              )
      
      )
      
      
      SELECT * FROM CTE; 
      
      
          -- SELECT  __iStartMonth AS TheRunningMonth; 
      
      
           --RAISE NOTICE  'The raise_test() function began.' + CAST( i AS text ) ;
          --FOR r IN SELECT table_schema, table_name FROM information_schema.tables WHERE table_type = 'VIEW' AND table_schema = 'public'
          --LOOP
            --  EXECUTE 'GRANT ALL ON ' || quote_ident(r.table_schema) || '.' || quote_ident(r.table_name) || ' TO webuser';
          --END LOOP;
      END$$;
      
      
      SELECT * FROM mytable;
      

      这确实是快速将查询转换为表值函数版本的基础,顺便说一句。:

      -- SELECT * FROM tfu_V_RPT_MonthList(2012,1,2013,4);
      
      CREATE OR REPLACE FUNCTION tfu_V_RPT_MonthList
      ( 
           __iStartYear integer
          ,__iStartMonth integer
          ,__iEndYear integer
          ,__iEndMonth integer
      )
        RETURNS TABLE(
           TheRunningMonth integer
          ,TheYear integer
          ,TheMonth integer
      ) AS
      $BODY$
      DECLARE
      -- Declare vars here
      BEGIN
      RETURN QUERY 
      
      WITH RECURSIVE CTE 
      AS
      (
      
              SELECT 
                   --__iStartYear AS TheStartYear 
                   __iStartMonth AS TheRunningMonth 
                  ,__iStartYear AS TheYear  
                  ,__iStartMonth AS TheMonth 
      
          UNION ALL 
      
              SELECT 
                   --CTE.TheStartYear AS TheStartYear 
                   --__iStartYear AS TheStartYear 
                   CTE.TheRunningMonth + 1 AS TheRunningMonth 
                  --,CTE.TheStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
                  ,__iStartYear + (CTE.TheRunningMonth / 12) AS TheYear 
                  ,(CTE.TheMonth + 1 -1) % 12 + 1 AS TheMonth
              FROM CTE 
              WHERE (1=1) 
      
              AND
              (
                  CASE 
                      --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) < __iEndYear 
                      WHEN (__iStartYear + (CTE.TheRunningMonth / 12) ) < __iEndYear 
                          THEN 1 
                      --WHEN (CTE.TheStartYear + (CTE.TheRunningMonth / 12) ) = __iEndYear 
                      WHEN (__iStartYear + (CTE.TheRunningMonth / 12) ) = __iEndYear 
                          THEN 
                              CASE 
                                  WHEN ( (CTE.TheMonth + 1 -1) % 12 + 1 ) <= __iEndMonth 
                                      THEN 1 
                                  ELSE 0 
                              END 
                      ELSE 0 
                  END = 1 
              )
      
      )
      
          SELECT * FROM CTE ;
      
      END;
      $BODY$
        LANGUAGE plpgsql VOLATILE
      
      
      --ALTER FUNCTION dbo.tfu_v_dms_desktop(character varying) OWNER TO postgres;
      





      顺便说一句,看看 SQL-Server 代码膨胀来实现这一点:

      SELECT 
           extract('year' FROM m) AS RPT_Year
          -- http://www.postgresql.org/docs/current/interactive/functions-formatting.html#FUNCTIONS-FORMATTING-DATETIME-TABLE
          --,to_char(m, 'TMmon')
          --,to_char(m, 'TMmonth')
          ,to_char(m, 'Month') AS RPT_MonthName 
          ,m AS RPT_MonthStartDate
          ,m + INTERVAL '1 month' - INTERVAL '1 day' AS RPT_MonthEndDate 
      
      FROM 
      (
         SELECT 
              generate_series((2012::text || '-' || 4::text || '-01')::date, (2016::text || '-' || 1::text || '-01')::date, interval '1 month') AS m 
      ) AS g
      ;
      

      变成这样:

      DECLARE @in_iStartYear integer
      DECLARE @in_iStartMonth integer
      
      
      DECLARE @in_iEndYear integer
      DECLARE @in_iEndMonth integer
      
      SET @in_iStartYear = 2012
      SET @in_iStartMonth = 12
      
      
      SET @in_iEndYear = 2016
      SET @in_iEndMonth = 12
      
      
      
      DECLARE @strOriginalLanguage AS nvarchar(200) 
      DECLARE @dtStartDate AS datetime 
      DECLARE @dtEndDate AS datetime 
      
      
      SET @strOriginalLanguage = (SELECT @@LANGUAGE) 
      
      SET @dtStartDate = DATEADD(YEAR, @in_iStartYear - 1900, 0) 
      SET @dtStartDate = DATEADD(MONTH, @in_iStartMonth -1, @dtStartDate) 
      
      SET @dtEndDate = DATEADD(YEAR, @in_iEndYear - 1900, 0) 
      SET @dtEndDate = DATEADD(MONTH, @in_iEndMonth -1, @dtEndDate) 
      
      SET LANGUAGE 'us_english'
      
      
      ;WITH CTE_YearsMonthStartAndEnd 
      AS
      (
              SELECT
                   YEAR(@dtStartDate) AS RPT_Year 
                  ,DATENAME(MONTH, @dtStartDate) AS RPT_MonthName 
                  ,@dtStartDate AS RPT_MonthStartDate  
                  ,DATEADD(DAY, -1, DATEADD(MONTH, 1, @dtStartDate)) AS RPT_MonthEndDate 
      
          UNION ALL
      
              SELECT 
                   YEAR(DATEADD(MONTH, 1, CTE_YearsMonthStartAndEnd.RPT_MonthStartDate)) AS RPT_Year 
                  ,DATENAME(MONTH, DATEADD(MONTH, 1, CTE_YearsMonthStartAndEnd.RPT_MonthStartDate)) AS RPT_MonthName 
                  ,DATEADD(MONTH, 1, CTE_YearsMonthStartAndEnd.RPT_MonthStartDate) AS RPT_MonthStartDate 
                  ,DATEADD(DAY, -1, DATEADD(MONTH, 1, DATEADD(MONTH, 1, CTE_YearsMonthStartAndEnd.RPT_MonthStartDate)) ) AS RPT_MonthEndDate 
      
              FROM CTE_YearsMonthStartAndEnd 
              WHERE DATEADD(MONTH, 1, CTE_YearsMonthStartAndEnd.RPT_MonthStartDate) <= @dtEndDate 
      )
      
      SELECT 
           RPT_Year 
          ,RPT_MonthName 
          ,RPT_MonthStartDate 
          ,RPT_MonthEndDate 
      FROM CTE_YearsMonthStartAndEnd 
      

      (感谢欧文!);)

      【讨论】:

      • 与 SQL-Server 的横向比较很有见地。感谢分享!
      【解决方案3】:

      要从 DO 匿名代码块中获取记录,您可以使用以下技术:

      DO $$
      DECLARE
        _query text;
        _cursor CONSTANT refcursor := '_cursor';
      BEGIN
        _query := 'SELECT * FROM table_name';
        OPEN _cursor FOR EXECUTE _query;
      END
      $$;
      
      FETCH ALL FROM _cursor;
      

      通知

      1. 游标在事务范围内可见,因此您应该使用它 一笔交易。
      2. 游标变量的名称应与文本常量相同;

      更多关于cursors。 技术来源here(俄语)。

      【讨论】:

        【解决方案4】:

        这不是太离题(恕我直言),并且可能会有所帮助...

        我最近遇到了这个问题,我需要在事务中执行一些语句并返回一些(非常少的)数据,这些数据将向 PHP 脚本指示事务是如何处理的(受影响的记录和任何自定义错误代码) .

        坚持 RAISE NOTICE 和 RAISE [EXCEPTION] 范例,我发现最好在返回的 NOTICE/EXCEPTION 中返回 JSON 字符串。这样,PHP 应用程序需要做的就是使用 pg_last_notice() 或 pg_last_error() 来获取和解码 JSON 字符串。

        例如

        RAISE EXCEPTION '{"std_response":{"affected":%,"error":%}}', var_affected, var_error_id;
        

        RAISE NOTICE '{"std_response":{"affected":%,"error":%}}', var_affected, var_error_id;
        

        由于返回的名为“std_response”的 JSON 对象实际上是所有这些类型脚本的标准响应,因此编写单元测试非常容易,因为加载和执行 SQL 的包装函数将始终返回“std_response”可以对其值进行测试的对象。

        仅当您在 RAISE 消息中返回 TINY 条数据时才应使用此范例(尽管我已经看到以这种方式返回多达 96,000 个字符 - 不确定限制是多少)。 如果您需要返回更大的数据集,则需要将结果集保存到表中,但至少您仍然可以使用此范例来准确隔离哪些记录属于被调用的 SQL。即将数据放入带有 UUID 的表中,并在 NOTICE 中返回 UUID,如下所示:

        RAISE NOTICE '{"table_name":{"affected":%,"uuid":%}}', var_affected, var_uuid;
        

        这样做的好处是,由于它仍然是结构化的并描述了从哪个表中选择数据,它也可以用于应用程序中的单元测试。

        (或者,您也可以使用 Postgresql 将结果集存储在 memcache 中,并让应用程序从那里获取数据集,这样您就不必处理磁盘 I/O 来存储结果 -设置应用程序将用于生成一些 HTML,然后在脚本完成后立即丢弃)

        【讨论】:

        • +1 有趣。可能会通过内置 JSON functions in PostgreSQL 9.2 进行改进,以简化 NOTICE 文本的构建。
        • 我找不到任何关于可以放在 NOTICE / ERROR 消息中的文本限制的文档
        猜你喜欢
        • 1970-01-01
        • 2013-01-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-09-01
        • 1970-01-01
        相关资源
        最近更新 更多