it-swarm.asia

SQL: SELECT Semua kolom kecuali beberapa

Apakah ada cara untuk SELECT semua kolom dalam sebuah tabel, kecuali yang spesifik? Akan sangat mudah untuk memilih semua kolom non-gumpalan atau non-geometris dari sebuah tabel.

Sesuatu seperti:

SELECT * -the_geom FROM segments;
  • Saya pernah mendengar bahwa fungsi ini sengaja dikecualikan dari standar SQL karena mengubah menambahkan kolom ke tabel akan mengubah hasil kueri. Apakah ini benar? Apakah argumennya valid?
  • Apakah ada solusinya, terutama di PostgreSQL?
119
Adam Matan

Fitur seperti itu tidak ada di Postgres maupun SQL Standard (AFAIK). Saya pikir ini adalah pertanyaan yang cukup menarik jadi saya mencari di Google sedikit dan menemukan artikel yang menarik di postgresonline.com .

Mereka menunjukkan pendekatan yang memilih kolom langsung dari skema:

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

Anda dapat membuat fungsi yang melakukan sesuatu seperti itu. Topik-topik semacam itu juga dibahas di milis, tetapi keseluruhan konsensus hampir sama: menanyakan skema.

Saya yakin ada solusi lain tapi saya pikir mereka semua akan melibatkan semacam skema sihir-queriying-foo.

BTW: Hati-hati dengan SELECT * ... karena ini dapat memiliki penalti kinerja

59
DrColossos

Jawaban sebenarnya adalah Anda tidak bisa melakukannya secara praktis. Ini telah menjadi fitur yang diminta selama beberapa dekade dan pengembang menolak untuk mengimplementasikannya.

Jawaban populer yang menyarankan agar kueri tabel skema tidak akan dapat berjalan secara efisien karena pengoptimal Postgres menganggap fungsi dinamis sebagai kotak hitam (lihat kotak uji di bawah). Itu berarti bahwa indeks tidak akan digunakan dan bergabung tidak akan dilakukan secara cerdas. Anda akan jauh lebih baik dengan semacam sistem makro seperti m4. Setidaknya itu tidak akan membingungkan pengoptimal (tetapi mungkin masih membingungkan Anda.) Tanpa memalsukan kode dan menulis fitur sendiri atau menggunakan antarmuka bahasa pemrograman Anda terjebak.

Saya menulis bukti sederhana konsep di bawah ini yang menunjukkan betapa buruknya kinerja dengan eksekusi dinamis yang sangat sederhana di plpgsql. Perhatikan juga, bahwa di bawah ini saya harus memaksa fungsi mengembalikan catatan generik ke jenis baris tertentu dan menghitung kolom. Jadi metode ini tidak akan berfungsi untuk 'pilih semua kecuali' kecuali Anda ingin membuat kembali fungsi ini untuk semua tabel Anda.

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)

Seperti yang Anda lihat, pemanggilan fungsi memindai seluruh tabel sementara kueri langsung menggunakan indeks (95,46 ms vs. 00,07 ms.) Jenis-jenis fungsi ini akan menampung segala jenis kueri rumit yang diperlukan untuk menggunakan indeks. atau gabung tabel dengan urutan yang benar.

18
user17130

Sebenarnya agak mungkin dengan PostgreSQL dimulai dengan 9,4 di mana JSONB diperkenalkan. Saya sedang memikirkan pertanyaan serupa tentang cara menampilkan semua atribut yang tersedia di Google Map (via GeoJSON).

johto pada saluran irc disarankan untuk mencoba menghapus elemen dari JSONB.

Inilah idenya

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

Sementara Anda mendapatkan json bukan kolom individual, itu persis apa yang saya inginkan. Mungkin json dapat diperluas kembali ke kolom individual.

14
mlt

Satu-satunya cara Anda dapat (jangan katakan Anda harus) melakukannya adalah dengan menggunakan pernyataan sql dinamis. Sangat mudah (seperti yang ditulis DrColossos) untuk menanyakan tampilan sistem dan menemukan struktur tabel dan membuat pernyataan yang tepat.

PS: Mengapa Anda ingin memilih semua/beberapa kolom tanpa mengetahui/menulis persis struktur tabel Anda?

6
Marian

Dalam komentar Anda menjelaskan bahwa motif Anda adalah untuk kenyamanan tidak menampilkan konten kolom dengan konten yang panjang, bukan daripada tidak menampilkan kolom itu sendiri:

... Kadang-kadang saya ingin query tabel dengan kolom geometris, tanpa menampilkan string geometri yang sangat panjang yang merusak output. Saya tidak ingin menentukan semua kolom, karena mungkin ada beberapa lusinan.

Ini dimungkinkan, dengan bantuan fungsi helper yang menggantikan konten lama dengan null (any text kolom dalam contoh saya, tetapi Anda akan mengubahnya untuk jenis yang ingin Anda tekan):

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 | bar | baz 
 -: | -: | : ---------------------------- 
 1 | 2 | blah blah blah blah blah blah 
 3 | 4 | bla bla 
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 | bar | baz 
 -: | -: | : --- 
 1 | 2 |  null  
 3 | 4 |  null 

dbfiddle di sini

Jika tujuan Anda adalah untuk menghapus kekacauan dari layar selama proses debug dengan tidak menampilkan kolom dengan nilai data yang besar, maka Anda dapat menggunakan trik berikut:

(instal paket contrib "hstore" jika Anda belum memilikinya: "CREATE EXTENSION hstore; ")

Untuk tabel "test" dengan col1, col2, col3, Anda dapat mengatur nilai "col2" menjadi nol sebelum menampilkan:

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

Atau, atur dua kolom ke nol sebelum menampilkan:

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

peringatannya adalah bahwa "test" harus berupa tabel (alias atau subselect tidak akan berfungsi) karena tipe record yang dimasukkan ke hstore harus didefinisikan.

3
Sean

Ada solusi yang baru saja saya temukan, tetapi diperlukan untuk mengirim pertanyaan SQL dari dalam R. Ini mungkin berguna bagi pengguna R.

Pada dasarnya paket dplyr mengirimkan SQL (dan khususnya PostgreSQL) permintaan dan menerima argumen -(column_name).

Jadi contoh Anda dapat ditulis sebagai berikut:

select(segments, -(the_geom))
3
Dario Lacan

Secara dinamis seperti yang dinyatakan di atas adalah satu-satunya jawaban tetapi saya tidak akan merekomendasikannya. Bagaimana jika Anda menambahkan lebih banyak kolom dalam jangka panjang tetapi belum tentu diperlukan untuk permintaan itu?

Anda akan mulai menarik lebih banyak kolom daripada yang Anda butuhkan.

Bagaimana jika pilih adalah bagian dari sisipan seperti pada

Masukkan ke tableA (col1, col2, col3 .. coln) Pilih semuanya kecuali 2 kolom dari tabelB

Kecocokan kolom akan salah dan sisipan Anda akan gagal.

Itu mungkin tetapi saya masih menyarankan untuk menulis setiap kolom yang diperlukan untuk setiap pilih yang ditulis walaupun hampir setiap kolom diperlukan.

3
  • Dari perspektif aplikasi, ini adalah solusi malas. Aplikasi tidak mungkin secara otomatis tahu apa yang harus dilakukan dengan kolom baru.

    Aplikasi peramban data dapat meminta metadata untuk data dan mengecualikan kolom dari kueri yang sedang dijalankan, atau memilih subset dari data kolom. Gumpalan baru dapat dikecualikan saat ditambahkan. Data BLOB untuk baris tertentu dapat dipilih sesuai permintaan.

  • Dalam varian SQL apa pun yang mendukung kueri dinamis, kueri dapat dibangun menggunakan kueri pada tabel meta data. Untuk maksud Anda, saya akan mengecualikan kolom berdasarkan jenis daripada nama.

3
BillThor

Anda tidak pernah melihat * Di SQL-VIEWS ... centang \d any_view Di psql Anda. Ada (introspektif) preprocessing untuk representasi internal.


Semua diskusi di sini menunjukkan bahwa proposal masalah (tersirat dalam pertanyaan dan diskusi) adalah gula sintaksis untuk programmer, bukan "masalah optimasi SQL" yang nyata ... Yah, tebakan saya, itu untuk 80% programmer.

Jadi dapat diimplementasikan sebagai " pre-parsing dengan introspeksi" ... Lihat apa yang PostgreSQL lakukan ketika Anda mendeklarasikan SQL-VIEW dengan SELECT *: Konstruktor VIEW mengubah * Ke dalam daftar semua kolom (dengan introspeksi dan saat Anda menjalankan kode sumber CREATE VIEW).

Implementasi untuk CREATE VIEW dan SIAPKAN

Ini adalah implementasi yang layak. Misalkan tabel t dengan bidang (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;

Sama untuk pernyataan SIAPKAN .

... jadi, itu mungkin, dan itulah yang dibutuhkan 80% programmer, gula sintaksis untuk SIAPKAN dan LIHAT!


CATATAN: tentu saja sintaks yang layak mungkin bukan - column_name, Jika ada beberapa konflik di PostgreSQL, jadi kami dapat menyarankan EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN) atau lainnya.

2
Peter Krauss

Ini adalah fungsi saya untuk memilih semua kolom yang diharapkan. Saya menggabungkan ide dari postgresonline.com dan postgresql tuturial dan dari sumber lain.

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)
1