SQL: SELECT Усі стовпці, крім деяких


108

Чи є спосіб до SELECTвсіх стовпців таблиці, крім конкретних? ІТ було б дуже зручно для вибору всіх стовпчиків, які не є кращими або негеометричними, з таблиці.

Щось на зразок:

SELECT * -the_geom FROM segments;
  • Я колись чув, що ця функціональність була навмисно виключена зі стандарту SQL, оскільки зміна додавання стовпців до таблиці змінить результати запитів. Це правда? Чи справедливий аргумент?
  • Чи існує рішення, особливо в PostgreSQL?

Який випадок використання, для якого потрібно знати всі стовпці, крім деяких? Це просто показувати на екрані під час виконання запитів вручну? Це частина програми?
joanolo

2
Стіл з 6 змістовними, короткими колонками (а-ля name, age, sid) , який чудово вписується в ширину екрану, Alongwith довгого бінарного geomколонка. Я хочу запитати всі поля, крім бінарних геометрії, і писати їхні імена по черзі є втомливим.
Адам Матан

У цьому випадку це може бути більше пов'язане з інструментом, який ви використовуєте з інтерактивним запитом, ніж із самим SQL ...
joanolo

1
@joanolo Plain 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.

BTW: Будьте обережні, 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 мс проти 00,07 мс ). Ці функції функціонують будь-який складний запит, який потребує використання індексів або приєднання таблиць у потрібному порядку .


1
Цікава перспектива. Це, безумовно, особливість для користувачів користувачів, а не код (або так, я сподіваюся!), Тому я можу бачити сенс в тому, щоб клієнт відповідав. Імовірно, такі речі, як розширений дисплей (\ х увімкнено), реалізовані виключно у клієнті, а опущені стовпці мають бути реалізовані у подібному місці.
Макс Мерфі

13

Насправді це можливо дещо з PostgreSQL, починаючи з 9.4, де був представлений JSONB. Я розмірковував над подібним питанням про те, як показати всі доступні атрибути на карті Google (через GeoJSON).

johto на irc-каналі запропонував спробувати видалити елемент з 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

Динамічно, як зазначено вище, є єдиною відповіддю, але я не рекомендую її. Що робити, якщо в довгостроковому періоді ви додасте більше стовпців, але вони не обов'язково потрібні для цього запиту?

Ви б почали тягнути більше стовпчика, ніж потрібно.

Що робити, якщо select є частиною вставки, як в

Вставити в tableA (col1, col2, col3 .. coln) Виберіть усе, окрім 2 стовпців З tableB

Збіг стовпців буде неправильним, а вставка не вдасться.

Це можливо, але я все ж рекомендую написати кожен потрібний стовпець для кожного написаного вибраного, навіть якщо потрібен майже кожен стовпець.


Цей підхід, очевидно, програмно неправильний, але він нешкідливий і корисний як запит консолі для SELECTs.
Адам Матан

3

Якщо ваша мета - видалити безлад з екрану під час налагодження, не відображаючи стовпці з великими значеннями даних, то ви можете використовувати наступний трюк:

(встановіть пакунок пакету "hstore", якщо у вас його ще немає: " CREATE EXTENSION hstore;")

Для таблиці "тест" з col1, col2, col3, ви можете встановити значення "col2" на нуль перед відображенням:

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

Або встановіть два стовпці на нуль перед відображенням:

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

Застереження полягає в тому, що "тест" повинен бути таблицею (псевдонім або підбір не буде працювати), оскільки тип запису, що подається в hstore, повинен бути визначений.


3

Щойно я виявив обхід, але він вимагає надсилання SQL запитів зсередини Р. Це може бути корисним користувачам 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;
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 | нульовий 
  3 | 4 | нуль

dbfiddle тут


2
  • З точки зору програми, це ліниве рішення. Додаток навряд чи автоматично знатиме, що робити з новою колоною.

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

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


2

Ви ніколи не бачите *в SQL-VIEWS ... перевірити \d any_viewсвій psql. Існує (інтроспективна) попередня обробка для внутрішнього представлення.


Усі обговорення тут показують, що пропозиція щодо проблеми (неявна у питанні та обговоренні) - це синтаксичний цукор для програмістів, а не справжня "проблема оптимізації SQL" ... Ну, я здогадуюсь, це для 80% програмістів.

Тож може бути реалізований як " попередній розбір з інтроспекцією" ... Подивіться, що робить PostgreSQL при оголошенні SQL-VIEW SELECT *: конструктор VIEW перетворюється *на список усіх стовпців (шляхом самоаналізу та в момент запуску СТВОРИТИ вихідний код 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% програмістів, синтаксичний цукор для ПІДГОТОВКИ та ПРОСМОТІВ!


Примітка: звичайно життєздатний синтаксис можливо не - 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.