SQL: SELECT Semua kolom kecuali beberapa


108

Apakah ada cara untuk SELECTsemua 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?

Kasus penggunaan mana yang ingin Anda ketahui semua kolom kecuali beberapa? Apakah hanya untuk ditampilkan di layar saat melakukan beberapa pertanyaan manual? Apakah ini bagian dari program?
joanolo

2
Sebuah meja dengan 6 bermakna, kolom pendek (a-la name, age, sid) yang cocok baik ke layar lebar, disertai biner panjang geomkolom. Saya ingin menanyakan semua bidang kecuali biner geometri, dan menulis nama mereka satu per satu itu membosankan.
Adam Matan

Dalam hal ini, ini mungkin lebih berkaitan dengan alat yang Anda gunakan dengan kueri interaktif daripada dengan SQL itu sendiri ...
joanolo

1
@joanolo Biasa PostgreSQL shell.
Adam Matan

3
Ini terlihat sangat jelas. Kadang-kadang Anda tidak ingin mencetak satu atau dua kolom karena tidak menarik atau Anda hanya ingin tabel hasil yang sesuai dengan layar (terutama jika klien baris perintah digunakan). Saya mengharapkan sintaksis sepertiselect (!coluns2,!column5) from sometable;
gumkins

Jawaban:


54

Fitur seperti itu tidak ada di Postgres maupun SQL Standard (AFAIK). Saya pikir ini adalah pertanyaan yang cukup menarik jadi saya mencari sedikit di Google 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: Berhati-hatilah SELECT * ...karena ini dapat memiliki penalti kinerja


Bagaimana cara membuat fungsi seperti itu? Saya tidak dapat menemukan cara untuk membuat fungsi yang mengembalikan kueri yang tidak dikenal. Saya selalu harus mendeklarasikan tabel sebelumnya.
ePascoal

17

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 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 menyaring segala jenis kueri rumit yang diperlukan untuk menggunakan indeks atau bergabung dengan tabel dalam urutan yang benar. .


1
Perspektif yang menarik. Ini jelas merupakan fitur untuk pengguna manusia daripada kode (atau jadi saya harus berharap!) Sehingga saya bisa melihat titik tentang membuat klien bertanggung jawab. Agaknya hal-hal seperti tampilan yang diperluas (\ x aktif) diimplementasikan murni di klien dan menghilangkan kolom harus diimplementasikan di tempat yang sama.
Max Murphy

13

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,
  row_to_json(foo)::jsonb - '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.


Ya, mungkin sesuatu dari sini, tapi saya belum dapat ini berfungsi- stackoverflow.com/questions/36174881/…
chrismarx

6

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 benar.

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


7
Mengenai PS Anda: Kadang-kadang saya ingin meng-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.
Adam Matan

Jadi hanya sql dinamis yang bisa menyelamatkan Anda dari banyak ketikan :-).
Marian

Semua orang berasumsi bahwa orang yang membuat permintaan adalah orang yang mendesain database. :-) Misalkan Anda perlu melakukan kueri database lama dengan banyak bidang (lebih dari 30) untuk menghasilkan excel, tetapi ada satu atau dua bidang yang memiliki informasi sensitif yang tidak ingin Anda sampaikan.
Yucer

3

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.


Pendekatan ini jelas salah secara program, tetapi tidak berbahaya dan bermanfaat sebagai permintaan konsol untuk SELECTs.
Adam Matan

3

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

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

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

Jadi contoh Anda dapat ditulis sebagai berikut:

select(segments, -(the_geom))

3

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

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

Ini dimungkinkan, dengan bantuan fungsi pembantu yang menggantikan konten lama dengan null( textkolom apa pun dalam contoh saya, tetapi Anda akan memodifikasi itu 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 | bla bla bla bla bla
  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 | batal

Aku di sini


2
  • 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 dibuat menggunakan kueri pada tabel meta data. Untuk maksud Anda, saya akan mengecualikan kolom berdasarkan jenis daripada nama.


2

Anda tidak pernah melihat *di SQL-VIEWS ... periksa \d any_viewdi psql. Ada preprocessing (introspektif) untuk representasi internal.


Semua diskusi di sini menunjukkan bahwa yang masalah usulan (tersirat dalam pertanyaan dan diskusi) adalah gula sintaks untuk programmer, bukan "masalah optimasi SQL" nyata ... Nah, saya duga, itu adalah untuk 80% dari programmer.

Jadi dapat diimplementasikan sebagai " pre-parsing dengan introspeksi" ... Lihat apa yang PostgreSQL lakukan ketika Anda mendeklarasikan SQL-VIEW dengan SELECT *: konstruktor-VIEW berubah *menjadi daftar semua kolom (dengan introspeksi dan pada saat Anda menjalankan BUAT LIHAT sumber-kode).

Implementasi untuk CREATE VIEW dan SIAPKAN

Ini adalah implementasi yang layak. Misalkan meja tdengan 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 tidak - column_name, jika ada beberapa konflik di PostgreSQL, jadi kami dapat menyarankan EXCEPT column_name,
EXCEPT (column_name1, column_name2, ..., column_nameN)atau yang lainnya.


1

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)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.