Mengapa SQL Server membutuhkan panjang datatype sama ketika menggunakan UNPIVOT?

28

Saat menerapkan UNPIVOTfungsi ke data yang tidak dinormalisasi, SQL Server mensyaratkan bahwa tipe data dan panjangnya harus sama. Saya mengerti mengapa datatype harus sama tetapi mengapa UNPIVOT membutuhkan panjangnya harus sama?

Katakanlah saya memiliki data sampel berikut yang harus saya batalkan unpivot:

CREATE TABLE People
(
    PersonId int, 
    Firstname varchar(50), 
    Lastname varchar(25)
)

INSERT INTO People VALUES (1, 'Jim', 'Smith');
INSERT INTO People VALUES (2, 'Jane', 'Jones');
INSERT INTO People VALUES (3, 'Bob', 'Unicorn');

Jika saya mencoba untuk UNPIVOT Firstnamedan Lastnamekolom yang mirip dengan:

select PersonId, ColumnName, Value  
from People
unpivot
(
  Value 
  FOR ColumnName in (FirstName, LastName)
) unpiv;

SQL Server menghasilkan kesalahan:

Msg 8167, Level 16, State 1, Line 6

Jenis kolom "Nama belakang" bertentangan dengan jenis kolom lain yang ditentukan dalam daftar UNPIVOT.

Untuk menyelesaikan kesalahan, kita harus menggunakan subquery untuk terlebih dahulu membuat Lastnamekolom dengan panjang yang sama seperti Firstname:

select PersonId, ColumnName, Value  
from
(
  select personid, 
    firstname, 
    cast(lastname as varchar(50)) lastname
  from People
) d
unpivot
(
  Value FOR 
  ColumnName in (FirstName, LastName)
) unpiv;

Lihat SQL Fiddle dengan Demo

Sebelum UNPIVOT diperkenalkan di SQL Server 2005, saya akan menggunakan SELECTwith UNION ALLuntuk membatalkan penguncian firstname/ lastnamekolom dan kueri akan berjalan tanpa perlu mengonversi kolom ke panjang yang sama:

select personid, 'firstname' ColumnName, firstname value
from People
union all
select personid, 'LastName', LastName
from People;

Lihat SQL Fiddle dengan Demo .

Kami juga dapat berhasil mem-undivot data menggunakan CROSS APPLYtanpa memiliki panjang yang sama pada tipe data:

select PersonId, columnname, value
from People
cross apply
(
    select 'firstname', firstname union all
    select 'lastname', lastname
) c (columnname, value);

Lihat SQL Fiddle dengan Demo .

Saya telah membaca MSDN tapi saya tidak menemukan penjelasan alasan untuk memaksa panjang pada datatype menjadi sama.

Apa logika di balik yang membutuhkan panjang yang sama saat menggunakan UNPIVOT?

Taryn
sumber
4
(Kemungkinan tidak berhubungan tetapi ...) Ketegasan yang sama diterapkan ketika membandingkan tipe kolom dari dua bagian CTE rekursif.
Andriy M

Jawaban:

25

Apa logika di balik yang membutuhkan panjang yang sama saat menggunakan UNPIVOT?

Pertanyaan ini hanya dapat benar-benar dijawab oleh orang-orang yang bekerja pada implementasi UNPIVOT. Anda mungkin dapat memperoleh ini dengan menghubungi mereka untuk mendapatkan dukungan . Berikut ini adalah pemahaman saya tentang alasan tersebut, yang mungkin tidak 100% akurat:


T-SQL berisi sejumlah contoh semantik aneh dan perilaku kontra-intuitif lainnya. Beberapa di antaranya pada akhirnya akan hilang sebagai bagian dari siklus penghinaan, tetapi yang lain mungkin tidak pernah 'ditingkatkan' atau 'diperbaiki'. Terlepas dari hal lain, ada aplikasi yang bergantung pada perilaku ini, sehingga kompatibilitas ke belakang harus dipertahankan.

Aturan untuk konversi tersirat, dan derivasi jenis ekspresi akun untuk proporsi signifikan dari keanehan yang disebutkan di atas. Saya tidak iri pada penguji yang harus memastikan bahwa perilaku aneh (dan sering tidak berdokumen) dipertahankan (di bawah semua kombinasi SETnilai sesi dan sebagainya) untuk versi baru.

Karena itu, tidak ada alasan untuk tidak melakukan perbaikan, dan menghindari kesalahan di masa lalu, ketika memperkenalkan fitur bahasa baru (dengan jelas tidak ada bagasi kompatibilitas ke belakang). Fitur baru seperti ekspresi tabel umum rekursif (seperti yang disebutkan oleh Andriy M dalam komentar) dan UNPIVOTbebas untuk memiliki semantik yang relatif waras dan aturan yang jelas.

Akan ada berbagai pandangan tentang apakah termasuk panjang dalam jenis terlalu banyak mengetik secara eksplisit, tetapi secara pribadi saya menyambutnya. Dalam pandangan saya, jenis varchar(25)dan varchar(50)yang tidak sama, lebih dari decimal(8)dan decimal(10)yang. Konversi jenis casing khusus mempersulit hal-hal yang tidak perlu dan tidak menambah nilai nyata, menurut saya.

Orang bisa berpendapat bahwa hanya konversi implisit yang mungkin kehilangan data yang harus dinyatakan secara eksplisit, tetapi ada juga kasus tepi di sana. Pada akhirnya, konversi akan diperlukan, jadi sebaiknya kita membuatnya eksplisit.

Jika konversi implisit dari varchar(25)ke varchar(50)diizinkan, itu hanya akan menjadi konversi implisit lain (kemungkinan besar tersembunyi), dengan semua kasus tepi aneh yang biasa dan SETpengaturan sensitivitas. Mengapa tidak menjadikan implementasi sesederhana dan se eksplisit mungkin? (Namun, tidak ada yang sempurna, dan memalukan bahwa bersembunyi varchar(25)dan varchar(50)di dalam a sql_variantdiperbolehkan.)

Menulis ulang UNPIVOTdengan APPLYdan UNION ALLmenghindari perilaku tipe (lebih baik) karena aturan UNIONtunduk pada kompatibilitas ke belakang, dan didokumentasikan dalam Books Online sebagai memungkinkan berbagai jenis selama mereka dapat dibandingkan menggunakan konversi implisit (yang aturan misterius dari prioritas tipe data digunakan, dan sebagainya).

Solusinya melibatkan menjadi eksplisit tentang tipe data dan menambahkan konversi eksplisit jika perlu. Ini terlihat seperti kemajuan bagi saya :)

Salah satu cara untuk menulis solusi yang diketik secara eksplisit:

SELECT
    U.PersonId,
    U.ColumnName,
    U.Value
FROM dbo.People AS P
CROSS APPLY
(
    VALUES (CONVERT(varchar(50), Lastname))
) AS CA (Lastname)
UNPIVOT
(
    Value FOR
    ColumnName IN (P.Firstname, CA.Lastname)
) AS U;

Contoh CTE rekursif:

-- Fails
WITH R AS
(
    SELECT Dummy = 'A row'
    UNION ALL
    SELECT 'Another row'
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

-- Succeeds
WITH R AS
(
    SELECT Dummy = CONVERT(varchar(11), 'A row')
    UNION ALL
    SELECT CONVERT(varchar(11), 'Another row')
    FROM R
    WHERE Dummy = 'A row'
)
SELECT Dummy
FROM R;

Akhirnya, perhatikan bahwa penulisan ulang yang digunakan CROSS APPLYdalam pertanyaan tidak sama dengan UNPIVOT, karena ia tidak menolak NULLatribut.

Paul White mengatakan GoFundMonica
sumber
1

The UNPIVOTOperator memanfaatkan INoperator. The spesifikasi untuk operator IN (gambar di bawah) menunjukkan bahwa baik test_expression(dalam hal ini, di sebelah kiri dari IN) dan masing-masing expression(di sisi kanan IN) harus tipe data yang sama. Berkat properti transitif kesetaraan, setiap ekspresi juga harus dari tipe data yang sama.

masukkan deskripsi gambar di sini

dev_etter
sumber
Benar, saya mengerti persyaratan datatype tetapi pertanyaannya adalah mengapa panjangnya harus sama.
Taryn
Saya mengabaikan itu, dan ya, operator IN biasanya tidak peduli tentang panjangnya.
dev_etter
Alternatif yang memungkinkan Anda untuk mengabaikan kebutuhan untuk menentukan panjang adalah dengan melemparkan masing-masing sebagai SQL_Variant: sqlfiddle.com/#!3/13b9a/2/0
dev_etter