Postgres find all rows in database tables matching criteria on a given column

眉间皱痕 提交于 2020-04-16 03:18:09

问题


I am trying to write sub-queries so that I search all tables for a column named id and since there are multiple tables with id column, I want to add the condition, so that id = 3119093.

My attempt was:

Select * 
from information_schema.tables 
where id = '3119093' and id IN (
    Select table_name 
    from information_schema.columns 
    where column_name = 'id' );

This didn't work so I tried:

Select * 
from information_schema.tables 
where table_name IN (
    Select table_name 
    from information_schema.columns 
    where column_name = 'id' and 'id' IN (
        Select * from table_name where 'id' = 3119093));

This isn't the right way either. Any help would be appreciated. Thanks!

A harder attempt is:

CREATE OR REPLACE FUNCTION search_columns(
    needle text,
    haystack_tables name[] default '{}',
    haystack_schema name[] default '{public}'
)
RETURNS table(schemaname text, tablename text, columnname text, rowctid text)
AS $$
begin
  FOR schemaname,tablename,columnname IN
      SELECT c.table_schema,c.table_name,c.column_name
      FROM information_schema.columns c
      JOIN information_schema.tables t ON
        (t.table_name=c.table_name AND t.table_schema=c.table_schema)
      WHERE (c.table_name=ANY(haystack_tables) OR haystack_tables='{}')
        AND c.table_schema=ANY(haystack_schema)
        AND t.table_type='BASE TABLE'
        --AND c.column_name = "id"
  LOOP
    EXECUTE format('SELECT ctid FROM %I.%I WHERE cast(%I as text) like %L',
       schemaname,
       tablename,
       columnname,
       needle
    ) INTO rowctid;
    IF rowctid is not null THEN
      RETURN NEXT;
    END IF;
 END LOOP;
END;
$$ language plpgsql;

select * from search_columns('%3119093%'::varchar,'{}'::name[]) ;

The only problem is this code displays the table name and column name. I have to then manually enter

Select * from table_name where id = 3119093

where I got the table name from the code above.

I want to automatically implement returning rows from a table but I don't know how to get the table name automatically.


回答1:


I took the time to make it work for you.

For starters, some information on what is going on inside the code.

Explanation

  1. function takes two input arguments: column name and column value
  2. it requires a created type that it will be returning a set of
  3. first loop identifies tables that have a column name specified as the input argument
  4. then it forms a query which aggregates all rows that match the input condition inside every table taken from step 3 with comparison based on ILIKE - as per your example
  5. function goes into the second loop only if there is at least one row in currently visited table that matches specified condition (then the array is not null)
  6. second loop unnests the array of rows that match the condition and for every element it puts it in the function output with RETURN NEXT rec clause

Notes

  • Searching with LIKE is inefficient - I suggest adding another input argument "column type" and restrict it in the lookup by adding a join to pg_catalog.pg_type table.

  • The second loop is there so that if more than 1 row is found for a particular table, then every row gets returned.

  • If you are looking for something else, like you need key-value pairs, not just the values, then you need to extend the function. You could for example build json format from rows.


Now, to the code.

Test case

CREATE TABLE tbl1 (col1 int, id int); -- does contain values
CREATE TABLE tbl2 (col1 int, col2 int); -- doesn't contain column "id"
CREATE TABLE tbl3 (id int, col5 int); -- doesn't contain values

INSERT INTO tbl1 (col1, id)
  VALUES (1, 5), (1, 33), (1, 25);

Table stores data:

postgres=# select * From tbl1;

 col1 | id
------+----
    1 |  5
    1 | 33
    1 | 25
(3 rows)

Creating type

CREATE TYPE sometype AS ( schemaname text, tablename text, colname text, entirerow text );

Function code

CREATE OR REPLACE FUNCTION search_tables_for_column (
    v_column_name text
  , v_column_value text
)
RETURNS SETOF sometype
LANGUAGE plpgsql
STABLE
AS
$$
DECLARE
  rec           sometype%rowtype;
  v_row_array   text[];
  rec2          record;
  arr_el        text;
BEGIN
FOR rec IN
  SELECT 
      nam.nspname AS schemaname
    , cls.relname AS tablename
    , att.attname AS colname
    , null::text AS entirerow
  FROM 
    pg_attribute att
    JOIN pg_class cls ON att.attrelid = cls.oid 
    JOIN pg_namespace nam ON cls.relnamespace = nam.oid 
  WHERE 
    cls.relkind = 'r'
    AND att.attname = v_column_name
LOOP
  EXECUTE format('SELECT ARRAY_AGG(row(tablename.*)::text) FROM %I.%I AS tablename WHERE %I::text ILIKE %s',
    rec.schemaname, rec.tablename, rec.colname, quote_literal(concat('%',v_column_value,'%'))) INTO v_row_array;
  IF v_row_array is not null THEN
    FOR rec2 IN
      SELECT unnest(v_row_array) AS one_row
    LOOP
      rec.entirerow := rec2.one_row;
      RETURN NEXT rec;
    END LOOP;
  END IF;
END LOOP;
END
$$;

Exemplary call & output

postgres=# select * from search_tables_for_column('id','5');

 schemaname | tablename | colname | entirerow
------------+-----------+---------+-----------
 public     | tbl1      | id      | (1,5)
 public     | tbl1      | id      | (1,25)
(2 rows)


来源:https://stackoverflow.com/questions/38900823/postgres-find-all-rows-in-database-tables-matching-criteria-on-a-given-column

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!