Je veux porter le code SQL ci-dessous de MS SQL-Server vers 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
C'est ce que j'ai jusqu'à présent.
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$$;
Comme vous pouvez le voir, j'ai eu quelques problèmes lorsque je voulais "imprimer" avec la fonctionnalité d'avis de relance. Mais j'ai réussi à résoudre ce problème avec Google.
De l'expérience précédente, je peux dire que la syntaxe Postgres avec les CTE est si similaire que je n'ai qu'à ajouter un récursif avant le CTE, donc le seul vrai problème est que je dois définir certaines variables, pour lesquelles j'ai besoin d'un bloc do.
De là résulte la simple question que j'ai:
Comment "exécuter" une requête de sélection dans un bloc do? Je veux voir les résultats dans l'onglet "sortie de données" dans pgAdmin3.
Et je ne veux pas créer de fonction.
DO
commande vs fonction PL/pgSQLLa commande DO
ne renvoie pas de lignes. Vous pouvez envoyer NOTICES
ou RAISE
autres messages (avec la langue plpgsql) ou vous pouvez écrire dans une table (temporaire) et plus tard SELECT
à partir de celle-ci pour contourner cela.
Mais vraiment, créez une fonction (plpgsql) à la place, où vous pouvez définir un type de retour avec la clause RETURNS
ou OUT
/INOUT
paramètres et retour de la fonction de différentes manières.
Si vous ne voulez pas qu'une fonction soit enregistrée et visible pour d'autres connexions, envisagez une fonction "temporaire", qui est une fonctionnalité non documentée mais bien établie:
generate_series()
pour problème à portée de mainPour le problème actuel, vous ne semblez pas avoir besoin de tout de cela. Utilisez plutôt cette simple requête:
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 <> violon ici
Pourquoi?
Voici plus de détails sur la solution de contournement avec la table temporaire conseillée par Erwin, qui devrait être la vraie réponse à la question, puisque la question est plus orientée vers "pendant le développement, comment puis-je écrire rapidement un bloc de code avec un select et voir les résultats "que pour résoudre cette requête réelle (la question sous-jacente depuis le début était" comment développer/déboguer rapidement des fonctions de valeur de table ").
Bien que je doive dire que j'aimerais voter 100 fois la partie generate_series;)
Il est possible de sélectionner les résultats dans une table temporaire,
et sélectionnez dans la table temporaire en dehors du bloc do,
comme ça:
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;
Quelle est vraiment la base pour transformer rapidement une requête en une version de fonction table, qui ressemble à ceci:
-- 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;
BTW, jetez un œil au codebloat de SQL-Server pour y parvenir:
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
;
Se transforme en ceci:
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
(merci Erwin!);)
Ceci n'est pas trop hors sujet (IMHO), et peut être utile ...
J'ai rencontré ce problème récemment où j'avais besoin d'exécuter un certain nombre d'instructions dans une transaction et de renvoyer (très peu) de données qui indiqueraient à un script PHP comment la transaction a été traitée (enregistrements affectés et tout code d'erreur personnalisé).
S'en tenir au paradigme RAISE NOTICE et RAISE [EXCEPTION], j'ai trouvé préférable de renvoyer une chaîne JSON dans le NOTICE/EXCEPTION renvoyé. De cette façon, tout ce que l'application PHP devra faire est d'utiliser pg_last_notice () ou pg_last_error () pour obtenir et décoder la chaîne JSON.
par exemple.
RAISE EXCEPTION '{"std_response":{"affected":%,"error":%}}', var_affected, var_error_id;
ou
RAISE NOTICE '{"std_response":{"affected":%,"error":%}}', var_affected, var_error_id;
Étant donné que l'objet JSON de retour nommé "std_response" est en fait une réponse standard pour tous ces types de scripts, il est très facile d'écrire des tests unitaires puisque la fonction wrapper qui charge et exécute le SQL renverra toujours un objet "std_response" qui peut faire tester ses valeurs.
Ce paradigme ne doit être utilisé que si vous renvoyez de minuscules données dans le message RAISE (bien que j'ai vu jusqu'à 96 000 caractères renvoyés de cette façon - je ne sais pas quelle est la limite). Si vous devez renvoyer un ensemble de données plus important, vous devrez enregistrer l'ensemble de résultats dans une table, mais au moins vous pouvez toujours utiliser ce paradigme pour isoler exactement les enregistrements appartenant au SQL appelé. c'est-à-dire placer les données dans une table avec un UUID et retourner l'UUID dans l'AVIS comme ceci:
RAISE NOTICE '{"table_name":{"affected":%,"uuid":%}}', var_affected, var_uuid;
La bonne chose à ce sujet est que, comme il est toujours structuré et décrit le tableau à partir duquel sélectionner les données, il peut également être utilisé avec des tests unitaires dans l'application.
(Alternativement, vous pouvez également utiliser Postgresql pour stocker le jeu de résultats dans memcache et demander à l'application de récupérer le jeu de données à partir de là, de cette façon, vous n'avez pas à gérer les E/S du disque juste pour stocker le jeu de résultats de l'application va utiliser pour générer du HTML puis le jeter immédiatement une fois le script terminé)
Pour obtenir des enregistrements d'un bloc de code anonyme DO
, vous pouvez utiliser la technique suivante:
DO $$
DECLARE
_query text;
_cursor CONSTANT refcursor := '_cursor';
BEGIN
_query := 'SELECT * FROM table_name';
OPEN _cursor FOR EXECUTE _query;
END
$$;
FETCH ALL FROM _cursor;
Remarque
En savoir plus sur curseurs . Source technique ici (en russe).