如何旋转两列表格?

发布于 2025-01-16 07:27:53 字数 948 浏览 4 评论 0原文

这可能是一个新手问题——我还在学习。我在 PostgreSQL 9.6 上使用以下查询:

SELECT locales, count(locales) FROM (
    SELECT lower((regexp_matches(locale, '([a-z]{2,3}(-[a-z]{2,3})?)', 'i'))[1]) 
    AS locales FROM users) 
AS _ GROUP BY locales

我的查询返回以下动态行:

localescount
en10
fr7
de3
n extra locales (~300)...n-count

我正在尝试旋转它以便区域设置值最终作为单行的列,如下所示:

enfrden extra locales (~300)...
1073n-count

我必须这样做才能很好地处理时间序列db/app

我尝试过使用 crosstab(),但所有示例都显示具有 3 列或更多列的更好定义的表。

我查看了使用 join 的示例,但我不知道如何动态地执行此操作。

This might be a novice question – I'm still learning. I'm on PostgreSQL 9.6 with the following query:

SELECT locales, count(locales) FROM (
    SELECT lower((regexp_matches(locale, '([a-z]{2,3}(-[a-z]{2,3})?)', 'i'))[1]) 
    AS locales FROM users) 
AS _ GROUP BY locales

My query returns the following dynamic rows:

localescount
en10
fr7
de3
n additional locales (~300)...n-count

I'm trying to rotate it so that locale values end up as columns with a single row, like this:

enfrden additional locales (~300)...
1073n-count

I'm having to do this to play nice with a time-series db/app

I've tried using crosstab(), but all the examples show better defined tables with 3 or more columns.

I've looked at examples using join, but I can't figure out how to do it dynamically.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

又爬满兰若 2025-01-23 07:27:53

基本查询

在 Postgres 10 或更高版本中,您可以使用更简单、更快的 regexp_match() 而不是 regexp_matches()。 (因为无论如何,您只获取每行的第一个匹配项。)但是不必费心,可以使用更简单的 substring() 来代替:

SELECT lower(substring(locale, '(?i)[a-z]{2,3}(?:-[a-z]{2,3})?')) AS locale
     , count(*)::int AS ct
FROM   users
WHERE  locale ~* '[a-z]{2,3}'  -- eliminate NULL, allow index support
GROUP  BY 1
ORDER  BY 2 DESC, 1

比原始基本查询更简单、更快。

关于 GROUP BYORDER BY 中的序数:

细微差别:regexp_matches()不匹配则返回无行,而substring() 返回null。我添加了一个 WHERE 子句来消除先验不匹配 - 并允许索引支持(如果适用),但我不希望索引在这里提供帮助。

请注意前缀 (?i),这是所谓的 "嵌入选项" 使用不区分大小写的匹配。

添加了确定性 ORDER BY 子句。您需要它来实现简单的 crosstab()

另外:对于“en_US”等区域设置,您可能需要在模式中使用 _ 而不是 -

尽管您可能会尝试

,但 SQL 不允许在单个查询中使用动态结果列。您需要两次往返服务器。看;

可以 使用动态生成的 crosstab() 查询。基础知识:

动态查询:

但是由于您生成单行纯整数值,我建议采用一种简单的方法:

SELECT 'SELECT ' || string_agg(ct || ' AS ' || quote_ident(locale), ', ')
FROM  (
   SELECT lower(substring(locale, '(?i)[a-z]{2,3}(?:-[a-z]{2,3})?')) AS locale
        , count(*)::int AS ct
   FROM   users
   WHERE  locale ~* '[a-z]{2,3}'
   GROUP  BY 1
   ORDER  BY 2 DESC, 1
   ) t

生成以下形式的查询:

SELECT 10 AS en, 7 AS fr, 3 AS de, 3 AS "de-at"

执行它以生成您的期望的结果。

在 psql 中,您可以将 \gexec 附加到生成的查询中,以将生成的 SQL 字符串立即反馈给服务器。请参阅:

Base query

In Postgres 10 or later you could use the simpler and faster regexp_match() instead of regexp_matches(). (Since you only take the first match per row anyway.) But don't bother and use the even simpler substring() instead:

SELECT lower(substring(locale, '(?i)[a-z]{2,3}(?:-[a-z]{2,3})?')) AS locale
     , count(*)::int AS ct
FROM   users
WHERE  locale ~* '[a-z]{2,3}'  -- eliminate NULL, allow index support
GROUP  BY 1
ORDER  BY 2 DESC, 1

Simpler and faster than your original base query.

About those ordinal numbers in GROUP BY and ORDER BY:

Subtle difference: regexp_matches() returns no row for no match, while substring() returns null. I added a WHERE clause to eliminate non-matches a-priori - and allow index support if applicable, but I don't expect indexes to help here.

Note the prefixed (?i), that's a so-called "embedded option" to use case-insensitive matching.

Added a deterministic ORDER BY clause. You'd need that for a simple crosstab().

Aside: you might need _ in the pattern instead of - for locales like "en_US".

Pivot

Try as you might, SQL does not allow dynamic result columns in a single query. You need two round trips to the server. See;

You can use a dynamically generated crosstab() query. Basics:

Dynamic query:

But since you generate a single row of plain integer values, I suggest a simple approach:

SELECT 'SELECT ' || string_agg(ct || ' AS ' || quote_ident(locale), ', ')
FROM  (
   SELECT lower(substring(locale, '(?i)[a-z]{2,3}(?:-[a-z]{2,3})?')) AS locale
        , count(*)::int AS ct
   FROM   users
   WHERE  locale ~* '[a-z]{2,3}'
   GROUP  BY 1
   ORDER  BY 2 DESC, 1
   ) t

Generates a query of the form:

SELECT 10 AS en, 7 AS fr, 3 AS de, 3 AS "de-at"

Execute it to produce your desired result.

In psql you can append \gexec to the generating query to feed the generated SQL string back to the server immediately. See:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文