SQL: SELECT все столбцы, кроме некоторых

108

Есть ли способ для SELECTвсех столбцов в таблице, кроме определенных? Это было бы очень удобно для выбора всех неблобных или негеометрических столбцов из таблицы.

Что-то вроде:

SELECT * -the_geom FROM segments;
  • Однажды я слышал, что эта функциональность была преднамеренно исключена из стандарта SQL, поскольку изменение добавления столбцов в таблицу изменит результаты запроса. Это правда? Является ли аргумент действительным?
  • Есть ли обходной путь, особенно в PostgreSQL?
Адам Матан
источник
Каков вариант использования, для которого вы хотите знать все столбцы, кроме некоторых? Это просто показывать на экране во время выполнения некоторых ручных запросов? Это часть программы?
Жоаноло
2
Стол с 6 содержательными, короткими колонками (а-ля name, age, sid) , который прекрасно вписывается в ширину экрана, Alongwith длинного бинарного geomколонка. Я хочу сделать запрос ко всем полям, кроме двоичного объекта геометрии, и писать их имена по одному утомительно.
Адам Матан
В этом случае это может быть больше связано с инструментом, который вы используете с интерактивным запросом, чем с самим SQL ...
joanolo
1
@joanolo Обычная оболочка PostgreSQL.
Адам Матан,
3
Это выглядит так очевидно. Иногда вы не хотите печатать один или два столбца, потому что они не интересны, или вы просто хотите, чтобы таблица результатов помещалась на экране (особенно если используется клиент командной строки). Я ожидал бы синтаксис вродеselect (!coluns2,!column5) from sometable;
gumkins

Ответы:

54

Такая функция не существует ни в Postgres, ни в стандарте SQL (AFAIK). Я думаю, что это довольно интересный вопрос, поэтому я немного погуглил и наткнулся на интересную статью на 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 * ...этим, поскольку это может иметь потери производительности

DrColossos
источник
Как создать такую ​​функцию? Я не могу найти способ создать функцию, которая возвращает неизвестный запрос, мне всегда нужно было бы заранее объявить таблицу.
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 мс против 00,07 мс .). Эти типы функций будут включать любой сложный запрос, который должен использовать индексы или объединять таблицы в правильном порядке. ,

user17130
источник
1
Интересная перспектива. Это определенно особенность для пользователей, а не кода (или я так надеюсь!), Поэтому я вижу смысл в том, чтобы сделать клиента ответственным. Предположительно такие вещи, как расширенное отображение (\ x on), реализованы исключительно в клиенте, а пропущенные столбцы должны быть реализованы в аналогичном месте.
Макс Мерфи
13

На самом деле это возможно в PostgreSQL, начиная с 9.4, где был представлен JSONB. Я размышлял о подобном вопросе о том, как показать все доступные атрибуты в Google Map (через GeoJSON).

johto на канале irc предлагает попробовать удалить элемент из JSONB.

Вот идея

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

Хотя вы получаете JSON вместо отдельных столбцов, это было именно то, что я хотел. Возможно, JSON можно расширить обратно в отдельные столбцы.

MLT
источник
Да, может быть, что-то отсюда, но я еще не заставил это работать
chrismarx
6

Единственный способ, которым вы можете (не говорите, что должны), это использовать динамические операторы SQL. Легко (как писал DrColossos) запрашивать системные представления, находить структуру таблицы и строить правильные операторы.

PS: Почему вы хотите выбрать все / некоторые столбцы, не зная / не записав точно структуру вашей таблицы?

Мэриан
источник
7
Что касается вашего PS: Иногда я хочу запросить таблицу с геометрическим столбцом, без отображения очень длинной геометрической строки, которая искажает вывод. Я не хочу указывать все столбцы, потому что может быть несколько десятков.
Адам Матан
Так что только динамический sql может спасти вас от большого количества печатания :-).
Marian
Все предполагают, что тот, кто делает запрос, является тем, кто разработал базу данных. :-) Предположим, что вам нужно запросить старую базу данных с большим количеством полей (более 30), чтобы создать Excel, но есть одно или два поля, которые содержат конфиденциальную информацию, которую вы не хотите предоставлять.
Юсер
3

Динамически, как указано выше, является единственным ответом, но я не буду рекомендовать его. Что, если вы добавите больше столбцов в долгосрочной перспективе, но они не обязательно требуются для этого запроса?

Вы бы начали тянуть больше столбца, чем вам нужно.

Что делать, если выбор является частью вставки, как в

Вставить в таблицу A (col1, col2, col3 .. coln) Выбрать все, кроме 2 столбцов ИЗ таблицыB

Соответствие столбцов будет неправильным, и ваша вставка не удастся.

Это возможно, но я все же рекомендую писать каждый необходимый столбец для каждого написанного выбора, даже если требуется почти каждый столбец.

Николя де Фонтене
источник
Этот подход явно программно неверен, но он безопасен и полезен как консольный запрос для SELECTs.
Адам Матан
3

Если ваша цель состоит в том, чтобы убрать помехи с экрана во время отладки, не отображая столбцы с большими значениями данных, то вы можете использовать следующий прием:

(установите пакет "hstore" contrib, если у вас его еще нет: " CREATE EXTENSION hstore;")

Для таблицы «test» с col1, col2, col3 вы можете установить значение «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;

предостережение в том, что «test» должен быть таблицей (псевдоним или подвыбор не будет работать), так как должен быть определен тип записи, передаваемый в hstore.

Шон
источник
3

Я только что обнаружил обходной путь, но он требует отправки SQL-запросов из R. Он может быть полезен для пользователей R.

По сути, dplyrпакет отправляет запросы SQL (и особенно PostgreSQL) и принимает -(column_name)аргумент.

Итак, ваш пример может быть записан следующим образом:

select(segments, -(the_geom))
Дарио Лакан
источник
3

В комментарии вы объясняете, что ваш мотив состоит в том, чтобы удобнее не отображать содержимое столбцов с длинным содержимым, а не отображать сам столбец:

… Иногда я хочу запросить таблицу с геометрическим столбцом, не отображая очень длинную геометрическую строку, которая искажает вывод. Я не хочу указывать все столбцы, потому что может быть несколько десятков.

Это возможно с помощью вспомогательной функции, которая заменяет длинное содержимое на null(любой textстолбец в моем примере, но вы бы изменили его для типов, которые вы хотите подавить):

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;
фу | бар | Baz                          
-: | -: | : ----------------------------
  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);
фу | бар | Baz
-: | -: | : ---
  1 | 2 | ноль 
  3 | 4 | значение NULL

dbfiddle здесь

Джек Дуглас
источник
2
  • С точки зрения приложения это ленивое решение. Приложение вряд ли автоматически узнает, что делать с новыми столбцами.

    Приложения браузера данных могут запрашивать метаданные для данных и исключать столбцы из выполняемых запросов или выбирать подмножество данных столбца. Новые BLOB-объекты могут быть исключены при добавлении. Данные BLOB для определенных строк могут быть выбраны по запросу.

  • В любом варианте SQL, который поддерживает динамические запросы, запрос может быть построен с использованием запроса к метаданным таблиц. Для вашего намерения я бы исключил столбцы по типу, а не по имени.

BillThor
источник
2

Вы никогда не видите *в SQL-VIEWS ... проверьте \d any_viewна свой psql. Существует (интроспективная) предварительная обработка для внутреннего представления.


Все обсуждение здесь показывает , что предложение вопроса (неявное в вопросе и дискуссиях) является синтаксис для программистов, а не реальный «оптимизация SQL вопроса» ... Ну, я думаю, это на 80% программист.

Так что может быть реализовано как « предварительный анализ с самоанализом» ... Посмотрите, что делает PostgreSQL, когда вы объявляете SQL-VIEW с помощью SELECT *: конструктор 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;

То же самое для ПОДГОТОВКИ .

... так, это возможно, и это то, что нужно 80% программистов, синтаксический сахар для ПОДГОТОВКИ и ПРОСМОТРОВ!


Примечание: конечно жизнеспособный синтаксис возможно не - column_name, если есть какой - то конфликт в PostgreSQL, так что мы можем предложить EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)или другие.

Питер Краусс
источник
1

Это моя функция, чтобы выбрать все столбцы, кроме одного. Я объединил идеи из postgresonline.com и postgresql 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)
Вели-Матти Сорвала
источник