Apakah SELECT * ok dalam Pemicu. Atau apakah saya meminta masalah?


8

Saya terjebak dalam perdebatan di tempat kerja, dan saya butuh nasihat tentang kemungkinan Perangkap yang mungkin saya abaikan.

Bayangkan skenario di mana Pemicu digunakan untuk menyalin Catatan yang Dihapus ke Tabel Audit. Pemicu menggunakan SELECT *. Semua orang menunjuk dan berteriak dan mengatakan betapa buruknya hal ini.

Namun, jika modifikasi dibuat ke Struktur Tabel Utama, dan Tabel Audit diabaikan maka Pemicu akan menghasilkan kesalahan membiarkan orang tahu tabel Audit juga perlu modifikasi.

Kesalahan akan ditangkap saat pengujian pada server DEV kami. Tetapi kita perlu memastikan Production Matches DEV, jadi kami mengizinkan SELECT * dalam Sistem Produksi (hanya Pemicu).

Jadi Pertanyaan saya adalah: Saya didorong untuk menghapus SELECT *, tetapi saya tidak yakin bagaimana lagi untuk memastikan bahwa kami secara otomatis menangkap Kesalahan Pengembangan seperti ini, ada ide atau ini praktik terbaik?

Saya telah mengumpulkan contoh di bawah ini:

--Create Test Table
CREATE TABLE dbo.Test(ID INT IDENTITY(1,1), Person VARCHAR(255))
--Create Test Audit Table
CREATE TABLE dbo.TestAudit(AuditID INT IDENTITY(1,1),ID INT, Person VARCHAR(255))

--Create Trigger on Test
CREATE TRIGGER [dbo].[trTestDelete] ON [dbo].[Test] AFTER DELETE
NOT FOR REPLICATION
AS
BEGIN
    SET NOCOUNT ON;
    INSERT  dbo.TestAudit([ID], [Person])
    SELECT  *
    FROM    deleted
END

--Insert Test Data into Test
INSERT INTO dbo.Test VALUES
('Scooby')
,('Fred')
,('Shaggy')

--Perform a delete
DELETE dbo.Test WHERE Person = 'Scooby'

PEMBARUAN (ulangi pertanyaan):

Saya seorang DBA dan perlu memastikan Pengembang tidak memberikan skrip Penerapan yang dipikirkan dengan buruk dengan berkontribusi pada Dokumentasi Praktik Terbaik kami. SELECT * menyebabkan kesalahan pada DEV ketika Pengembang mengabaikan Tabel Audit (ini adalah jaring pengaman) sehingga kesalahan tersebut diketahui di awal proses pengembangan. Tetapi di suatu tempat dalam Konstitusi SQL - amandemen ke-2 berbunyi "Jangan gunakan SELECT *". Jadi sekarang ada dorongan untuk menyingkirkan Safety Net.

Bagaimana Anda mengganti Jaring Pengaman, atau haruskah saya menganggap ini sebagai praktik terbaik untuk Pemicu?

PEMBARUAN 2: (solusi)

Terima kasih atas semua masukan Anda, saya tidak yakin apakah saya memiliki jawaban yang jelas karena ini tampaknya menjadi subjek yang sangat abu-abu. Namun secara kolektif Anda telah memberikan poin diskusi yang dapat membantu pengembang kami maju dengan menetapkan Praktik Terbaik mereka.

Terima kasih Daevinatas kontribusi Anda, jawaban Anda memberikan dasar untuk beberapa mekanisme pengujian yang dapat diterapkan oleh Pengembang kami. +1

Terima kasih CM_Dayton, saran Anda yang berkontribusi pada praktik terbaik dapat bermanfaat bagi siapa saja yang memicu Pemicu Audit. +1

Terima kasih banyak ypercube, Anda telah banyak memikirkan masalah tentang tabel yang mengalami berbagai bentuk perubahan definisi. +1

Kesimpulannya:

Is Select * ok in a tigger? Ya, ini wilayah abu-abu, jangan membabi buta mengikuti ideologi "Select * is Bad".

Am I asking for Trouble? Ya, kami melakukan lebih dari sekadar menambahkan kolom baru ke tabel.


Anda menjawab sendiri dalam pertanyaan itu. pilih * akan pecah jika tabel sumber diubah. Untuk memastikan dev dan prod sama, gunakan beberapa bentuk kontrol sumber.
Bob Klimes

pertanyaan yang sedikit lebih luas, seberapa sering Anda menghapus catatan dan berapa banyak persentase total tabel? alternatif untuk pemicu adalah memiliki tanda bit yang menandai baris sebagai dihapus dan pekerjaan agen yang berjalan sesuai jadwal untuk memindahkan mereka ke tabel log. Anda bisa membangun ke dalam cek pekerjaan agen untuk melihat apakah skema tabel cocok dan pekerjaan hanya akan gagal jika ada masalah dengan langkah itu sampai itu diperbaiki.
Tanner

Saya biasanya setuju dengan SELECT *yang malas, tetapi karena Anda memiliki alasan yang sah untuk menggunakannya, itu lebih abu-abu daripada hitam-putih. Apa yang harus Anda coba lakukan adalah sesuatu seperti ini , tetapi sesuaikan agar tidak hanya memiliki jumlah kolom yang sama, tetapi bahwa nama kolom dan tipe data adalah sama (karena seseorang dapat mengubah tipe data dan masih menyebabkan masalah dalam db yang biasanya tidak ditangkap dengan SELECT *'jaring pengaman' Anda
Daevin

3
Saya suka gagasan menggunakan SELECT *sebagai jaring pengaman tetapi tidak akan menangkap semua kasus. Misalnya, jika Anda menjatuhkan kolom dan menambahkannya lagi. Ini akan mengubah urutan kolom dan (kecuali semua kolom dari jenis yang sama) memasukkan ke dalam tabel audit akan gagal atau mengakibatkan hilangnya data karena konversi tipe implisit.
ypercubeᵀᴹ

2
Saya juga bertanya-tanya bagaimana desain audit Anda akan berfungsi ketika sebuah kolom dijatuhkan dari sebuah tabel. Apakah Anda juga menjatuhkan kolom dari tabel audit (dan kehilangan semua data audit sebelumnya)?
ypercubeᵀᴹ

Jawaban:


10

Biasanya, itu dianggap pemrograman "malas".

Mengingat bahwa Anda secara khusus memasukkan dua nilai ke dalam TestAudittabel Anda di sini, saya akan berhati-hati untuk memastikan bahwa pilihan Anda juga mendapatkan tepat dua nilai. Karena jika, karena alasan tertentu, Testtabel itu memiliki atau pernah mendapat kolom ketiga, pemicu ini akan gagal.

Tidak terkait langsung dengan pertanyaan Anda tetapi jika Anda menyiapkan tabel audit, saya juga akan menambahkan beberapa kolom tambahan ke TestAuditmeja Anda ke ...

  • lacak tindakan yang Anda audit (hapus dalam hal ini, vs sisipan atau pembaruan)
  • kolom tanggal / waktu untuk dilacak ketika peristiwa audit terjadi
  • kolom ID pengguna untuk melacak siapa yang melakukan tindakan yang Anda audit.

Sehingga menghasilkan kueri seperti:

INSERT dbo.TestAudit([ID], [Person], [AuditAction], [ChangedOn], [ChangedBy])
SELECT [ID], [Person], 
   'Delete', -- or a 'D' or a numeric lookup to an audit actions table...
   GetDate(), -- or SYSDATETIME() for greater precision
   SYSTEM_USER -- or some other value for WHO made the deletion
FROM deleted

Dengan cara itu Anda mendapatkan kolom persis yang Anda butuhkan dan diaudit tentang apa / kapan / mengapa / siapa acara audit itu.


"ID pengguna" Yang ini rumit dengan audit. Biasanya, akun basis data tidak sesuai dengan pengguna sebenarnya. Jauh lebih sering, mereka berhubungan dengan aplikasi web tunggal atau jenis komponen lainnya, dengan satu set kredensial yang digunakan oleh komponen itu. (Dan kadang-kadang, komponen juga akan berbagi kredensial.) Jadi kredensial database cukup berguna sebagai pengidentifikasi siapa yang melakukan apa, kecuali jika Anda hanya tertarik pada komponen apa yang melakukannya. Tetapi menyampaikan data aplikasi yang mengidentifikasi "siapa" tidak sepenuhnya mudah dengan fungsi pemicu, sejauh yang saya tahu.
jpmc26

lihat pembaruan untuk pertanyaan.
pacreely

Masalah lain yang dapat muncul dengan SELECT * secara umum (meskipun mungkin tidak dalam contoh Anda) adalah bahwa jika kolom tabel yang mendasari tidak dalam urutan yang sama dengan kolom insert Anda, insert akan gagal.
CaM

3

Saya mengomentari ini pada pertanyaan Anda, tetapi saya pikir saya akan mencoba untuk benar-benar menyajikan solusi kode.

Saya biasanya setuju dengan SELECT *menjadi malas, tetapi karena Anda memiliki alasan yang sah untuk menggunakannya, itu lebih abu-abu daripada hitam-putih.

Apa yang harus Anda coba (menurut saya) coba lakukan adalah sesuatu seperti ini , tetapi sesuaikan untuk memastikan nama kolom dan tipe data sama (karena seseorang dapat mengubah tipe data dan masih menyebabkan masalah dalam db yang biasanya tidak ditangkap oleh AndaSELECT * keselamatan ' bersih'.

Anda bahkan dapat membuat fungsi yang akan memungkinkan Anda dengan cepat memeriksa apakah versi Audit tabel cocok dengan versi non-audit:

-- The lengths are, I believe, max values for the corresponding db objects. If I'm wrong, someone please correct me
CREATE FUNCTION TableMappingComparer(
    @TableCatalog VARCHAR(85) = NULL,
    @TableSchema VARCHAR(32) = NULL,
    @TableName VARCHAR(128) = NULL) RETURNS BIT
AS
BEGIN
    DECLARE @ReturnValue BIT = NULL;
    DECLARE @VaryingColumns INT = NULL;

    IF (@TableCatalog IS NOT NULL
            AND @TableSchema IS NOT NULL
            AND @TableName IS NOT NULL)
        SELECT @VaryingColumns = COUNT(COLUMN_NAME)
            FROM (SELECT COLUMN_NAME,
                        DATA_TYPE -- Add more columns that you want to ensure are identical
                    FROM INFORMATION_SCHEMA.COLUMNS
                    WHERE TABLE_CATALOG = @TableCatalog
                        AND TABLE_SCHEMA = @TableSchema
                        AND TABLE_NAME = @TableName
                EXCEPT
                    SELECT COLUMN_NAME,
                            DATA_TYPE -- Add more columns that you want to ensure are identical
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE (TABLE_CATALOG = @TableCatalog
                            AND TABLE_SCHEMA = @TableSchema
                            AND TABLE_NAME = @TableName + 'Audit')
                            AND (COLUMN_NAME != 'exclude your audit table specific columns')) adt;
    IF @VaryingColumns = 0
        SET @ReturnValue = 1
    ELSE IF @VaryingColumns > 0
        SET @ReturnValue = 0

    RETURN @ReturnValue;
END;

The SELECT ... EXCEPT SELECT ...Auditakan menunjukkan apa kolom dalam tabel yang tidak ada dalam tabel Audit. Anda bahkan dapat mengubah fungsi untuk mengembalikan nama kolom yang tidak sama, bukan hanya apakah mereka memetakan atau tidak, atau bahkan meningkatkan pengecualian.

Anda kemudian dapat menjalankan ini sebelum pindah dari DEVke PRODUCTIONserver untuk setiap tabel di db, menggunakan kursor di atas:

SELECT TABLE_NAME
    FROM INFORMATION_SCHEMA.TABLES
    WHERE NOT (TABLE_NAME LIKE '%Audit')

1
lihat pembaruan untuk pertanyaan
pacreely

Senang bisa membantu. Penghargaan kepada Anda karena membaca semua jawaban dan membawanya kembali ke tim Anda untuk saran; kemampuan beradaptasi dan kemauan untuk meningkatkan adalah cara departemen teknologi menjaga perusahaan berjalan, dan berjalan dengan lancar! : D
Daevin

0

Pernyataan yang akan mengeluarkan pemicu akan gagal dan pemicu akan gagal. Akan lebih baik untuk mendokumentasikan jejak dan audit jejak sehingga Anda tahu untuk memodifikasi kueri untuk menambahkan kolom daripada menentukan *.

Paling tidak Anda harus memodifikasi pemicu sehingga bisa gagal dengan anggun saat mencatat kesalahan ke sebuah tabel dan mungkin memberi peringatan pada tabel pemicu yang mencatat kesalahan.

Ini juga mengingatkan Anda, Anda dapat menempatkan pemicu atau peringatan ketika seseorang mengubah tabel dan menambahkan lebih banyak kolom atau menghapus kolom, untuk memberi tahu Anda untuk menambahkan pemicu.

Menurut saya, kinerja * tidak mengubah apa-apa, itu hanya meningkatkan kemungkinan kegagalan saat terjadi perubahan dan juga dapat menyebabkan latensi jaringan saat Anda menarik lebih banyak informasi di jaringan saat diperlukan. Ada waktu dan tempat untuk *, tetapi saya merasa seperti dijelaskan di atas Anda memiliki solusi dan alat yang lebih baik untuk dicoba.


0

Jika struktur tabel orisinal atau audit Anda berubah sama sekali, Anda menjamin bahwa Anda akan mengalami masalah dengan * pilihan Anda.

INSERT INTO [AuditTable]
(Col1,Col2,Col3)
SELECT * 
FROM [OrigTable] or [deleted];

Jika salah satu berubah, pemicu akan kesalahan.

Anda bisa melakukannya:

INSERT INTO [AuditTable]
SELECT * 
FROM [OrigTable];

Tapi seperti kata CM_Dayton, itu pemrograman yang malas dan membuka pintu bagi ketidakkonsistenan lainnya. Agar skenario ini berfungsi, Anda harus benar-benar memastikan bahwa Anda memperbarui struktur kedua tabel secara bersamaan.

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.