【问题标题】:SELECT statement inside Loop循环内的 SELECT 语句
【发布时间】:2019-10-02 16:20:44
【问题描述】:

我们的应用程序使用 Rails 公寓 gem,因此每个微型站点都有自己的架构。因此,我们有 50 个模式,每个模式都有自己的用户表。

使用 SQL(Postgres),如何从所有 50 个模式中选择用户,而不必遍历每个模式?

    DO $$
DECLARE
table_name text;
BEGIN
  FOR schema_name IN SELECT schema FROM tenants LOOP
EXECUTE 'SELECT * FROM ' ||  schema_name || '.users';
  END LOOP;
END;
$$;

结果

subdomain1, 'john smith'
subdomain1, 'mary smith'
subdomain2, 'charles geiger'
subdomain2, 'ann geiger'
subdomain3, 'allison reidy'

【问题讨论】:

  • 所以你有 50 个不同的表,同名,但每个表都在不同的模式中?
  • 没错。截至一分钟前,它实际上是 97 个模式。该网站的开发人员使用了 gem:github.com/influitive/apartment
  • @parfait - 不幸的是,我只是对现有项目进行数据分析。我没有受雇在由上市公司开发的网站上工作。
  • 如果我的任务是对此进行分析,我会开发一个 ETL 流程来从每个模式中提取并将其推入一个具有相同表的新模式中,每个表都有一个额外的列来描述源架构。然后我会在此之上通过分析构建(也可以完全移动到另一个数据库)。然后你运行一个作业来保持更新。
  • @JuanCarlosOropeza - 是的!这正是我所要求的,如果我能弄清楚的话。我在问题中的行不是 SELECT * FROM pg_catalog.pg_tables,而是给出了每个模式的名称。从租户中选择子域;

标签: sql postgresql


【解决方案1】:

您可以通过以下方式生成您正在寻找的查询:

WITH relevant_tables AS (
  SELECT CONCAT(nspname, '.', relname) as table_name,
                 CONCAT('SELECT * FROM ', nspname, '.', relname) as table_query
      FROM pg_class c
      LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
      WHERE relkind = 'r'
      AND relname = 'users'
)
SELECT string_agg(table_query, ' UNION ALL ') as final_query FROM relevant_tables

然后您可以使用DO 块来执行它。

edit considering comments:

WITH relevant_tables AS (
  SELECT CONCAT(nspname, '.', relname) as table_name,
                 CONCAT('SELECT * FROM ', nspname, '.', relname) as table_query,
                 nspname as schema
      FROM pg_class c
      LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
      WHERE relkind = 'r'
      AND relname = 'users'
)
SELECT string_agg(table_query, ' UNION ALL ') as final_query
FROM relevant_tables a
LEFT JOIN (SELECT DISTINCT schema FROM tenants) b ON a.schema = b.schmea
WHERE b.schema IS NOT NULL

所以即使有一个DO 块并将结果存储到表中(schema.table):

DO
LANGUAGE plpgsql
$$
DECLARE
  stmt text;
BEGIN
    stmt = (
      WITH relevant_tables AS (
    SELECT CONCAT(nspname, '.', relname) as table_name,
           CONCAT('SELECT * FROM ', nspname, '.', relname) as table_query,
           nspname as schema
      FROM pg_class c
      LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
     WHERE relkind = 'r'
       AND relname = 'users'
  )
  SELECT CONCAT('DROP TABLE IF EXISTS schema.table; CREATE TABLE schema.table AS ', string_agg(table_query, ' UNION ALL ')) as final_query
    FROM relevant_tables a
    LEFT JOIN (SELECT DISTINCT schema FROM tenants) b ON a.schema = b.schmea
   WHERE b.schema IS NOT NULL
    );
    EXECUTE stmt;
END;
$$;

【讨论】:

  • 这不会从表 SELECT schema FROM tenants 中获取模式名称。
  • 检查编辑:我左加入了您的表,并且只使用了您的 tenants 对象中的那些架构名称
  • 如何将 do 块添加到上面的代码中,因为这是最重要的部分?非常感谢您的帮助!新的模式会定期添加和删除,每次都手动编辑 SQL 查询是不可行的。
  • 如果您想执行此查询并将结果存储到某个表 (schema.table) 中,则只需将 string_agg(table_query, ' UNION ALL ') 替换为 CONCAT('DROP TABLE IF EXISTS schema.table; CREATE TABLE schema.table AS ', string_agg(table_query, ' UNION ALL '))
  • 我认为您的最新评论是一个笑话,因为它建议删除表格。这个问题似乎是关于获取模式/表名称列表的问题。
【解决方案2】:

您可能需要UNION 运算符,特别是UNION ALL

基本形式是:

SELECT a,b,c
  FROM schema1.table
UNION ALL
SELECT a,b,c
  FROM schema2.table
UNION ALL
SELECT ...
  ...

基本上,您可以将UNION ALL 放在两个不同的SELECT 语句之间,您的查询将在一个结果集中返回两个SELECTs 的结果。

请注意,这仅在两个 SELECT 查询返回相同的数据类型集时才有效。所以...

SELECT an_int, another_int, a_text
  ...
UNION ALL
SELECT a_text, another_text, a_timestamp
  ...

会抛出错误。

由于您可以将其放在SELECT 语句之间,因此您可以将大量SELECT 语句连续链接起来。是的,即使是其中的 97 个,如果您需要的话。

因为它可能很麻烦,所以人们通常会创建UNION ALL 查询的视图并与之抗衡。在您的情况下,必须为每个新架构更新该视图,因此它可能不可行,但它是一个选项。

哦,差点忘了。 ALL 部分仅表示“返回每个查询的所有结果”。如果您只使用UNION 而不使用ALL,则数据库将执行重复数据删除过程。例如,如果您在两个不同的模式中有相同的user,它只会返回其中一个。 UNION ALL 将返回两者。

最后,可以让事情变得更容易一点的一点是,如果您确实创建了一个视图,请包含源代码。类似的东西

CREATE VIEW blah AS
  SELECT 'schema1', a,b,c
    FROM schema1.table
  UNION ALL
  SELECT 'schema2', a,b,c
    FROM schema2.table
  UNION ALL
  ...

希望有帮助!

【讨论】:

  • 新增租户时新增schema,需要动态查询
【解决方案3】:

另一种解决方案,甚至允许您将其作为查询运行,只打印结果:

首先,运行此命令以创建一个调用查询的函数(将some_schema 更改为您可以用来存储它的架构,并将some_function_name 更改为您想要使用的名称)。我不知道结果应该是什么样子,因为我没有看到您的数据,但如果它是一个包含 2 个 varchar 列(subdomainname)的表,那么它将是这样的:

CREATE OR REPLACE FUNCTION some_schema.some_function_name()
returns TABLE(subdomain varchar(300), name varchar(300))
AS $BODY$
DECLARE
  stmt text;
  BEGIN
    stmt = (
      WITH relevant_tables AS (
        SELECT CONCAT(nspname, '.', relname) as table_name,
        CONCAT('SELECT * FROM ', nspname, '.', relname) as table_query,
        nspname as schema
        FROM pg_class c
        LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE relkind = 'r'
        AND relname = 'users'
      )
      SELECT string_agg(table_query, ' UNION ALL ') as final_query
      FROM relevant_tables a
      LEFT JOIN (SELECT DISTINCT schema FROM tenants) b ON a.schema = b.schmea
      WHERE b.schema IS NOT NULL
    );
    return query EXECUTE stmt;
  end; $BODY$
language plpgsql
;

有了这个功能,你就可以运行了:

select * from some_schema.some_function_name()

你应该会看到你的结果。

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2019-05-24
    • 2019-02-23
    • 2017-05-02
    • 2018-08-22
    • 2014-06-29
    • 2014-02-02
    相关资源
    最近更新 更多