SQL:SELECT除某些列外的所有列


108

SELECT除了特定列之外,是否有办法访问表中的所有列?从表中选择所有非blob或nongeometry列将非常方便。

就像是:

SELECT * -the_geom FROM segments;
  • 我曾经听说该功能被故意排除在SQL标准之外,因为更改向表中添加列将更改查询结果。这是真的?该论点有效吗?
  • 有没有解决方法,尤其是在PostgreSQL中?

您想了解除某些列以外的所有列的用例是什么?只是在进行一些手动查询时显示在屏幕上吗?它是程序的一部分吗?
joanolo 2016年

2
具有6个有意义的,短柱(一拉的表nameagesid),其能装进屏幕宽度,非常久远长二进制geom列。我想查询除几何二进制以外的所有字段,并一一写出它们的名称是乏味的。
亚当·马坦

在这种情况下,这可能与您用于交互式查询的工具有关,而不是与SQL本身有关……
joanolo 2016年

1
@joanolo Plain PostgreSQL shell。
亚当·马坦

3
这看起来很明显。有时您不想打印一两列,因为它们没有意思,或者您只希望结果表适合屏幕(特别是如果使用命令行客户端)。我期望这样的语法select (!coluns2,!column5) from sometable;
gumkins

Answers:


54

这样的功能在Postgres和SQL Standard(AFAIK)中都不存在。我认为这是一个非常有趣的问题,因此我在Google上搜索了一下,并在postgresonline.com上看到了一篇有趣的文章。

它们显示了一种直接从架构中选择列的方法:

SELECT 'SELECT ' || array_to_string(ARRAY(SELECT 'o' || '.' || c.column_name
        FROM information_schema.columns As c
            WHERE table_name = 'officepark' 
            AND  c.column_name NOT IN('officeparkid', 'contractor')
    ), ',') || ' FROM officepark As o' As sqlstmt

您可以创建一个执行类似功能的函数。在邮件列表中也讨论了这些主题,但是总体共识几乎是相同的:查询模式。

我敢肯定还有其他解决方案,但我认为它们都将涉及某种魔术模式-查询-foo。

顺便说一句:请谨慎操作,SELECT * ...因为这可能会导致性能下降


如何创建这样的功能?我找不到任何方法来创建返回未知查询的函数,我总是必须事先声明一个表。
ePascoal

17

真正的答案是您实际上无法做到。数十年来,这一直是要求的功能,开发人员拒绝实施。

流行的建议查询模式表的答案将无法有效运行,因为Postgres优化器将动态函数视为黑匣子(请参见下面的测试用例)。这意味着将不会使用索引,也不会智能地执行连接。使用诸如m4之类的某种宏系统会更好。至少它不会使优化器感到困惑(但仍可能使您感到困惑。)如果不分叉代码并自己编写功能或使用编程语言界面,就会陷入困境。

我在下面写了一个简单的概念证明,显示了在plpgsql中执行非常简单的动态执行会导致性能下降。还要注意,下面我必须强制一个将通用记录返回为特定行类型并枚举列的函数。因此,除非您要为所有表重新制作此功能,否则此方法不适用于“全选,但”。

test=# create table atest (i int primary key);
CREATE TABLE
test=# insert into atest select generate_series(1,100000);
INSERT 0 100000

test=# create function get_table_column(name text) returns setof record as
$$
    declare r record;
    begin
    for r in execute 'select  * from ' || $1 loop
    return next r;
    end loop;
    return; 
    end; 
$$ language plpgsql; 

test=# explain analyze select i from atest where i=999999;
                                                      QUERY PLAN                                    
----------------------------------------------------------------------------------------------------
-------------------
 Index Only Scan using atest_pkey on atest  (cost=0.29..8.31 rows=1 width=4) (actual time=0.024..0.0
24 rows=0 loops=1)
   Index Cond: (i = 999999)
   Heap Fetches: 0
 Planning time: 0.130 ms
 Execution time: 0.067 ms
(5 rows)

test=# explain analyze
    select * from get_table_column('atest') as arowtype(i int) where i = 999999;
                                                        QUERY PLAN                                  
----------------------------------------------------------------------------------------------------
-----------------------
 Function Scan on get_table_column arowtype  (cost=0.25..12.75 rows=5 width=4) (actual time=92.636..
92.636 rows=0 loops=1)
   Filter: (i = 999999)
   Rows Removed by Filter: 100000
 Planning time: 0.080 ms
 Execution time: 95.460 ms
(5 rows)

如您所见,函数调用扫描了整个表,而直接查询使用了索引(95.46 ms vs. 00.07ms。)这些类型的函数会处理需要使用索引或以正确顺序连接表的任何类型的复杂查询。


1
有趣的观点。这绝对是人类用户的功能,而不是代码的功能(因此,我希望如此!),这样我就可以理解使客户端负责的意义。大概是诸如扩展显示(\ x on)之类的事情完全在客户端中实现,而省略列应在相似的地方实现。
Max Murphy

13

实际上,从9.4开始引入JSONB的PostgreSQL确实可行。我正在考虑有关如何在Google Map中显示所有可用属性的类似问题(通过GeoJSON)。

irc频道上的johto建议尝试从JSONB删除元素。

这是主意

select the_geom,
  row_to_json(foo)::jsonb - 'the_geom'::text attributes
from (
  select * from
  segments
) foo

当您获取json而不是单个列时,这正是我想要的。也许json可以扩展回单独的列。


是的,也许从这里的东西,但我还没有得到这个工作yet- stackoverflow.com/questions/36174881/...
chrismarx

6

您可以(不说应该这样做)的唯一方法是使用动态sql语句。可以很容易地(如DrColossos所写)查询系统视图并查找表的结构并构建适当的语句。

PS:为什么要在不完全了解/编写表结构的情况下选择所有/某些列?


7
关于您的PS:有时我想查询带有几何列的表,而不显示非常长的几何字符串,这会使输出混乱。我不想指定所有列,因为可能有几十个。
亚当·马坦

因此,只有动态sql才能使您免于大量键入:-)的麻烦。
玛丽安

每个人都假设进行查询的人就是设计数据库的人。:-)假设您需要查询具有许多字段(超过30个)的旧数据库以生成excel,但是有一两个字段包含您不想传递的敏感信息。
yucer

3

如上所述,动态地是唯一的答案,但我不建议这样做。如果从长远来看添加更多列,但该查询不一定需要添加列怎么办?

您将开始拉出超出所需数量的列。

如果选择是插入内容的一部分,怎么办?

插入表A(col1,col2,col3 .. coln)从tableB中选择除2列之外的所有内容

列匹配将错误,并且插入将失败。

有可能,但是我仍然建议为几乎每个列都需要的每个选择写一个所需的列。


这种方法显然在编程上是错误的,但是它对于SELECTs 的控制台查询是无害且有用的。
亚当·马坦

3

如果您的目标是通过不显示具有大数据值的列来在调试过程中消除屏幕上的混乱现象,则可以使用以下技巧:

(如果尚未安装“ hstore” contrib软件包,请安装它:“ CREATE EXTENSION hstore;”)

对于具有col1,col2,col3的表“ test”,可以在显示之前将“ col2”的值设置为null:

select (r).* from (select (test #= hstore('col2',null)) as r from test) s;

或者,在显示之前将两列设置为null:

select (r).* from (select (test #= hstore('col2',null) #= hstore('col1',null)) as r from test) s;

需要注意的是,“测试”必须是一个表(别名或子选择将不起作用),因为必须定义提供给hstore的记录类型。


3

我刚刚发现了一种解决方法,但是它需要从R内部发送SQL查询。它可能对R用户有用。

基本上,dplyr程序包发送SQL(特别是PostgreSQL)查询并接受-(column_name)参数。

因此,您的示例可以编写如下:

select(segments, -(the_geom))

3

注释中,您解释说,这样做的目的是避免不显示内容较长的列的内容,而不是不显示列本身的便利:

…有时我想查询带有几何列的表,而不显示非常长的几何字符串,这会使输出混乱。我不想指定所有列,因为可能有几十个。

这可以通过辅助函数来实现,该函数将长内容替换为nulltext我的示例中的任何列,但您都可以针对要禁止的类型进行修改):

create table my_table(foo integer, bar integer, baz text);
insert into my_table(foo,bar,baz) values (1,2,'blah blah blah blah blah blah'),(3,4,'blah blah');
select * from my_table;
foo | 酒吧 巴兹                          
-:| -:| :----------------------------
  1 | 2 | 等等等等等等等等
  3 | 4 | 等等等等                    
create function f(ttype anyelement) returns setof anyelement as
$$
declare
  toid oid;
  tname text;
  nname text;
  cols text;
begin
  --
  select pg_type.oid, pg_namespace.nspname, pg_type.typname
  into toid, nname, tname
  from pg_type join pg_namespace on pg_namespace.oid=pg_type.typnamespace
  where pg_type.oid=pg_typeof(ttype);
  --
  select string_agg((case when data_type<>'text' 
                          then column_name 
                          else 'null::'||data_type||' "'||column_name||'"' end)
                   ,', ' order by ordinal_position)
  into cols
  from information_schema.columns 
  where table_schema=nname and table_name=tname;
  --
  return query execute 'select '||cols||' from '||nname||'.'||tname;
  --
end
$$ language plpgsql;
select * from f(null::my_table);
foo | 酒吧 巴兹
-:| -:| :---
  1 | 2 | null 
  3 | 4 | 空值

dbfiddle 在这里


2
  • 从应用程序的角度来看,这是一个懒惰的解决方案。应用程序不太可能自动知道如何处理新列。

    数据浏览器应用程序可以查询数据的元数据,并从正在运行的查询中排除列,或者选择列数据的子集。添加时可以排除新的BLOB。可以根据需要选择特定行的BLOB数据。

  • 在任何支持动态查询的SQL变体中,都可以使用对表元数据的查询来构建查询。为了您的意图,我将根据类型而不是名称排除列。


2

您永远不会*在SQL-VIEWS中看到...检查 \d any_view您的psql。内部表示有(自省)预处理


这里所有的讨论表明问题的提案(隐含的问题和讨论)是一个语法糖的程序员,而不是一个真正的“SQL优化问题” ......嗯,我猜,这是程序员的80%。

因此,可以实现为“ 使用自省进行预解析 ” ...查看使用SELECT *以下命令声明SQL-VIEW时PostgreSQL会做什么:VIEW-constructor转换*为所有列的列表(通过自省以及在运行时CREATE VIEW源代码)。

实现CREATE VIEW和PREPARE

这是一个可行的实现。假设表t带有字段(id serial, name text, the_geom geom)

CREATE VIEW t_full AS SELECT * FROM t;
-- is transformed into SELECT id,name,the_geom FROM t;

CREATE VIEW t_exp_geom AS SELECT * -the_geom FROM t;
-- or other syntax as EXCEPT the_geom
-- Will be transformed into SELECT id,name FROM t;

PREPARE语句相同。

...因此,这是有可能的,而这正是80%程序员所需要的,这是PREPARE和VIEWS的语法糖!


注:当然是可行的语法也许不是- column_name,如果在PostgreSQL的一些冲突,所以我们可以建议EXCEPT column_name
EXCEPT (column_name1, column_name2, ..., column_nameN)或其他。


1

这是我选择所有期望的列的功能。我结合了来自postgresonline.compostgresql tuturial以及其他来源的想法。

CREATE TABLE phonebook(phone VARCHAR(32), firstname VARCHAR(32),
lastname VARCHAR(32), address VARCHAR(64));
INSERT INTO phonebook(phone, firstname, lastname, address) 
VALUES ('+1 123 456 7890', 'John', 'Doe', 'North America'), 
('+1 321 456 7890', 'Matti', 'Meikeläinen', 'Finland'), 
('+1 999 456 7890', 'Maija', 'Meikeläinen', 'Finland'), 
('+9 123 456 7890', 'John', 'Doe', 'Canada'), 
('+1 123 456 7890', 'John', 'Doe', 'Sweden'), 
('+1 123 456 7890', 'John', 'Doe2', 'North America');

drop function all_except_one(text,text);
CREATE OR REPLACE FUNCTION all_except_one(to_remove TEXT, table_name1 TEXT) 
RETURNS void AS $$

 DECLARE 
 rec_row RECORD;
 curs1 refcursor ;

 BEGIN
  --print column names:
  raise notice '%', ('|'|| ARRAY_TO_STRING(ARRAY(SELECT 
  COLUMN_NAME::CHAR(20) FROM INFORMATION_SCHEMA.COLUMNS WHERE
  TABLE_NAME=table_name1 AND COLUMN_NAME NOT IN (to_remove) ), 
  '|') ||'|') ; 

  OPEN curs1 FOR
  EXECUTE 'select table_1  from (SELECT ' || ARRAY_TO_STRING(ARRAY(
  SELECT COLUMN_NAME::VARCHAR(50) FROM INFORMATION_SCHEMA.COLUMNS 
  WHERE TABLE_NAME=table_name1 AND COLUMN_NAME NOT IN (to_remove)    
  ), ', ') || ' FROM ' || table_name1 || ' limit 30)   table_1 ';

  LOOP
  -- fetch row into the rec_row
  FETCH curs1 INTO rec_row;

  -- exit when no more row to fetch
  EXIT WHEN NOT FOUND;

  -- build and print the row output

  raise notice '%',(select'| '|| regexp_replace( array_to_string(
  array_agg(a::char(20)),'|'),'["\(.*\)]+',   '','g') ||'|'  from 
  unnest(string_to_array(replace(replace(replace(trim(rec_row::text,
  '()'),'"',''), ', ','|'),')',' '),',')) as a);

  END LOOP;

  -- Close the cursor

  CLOSE curs1;

  END; $$ LANGUAGE plpgsql;

select  all_except_one('phone','phonebook');

--output:
--NOTICE:  |firstname           |lastname            |address             |
--NOTICE:  | John               |Doe                 |North America       |
--NOTICE:  | Matti              |Meikeläinen         |Finland             |
--NOTICE:  | Maija              |Meikeläinen         |Finland             |
--NOTICE:  | John               |Doe                 |Canada              |
--NOTICE:  | John               |Doe                 |Sweden              |
--NOTICE:  | John               |Doe2                |North America       |
-- all_except_one 
-- ----------------
-- (1 row)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.