如何在PostgreSQL中创建循环通过另一个函数的函数?

如何在PostgreSQL中创建循环通过另一个函数的函数?,postgresql,Postgresql,我使用的是PostgreSQL 9.3.9,我有一个名为list_all_upsells的过程,该过程在一个月的开始和结束时进行。(有关示例数据,请参见sqlfiddle.com/#!15/abd02)例如,下面的代码将列出10月份追加销售的帐户数: select COUNT(up.*) as "Total Upsell Accounts in October" from list_all_upsells('2015-10-01 00:00:00'::timestamp, '2015-10-3

我使用的是PostgreSQL 9.3.9,我有一个名为list_all_upsells的过程,该过程在一个月的开始和结束时进行。(有关示例数据,请参见sqlfiddle.com/#!15/abd02)例如,下面的代码将列出10月份追加销售的帐户数:

select COUNT(up.*) as "Total Upsell Accounts in October" from 
list_all_upsells('2015-10-01 00:00:00'::timestamp, '2015-10-31 23:59:59'::timestamp) as up
where up.user_id not in
(select distinct user_id from paid_users_no_more 
where concat(extract(month from payment_stop_date),'-',extract(year from payment_stop_date))<>
concat(extract(month from payment_start_date),'-',extract(year from payment_start_date)));
我有一个查询,以获取我们开展业务的月份中每个月的第一个月和最后一个月:

select distinct date_trunc('month', payment_start_date)::date as startmonth
from paid_users ORDER BY startmonth;
本月最后一个月:

SELECT distinct (date_trunc('MONTH', payment_start_date) + 
INTERVAL '1 MONTH - 1 day')::date as endmonth from paid_users 
ORDER BY endmonth;
现在,我如何创建一个函数来循环查看
列表\u all\u upsells
,并获取每个月的计数?也就是说,
startmonth
的第一次查询给出了2014-03-012014-04-01,…到2015-10-01,而
endmonth
的第二次查询给出了2014-03-312014-04-30,…到2015-10-31。我想在每个月都运行
列表\u all\u seals
,这样我就可以得到每个月我们有多少追加销售的账户的总数

我的
付费用户
表如下所示:

DECLARE
payor_email_2 text;
   BEGIN
FOR payor_email_2 in select distinct payor_email from paid_users LOOP
return query
execute
'select paid_users.* from paid_users,
(
select payment_start_date as first_time from paid_users
where payor_email = $3
order by payment_start_date limit 1
) as dummy
where payor_email = $3
and payment_start_date > first_time
and payment_start_date between $1 and $2
and first_time < $1'
using a, b, payor_email_2;
END LOOP;
return;
END
Month   | Total Upselled Accounts
---------------------------------
08/2014 | 23
09/2014 | 35
ETC...
10/2015 | 56
CREATE TABLE paid_users
(
  user_id integer,
  user_email character varying(255),
  payor_id integer,
  payor_email character varying(255),
  payment_start_date timestamp without time zone DEFAULT now()
)
付费用户\u无更多

CREATE TABLE paid_users_no_more
(
  user_id integer,
  payment_stop_date timestamp without time zone DEFAULT now()
)

你的函数有几个问题,让我们从这里开始。它的缺点是(1)您只需要一个参数来指示月份,使用月的开始和结束设置您自己的问题;(2) 您不需要动态查询,因为您没有更改标识符(表或列名);(3) 你不需要一个循环;(4)你的逻辑是错误的。我还可以提到PostgreSQL使用函数,它们都是从一行开始的,比如
CREATE FUNCTION list\u all\u upsells(…)
,但这太挑剔了

首先,逻辑是:显然,由其电子邮件地址标识的用户从某个
付款开始日期
到某个
付款停止日期
都可以进行多次订阅。您要查找的是那些在相关月份之前首次订阅的用户,以及在相关月份开始新订阅但不是首次订阅的用户。在这种情况下,筛选器
付款开始日期>首次时间
是无用的,因为您已经筛选出在所述月份之前的首次订阅(
首次时间
)和新订阅(
付款开始日期介于$1和$2之间

第(1)、(2)和(3)点只有在重写函数内部的查询时才变得明显:

CREATE FUNCTION list_all_upsells(timestamp) RETURNS SETOF paid_users AS $$
  SELECT paid_users.*
  FROM paid_users
  JOIN (  -- This JOIN keeps only those rows where the payor_email has a prior subscription
    SELECT DISTINCT payor_email,
           first_value(payment_start_date) OVER (PARTITION BY payor_email ORDER BY payment_start_date) AS dummy
    FROM paid_users
    WHERE payment_start_date < date_trunc('month', $1)
  ) dummy USING (payor_email)
  -- This filter keeps only those rows with new subscriptions in the month
  WHERE date_trunc('month', payment_start_date) = date_trunc('month', $1)
$$ LANGUAGE sql STRICT;
顺便说一句,这确实回避了一个问题:为什么表中有付费用户。为什么不简单地在表
付费用户
中添加一列
付费用户
?如果该列为
NULL
,则用户仍在订阅。但是整个查询相当奇怪,因为
list\u all\u upsells()
会在一个月内返回新的订阅,那么为什么还要在其他时间处理取消的订阅呢

现在谈谈你真正的问题:

SELECT months.m "Month", coalesce(count(up.*), 0) "Total Upselled Accounts"
FROM generate_series('2014-08-01'::timestamp,
                     date_trunc('month', LOCALTIMESTAMP),
                     '1 month') AS months(m)
LEFT JOIN list_all_upsells(months.m) AS up ON date_trunc('month', payment_start_date) = m
GROUP BY 1
ORDER BY 1;
从某个起始月到当前月,然后计算每个月的新订阅数,可能为0


我真的不擅长postgres,但是用适当的连接代替execute难道不可能吗?嘿@GSazheniuk我不知道:当查看循环层时,几乎总是这样,转换为使用子查询、连接等组合查询要快得多。这是一个非常好的答案,逻辑非常合理。我试着运行你的list\u all\u upsells create函数,但是在“SELECT”(第5行)附近出现了语法错误-这是为什么@帕特里克,那是个严重的错误。与
选择无关
。我从您的代码开始工作,并将主查询的select列表中的标量子查询更改为常规的
连接
:您需要从付费用户中删除
之后第二行中的
。我花了一段时间才找到那个挑剔的小家伙!嘿,帕特里克-应该是当前日期而不是当前时间吗?我得到“错误:函数list\u all\u upsells(带时区的时间)不存在第2行:从list\u all\u upsells(当前时间)开始^HINT:没有函数与给定的名称和参数类型匹配。您可能需要添加显式类型转换。”当我执行当前时间时。当我尝试当前约会时,我得到0 thoAh
CURRENT_TIME
给出带有时区的
时间戳
。改用
LOCALTIMESTAMP
;这会给出一个正常的
时间戳。我以为演员是自动的。答案更新。嗯,我不知道为什么,但我得到的结果是0,当我运行最后一个查询时,我只得到10月份的返回
SELECT months.m "Month", coalesce(count(up.*), 0) "Total Upselled Accounts"
FROM generate_series('2014-08-01'::timestamp,
                     date_trunc('month', LOCALTIMESTAMP),
                     '1 month') AS months(m)
LEFT JOIN list_all_upsells(months.m) AS up ON date_trunc('month', payment_start_date) = m
GROUP BY 1
ORDER BY 1;