如何解决如何将 MySQL 转换为 PostgreSQL 并添加时区转换
我想显示过去 2 年内访问过的所有课程、上次访问者和访问时间。
此 MySQL 查询列出了上次访问每个课程的时间和访问者。我正在将此查询转换为 PostgreSQL 9.3.22。我对 Postgres 的接触并不多,事实证明这非常困难。我还需要将纪元日期转换为不同的时区,因为 PostgreSQL 数据库位置不在我的时区中。编辑:timecreated
在两个数据库中都存储为纪元(例如 1612399773
)
select
userid 'lastaccesseduserid',courseid,contextid,from_unixtime(max(timecreated),'%D %M %Y') 'lastaccesseddate'
from mdl_logstore_standard_log
where timecreated >= unix_timestamp(date_sub(now(),interval 2 year))
group by courseid
这列出了这样的输出:
| lastaccesseduserid | courseid | contextid | lastaccesseddate |
|--------------------|----------|-----------|-------------------|
| 45 | 6581 | 68435 | 22nd January 2021 |
| 256676 | 32 | 4664 | 19th August 2019 |
etc.
我在转换为 PostgreSQL 方面所做的努力:
select
distinct ON (courseid) courseid,to_timestamp(max(timecreated))::timestamptz::date at time zone 'utc' at time zone 'Australia/Sydney' "last accessed date",userid
from mdl_logstore_standard_log
where timecreated >= extract(epoch from now()- interval '2 year')
group by courseid
-- error: column userid,contextid must appear in the GROUP BY clause or be used in an aggregate function
这些列都不是主键(id
是,根据 here)。按 id
分组是不好的,因为它将列出日志表中的每个条目。任何帮助表示赞赏!
解决方法
Postgres 是正确的,该查询不是有效的 SQL。
SQL-92 及更早版本不允许查询的选择列表、HAVING 条件或 ORDER BY 列表引用未在 GROUP BY 子句中命名的非聚合列。
您不能使用 group by courseid
和 select courseid,contextid,userid
,因为每个 courseid
可能有许多具有不同上下文 ID 和用户 ID 的行。您要么需要 group by courseid,userid
,要么需要 tell the database how you want those columns aggregated 喜欢 sum
或 string_agg
。
我不能告诉你哪个是正确的,但原来的从来没有真正奏效。 MySQL 只是为您随机选择一个值。
在这种情况下,服务器可以自由地从每个组中选择任何值,因此除非它们相同,否则选择的值是不确定的,这可能不是您想要的
MySQL 允许一些不明智的 SQL“扩展”,后来的版本默认将它们关闭。这个特殊的由 ONLY_FULL_GROUP_BY 控制,默认情况下 MySQL 5.7 及更高版本明智地打开。您的数据库要么将其关闭,要么太旧以至于它不是默认设置。
请参阅 MySQL Handling of GROUP BY 了解更多信息。
我建议首先启用 ONLY_FULL_GROUP_BY
并修复 MySQL 中的查询。然后移植到 Postgres。
MySQL 有很多这样的非标准特性。 PostgreSQL 更符合标准。转换为标准 SQL 和 PostgreSQL 将是一场斗争。我建议一次做一个。首先,通过打开 ANSI and TRADITITONAL SQL modes 转换为标准 SQL 并修复 MySQL 中产生的问题。然后尝试将现在更标准的 SQL 转换为 PostgreSQL。这些 SQL 模式是 MySQL 服务器配置的集合,例如 ONLY_FULL_GROUP_BY
,并且可以一次打开和修复一个。
请注意,PostgreSQL 9.3.22 已于两年前停产。做所有这些工作来更改数据库只是为了使用过时的版本是愚蠢的。考虑升级。
将时间存储为 Unix 纪元既尴尬又不必要。如果可能,请考虑在迁移数据时转换为 timestamp
。如果您还打算存储时区,请使用 timestamp with zone
。
您没有说明您的意图,但您似乎想获取每个课程 ID 的最新 timecreated
。
这在 Postgres 中不需要 GROUP BY,只需要 distinct on ()
。其额外好处是您可以包含所需的任何列,而不受 GROUP BY
规则的限制。但是,这仅适用于每个 courseid
需要一行(并且应该是“最早的”或“最新的”)。对于其他要求(例如“三个最新”),窗口函数更适合。
to_timestamp()
已经返回一个 timestamptz
,因此不需要强制转换。如果您想删除时间部分(这是 ::date
演员将要做的),我认为应该在您调整时区后完成。但是如果你不关心时间,那么调整时区似乎是徒劳的。
select distinct ON (courseid)
courseid,to_timestamp(timecreated) at time zone 'utc' at time zone 'Australia/Sydney' "last accessed date",userid
from mdl_logstore_standard_log
where to_timestamp(timecreated) >= current_timestamp - interval '2 year'
group by courseid,3 DESC
您还应该在 WHERE 子句中使用实际的 timestamp
值,因为“2 年”的持续时间可能因实际年份而异。比较时代不会考虑到这一点。
从长远来看,您可能需要考虑将列完全更改为正确的 timestamptz
列。
您也可以重复整个表达式,而不是在 order by 中引用 (3
) 中的列索引:order by courseid,to_timestamp(timecreated) at time zone 'utc' at time zone 'Australia/Sydney' DESC
而且您真的不应该使用 Postgres 9.3 - 尤其不应该用于新安装。没有理由不使用最新版本(截至 2021-02-04 为 13)。如果这是现有(旧)安装,请尽快升级。 Upgrading from 9.3.22 to 13.1 gives you 2.7 years worth of fixes (2278 of them)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。