[英]How to create a PostgreSQL function that I can call from DBeaver?
这是示例日期:
CREATE TABLE #logins (
username text not null,
logged_at timestamp not null);
insert into #logins (username, logged_at) values
('a','2019-01-01'),('b','2019-01-01'),('c','2019-01-01'),('d','2019-01-01'),('e','2019-01-01'),
('a','2019-02-01'),('b','2019-02-01'),('c','2019-02-01'),('f','2019-02-01'),('g','2019-02-01'),
('h','2019-02-01'),('i','2019-02-01'),('j','2019-02-01'),('a','2019-03-01'),('b','2019-03-01'),
('f','2019-03-01'),('h','2019-03-01'),('g','2019-03-01'),('k','2019-03-01'),('l','2019-03-01'),
('m','2019-03-01'),('n','2019-03-01'),('o','2019-03-01'),('a','2019-04-01'),('f','2019-04-01'),
('g','2019-04-01'),('k','2019-04-01'),('l','2019-04-01')`
我通常做什么
drop table if exists #a;
create table #a as
select username, min(logged_at) as date from #logins --Please note that there is **MIN()** here
group by 1;
alter table #a
add m_1 varchar;
update #a
set m_1 = (select username from #logins
where add_months(#a.date,1) = #logins.logged_at and #logins.username = #a.username);
alter table #a
add m_2 varchar;
update #a
set m_2 = (select username from #logins
where add_months(#a.date,2) = #logins.logged_at and #logins.username = #a.username);
alter table #a
add m_3 varchar;
update #a
set m_3 = (select username from #logins
where add_months(#a.date,1) = #logins.logged_at and #logins.username = #a.username);
select to_date(date,'yyyy-mm') as date, count(username) as num_acc,
count(m_1) as m_1,
count(m_2) as m_2,
count(m_3) as m_3
from #a
group by 1
order by 1
预期结果:
num_acc m_1 m_2 m_3
2019-01-01 5 3 2 3
2019-02-01 5 3 2 3
2019-03-01 5 2 0 2
从这一点开始,我将下载数据并在队列中对其进行可视化
关键是我想创建一个方便的函数。 我正在使用 PostgreSQL 开发 Dbeaver,以供您参考。 在这个函数中,我们只需要输入一个带有 ID 和日期的表,然后它就会自动执行这个过程。
到目前为止,这是我的尝试:
CREATE OR REPLACE FUNCTION test(timestamp,varchar(255))
RETURNS int
declare
counter integer :=1
stable
AS $$
LOOP
EXIT WHEN counter = 6 ;
counter := counter + 1 ;
alter table #a
add counter varchar;
update #a
counter = select user_name from #logins
where add_month(#logins.logged_at,counter) = #a.first_login
#a.first_login and #logins.username = #a.username
END LOOP
$$ LANGUAGE sql;
这很尴尬,因为 SQL 中的function
相当困难。 这是我能做的最好的事情。
(p/s:请LANGUAGE plpythonu
不能使用。我们唯一的选择是sql
)
修订:纳入额外要求
有了新信息,可以进行一些小的调整。 由于“无论您在一个月内登录多少次,我们只计算1,基于用户名”。 我们将使用 Posrgres date_trunc 函数查看每月的 1 日,而不是寻找相等的日期,无论实际登录日期是什么。 继续使用 WHERE EXISTS 确保无论用户有多少登录,我们都只计算 1。因此REVISED函数:
create or replace function collect_user_login_counts(login_start_in date)
returns table( "Date" text
, num_acc bigint
, m_1 bigint
, m_2 bigint
, m_3 bigint
)
language sql strict
as $$
-- work table exists for single execution so clear any existing data
truncate user_login_wrk;
with su_dater as
-- get each user and the earliest date of login such that the login date in not less than parameter date
(select l0.username, min(date_trunc('month', l0.logged_at))::date logged_at
from logins l0
where date_trunc('month', l0.logged_at)::date >= date_trunc('month', login_start_in)::date
group by l0.username
)
, inserter as
-- insert the the counter table the user name for least login date and the following 3 months,
-- return each row for subsequent summerization
( insert into user_login_wrk(username, logged_at, m_1,m_2,m_3)
select su.username
, su.logged_at
, (select su.username where exists
(select null
from logins l1
where l1.username = su.username
and date_trunc('month',l1.logged_at)::date = (su.logged_at + interval '1 month')::date))
, (select su.username where exists
(select null
from logins l2
where l2.username = su.username
and date_trunc('month',l2.logged_at)::date = (su.logged_at + interval '2 month')::date))
, (select su.username where exists
(select null
from logins l3
where l3.username = su.username
and date_trunc('month',l3.logged_at)::date = (su.logged_at + interval '3 month')::date))
from su_dater su
returning *
)
-- summarize count on user logins over period current and next 3 months result returned caller
select to_char(ulc.logged_at,'yyyy-mm')
, count(ulc.username)
, count(ulc.m_1)
, count(ulc.m_2)
, count(ulc.m_3)
from inserter ulc
where ulc.logged_at >= date_trunc('month',login_start_in)::date
group by to_char(logged_at,'yyyy-mm')
order by to_char(logged_at,'yyyy-mm');
$$;
测试:为了测试,我更改了您的原始日期,因此没有实际具有当月 1 日的行,也没有在同一天编号的行。 此外,函数的参数日期不会出现在数据中。
truncate logins;
insert into logins (username, logged_at) values
('a','2019-01-03'),('b','2019-01-04'),('c','2019-01-11'),('d','2019-01-15'),('e','2019-01-21'),
('a','2019-02-06'),('b','2019-02-02'),('c','2019-02-04'),('f','2019-02-08'),('g','2019-02-09'),
('h','2019-02-12'),('i','2019-02-24'),('j','2019-02-26'),('a','2019-03-02'),('b','2019-03-03'),
('f','2019-03-05'),('h','2019-03-11'),('g','2019-03-17'),('k','2019-03-31'),('l','2019-03-09'),
('m','2019-03-29'),('n','2019-03-27'),('o','2019-03-24'),('a','2019-04-06'),('f','2019-04-03'),
('g','2019-04-14'),('k','2019-04-30'),('l','2019-04-11');
select collect_user_login_counts(date '2019-01-18'); -- select as row
select * from collect_user_login_counts(date '2019-01-18'); -- select as individual columns
结果
Date | num_acc| m_1| m_2| m_3
________________________________
2019-01 | 5 | 3 | 2 | 1
2019-02 | 5 | 3 | 2 | 0
2019-03 | 5 | 2 | 0 | 0
尽管数据发生了变化,但产生了相同的结果。 顺便提一句。 我确实用你的数据测试了原件。 除了 m_3 之外,这些结果完全符合您的期望,原始回复中对此进行了解释。 我只是没有发布它,我的错误。
*原回复**
好吧,您发布的代码存在一些问题。 正如@a_horse_with_no_name 指出的# 字符在Postgres 对象名称中无效,除非名称是双引号括起来(即“#logins”)而不管模式如何。 此外,Postges 没有函数 add_months (您可以将它作为用户编写的函数使用,但我不知道。)
我注意到与您的预期结果不一致。 首先,您“通常所做”的最终查询无法产生这些结果。 查询返回年月为日期,预期有年月日。 我会假设年月。 其次,我相信 m_3 预期输出是不正确的。 这是由于您使用了 add_months(#a.date,1) 的集合 m_3。 我相信从命名结构和先前的设置来看,这是一个复制/过去的错字,应该阅读 add_months(#a.date,3)。 我会假设是后者。 然而,这确实改变了 m_3 列的结果。
您发布的功能中有一个项目我还没有完全理解。 我不确定神奇的数字 6 在做什么。 您是否尝试创建列 m_1 到 m_6,这似乎是。 然而,代码实际上会尝试创建列名计数器 6 次,这将在第二次失败。 在下面的函数中,我将保留 m_1 到 m_3。 如果 m_6 是您的目标,只需根据需要复制 m_1 编辑。 (还必须更新表定义)。
做了一些改变:
考虑到所有这些,我们得到:
-- create 'months' work table
create table user_login_wrk( username text
, logged_at date
, m_1 text
, m_2 text
, m_3 text
);
现在是重头戏。
create or replace function collect_user_login_counts(login_start_in date)
returns table( "Date" text
, num_acc bigint
, m_1 bigint
, m_2 bigint
, m_3 bigint
)
language sql strict
as $$
-- work table exists for single execution so clear any existing data
truncate user_login_wrk;
with su_dater as
-- get each user and the earliest date of login such that the login date in not less than parameter date
(select l0.username, min(l0.logged_at)::date logged_at
from logins l0
where l0.logged_at::date >= login_start_in
group by l0.username
)
, inserter as
-- insert the the counter table the user name for least login date and the following 3 months,
-- return each row for subsequent summerization
( insert into user_login_wrk(username, logged_at, m_1,m_2,m_3)
select su.username
, su.logged_at
, (select su.username where exists (select null from logins l1 where l1.username = su.username and l1.logged_at = su.logged_at + interval '1 month'))
, (select su.username where exists (select null from logins l2 where l2.username = su.username and l2.logged_at = su.logged_at + interval '2 month'))
, (select su.username where exists (select null from logins l3 where l3.username = su.username and l3.logged_at = su.logged_at + interval '3 month'))
from su_dater su
returning *
)
-- summerize count on user logins over period current and next 3 months result returned caller
select to_char(ulc.logged_at,'yyyy-mm')
, count(ulc.username)
, count(ulc.m_1)
, count(ulc.m_2)
, count(ulc.m_3)
from inserter ulc
where ulc.logged_at >= login_start_in
group by to_char(logged_at,'yyyy-mm')
order by to_char(logged_at,'yyyy-mm');
$$;
-- test
select collect_user_login_counts(date '2019-01-01'); -- select as row
select * from collect_user_login_counts(date '2019-01-01'); -- select as individual columns
select * from collect_user_login_counts(date '2019-02-01'); -- Next month
以上完全刷新工作表并重建它。 但是,有时需要/需要查看上次运行的结果。 下面提供了该功能。 (请注意,如果需要,可以提取实际查询并单独运行。
create or replace function show_user_login_counts()
returns table( "Date" text
, num_acc bigint
, m_1 bigint
, m_2 bigint
, m_3 bigint
)
language sql strict
as $$
select to_char(ulc.logged_at,'yyyy-mm')
, count(ulc.username)
, count(ulc.m_1)
, count(ulc.m_2)
, count(ulc.m_3)
from user_login_wrk ulc
group by to_char(logged_at,'yyyy-mm')
order by to_char(logged_at,'yyyy-mm') ;
$$;
-- test
select show_user_login_counts(); -- select as row
select * from show_user_login_counts(); -- select as individual columns
有几个问题没有解决。 目前每个后续 (m_1,m_2,m_3) 都是从开始日期开始的确切月份?如果用户登录的日期不是确切日期而是第二天,会发生什么? 也不允许用户在一个月内多次登录。 好吧,这些是改天的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.