如何在 DO 块中执行选择查询?
Posted
技术标签:
【中文标题】如何在 DO 块中执行选择查询?【英文标题】:How to perform a select query in a DO block? 【发布时间】: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 的“数据输出”选项卡中查看结果。 而且我不想创建函数。
【问题讨论】:
【参考方案1】:
DO
命令与 PL/pgSQL 函数
DO
命令不返回行。您可以发送 NOTICES
或 RAISE
其他消息(使用语言 plpgsql),或者您可以写入(临时)表,稍后再写入 SELECT
以解决此问题。
但实际上,create a (plpgsql) function 可以通过多种方式使用RETURNS
clause or OUT
/ INOUT
parameters 和return from the function 定义返回类型。
如果您不希望某个函数被保存且对其他连接可见,请考虑使用“临时”函数,这是一个未记录但已确立的特性:
How to create a temporary function in PostgreSQL?
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
为什么?
Generating time series between two dates in PostgreSQL【讨论】:
【参考方案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;
通知
-
游标在事务范围内可见,因此您应该使用它
一笔交易。
游标变量的名称应与文本常量相同;
更多关于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 消息中的文本限制的文档以上是关于如何在 DO 块中执行选择查询?的主要内容,如果未能解决你的问题,请参考以下文章