Problem u slučaju UNPIVOT-a kolona tipa string
Objavljeno: 02-01-2014 | Autor: Tatjana Mađer | Kategorija: T-SQL
0
UNPIVOT operator u T-SQL-u se pojavljuje od verzije SQL Server-a 2005 pa nadalje. To je operator koji omogućava normalizaciju podataka tj. transformaciju naziva kolona u vrednosti jedne kolone i podataka u skladu sa tim. Prilikom korišćenja ovog operatora, neophodno je da sve kolone koje se nalaze u UNPIVOT listi budu istog tipa. Postoji još jedno ograničenje koje se tiče podataka tipa string: neophodno je da sve kolone osim istog tipa i iste dužine, imaju i istu kolaciju. Ukoliko to nije ispunjeno, SQL server vraća grešku
The type of column „YYY“ conflicts with the type of other columns specified in the UNPIVOT list.
koja ne upućuje na konkretan problem i na osnovu koje, ukoliko nemate informaciju o različitosti kolacije medju kolonama, nije lako niti brzo doći do rešenja problema.
U nastavku će na primeru biti objašnjen problem i potencijalna rešenja.
Inicijalni zahtev bi bio uraditi UNPIVOT result set-a dobijenog kao join kombinacije čvrstih i privremenih tabela gde kolone tipa string sve dolaze iz privremenih tabela. Da bismo mogli da reprodukujemo problem koji pritom nastaje kroz primere, result set ćemo ubaciti u privremenu tabelu #PREPARE i uprostiti zahtev, tj. definisati ga kao: potrebno je uraditi UNPIVOT privremene tabele #PREPARE po svim kolonama osim kolone ORDNL_NUM.
CREATE TABLE #PREPARE (ORDNL_NUM int, Naziv_Bloka NVARCHAR(100) , ISIN NVARCHAR(100) , Iznos_U_Valuti NUMERIC(19,2), Valuta NVARCHAR(100) , Iznos_Kursiran NUMERIC (19,2), Portfelj NVARCHAR(100) , KuponskaStopa NUMERIC (19,7), DatumDospeca NVARCHAR(100) )
Na okruženju na kome je uočen problem, server ima jednu kolaciju (Serbian_Latin_100_CI_AS) a baza na kojoj se puštaju upiti drugu (SQL_Latin1_General_CP1250_CI_AS). Ukoliko kod vas nije takva situacija, da bi se reprodukovao problem, umesto prethodnog skripta za kreiranje tabele, potrebno je pustiti naredni (ukoliko vaš server ima kolaciju Serbian_Latin_100_CI_AS, potrebno je u skriptu za kreiranje tabele, staviti bilo koju drugu):
CREATE TABLE #PREPARE (ORDNL_NUM int, Naziv_Bloka NVARCHAR(100) collate Serbian_Latin_100_CI_AS, ISIN NVARCHAR(100) collate Serbian_Latin_100_CI_AS, Iznos_U_Valuti NUMERIC(19,2), Valuta NVARCHAR(100) collate Serbian_Latin_100_CI_AS , Iznos_Kursiran NUMERIC (19,2), Portfelj NVARCHAR(100) collate Serbian_Latin_100_CI_AS , KuponskaStopa NUMERIC (19,7), DatumDospeca NVARCHAR(100) collate Serbian_Latin_100_CI_AS)
Dakle, kada server ima jednu kolaciju, sve kolone tipa karakter u privremenim tabelama preuzimaju kolaciju servera, iako se upiti puštaju na bazi koja ima drugu kolaciju. To je zbog toga što tempdb baza nasleđuje kolaciju servera a samim tim i njene tabele i kolone ukoliko se eksplicitno ne navede drugi. Kolone privremene tabele izgledaju ovako:
Sledeći script radi ubacivanje podataka u privremenu tabelu.
INSERT INTO #PREPARE SELECT ORDNL_NUM=1, Naziv_Bloka ='Blok1', ISIN='HRRXXX' ,Iznos_U_Valuti=26500000.00, Valuta='HRK' , Iznos_Kursiran=26500000.00, Portfelj='HTM' , KuponskaStopa=5.2500000, DatumDospeca='15.12.2015' UNION ALL SELECT 2, 'Blok2', 'HRRYYY', 0.00, 'HRK' ,0.00, 'HTM' ,4.7500000, '08.02.2017' UNION ALL SELECT 3, 'Blok3', 'HRRZZZ', 0.00, 'HRK',0.00, 'HTM' ,6.2500000, '25.11.2017' UNION ALL SELECT 4, 'Blok4', 'HRRQQQ', 8000000.00, 'HRK', 8000000.00, 'HTM', 5.2500000, '10.07.2018'
Potrebno je tabelu #PREPARE iz strukture
Međutim, skript koji radi UNPIVOT
SELECT ORDNL_NUM,CLMN,VAL FROM (SELECT CONVERT (NVARCHAR(MAX),ORDNL_NUM) AS ORDNL_NUM ,ISNULL(CONVERT (NVARCHAR(MAX),Naziv_Bloka),'') AS Naziv_Bloka ,ISNULL(CONVERT (NVARCHAR(MAX),ISIN),'') AS ISIN ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_U_Valuti),'') AS Iznos_U_Valuti ,ISNULL(CONVERT (NVARCHAR(MAX),Valuta),'') AS Valuta ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_Kursiran),'') AS Iznos_Kursiran ,ISNULL(CONVERT (NVARCHAR(MAX),Portfelj),'') AS Portfelj ,ISNULL(CONVERT (NVARCHAR(MAX),KuponskaStopa),'') AS KuponskaStopa ,ISNULL(CONVERT (NVARCHAR(MAX),DatumDospeca),'') AS DatumDospeca FROM #PREPare ) P UNPIVOT (VAL FOR CLMN IN (Naziv_Bloka, ISIN, Iznos_U_Valuti, Valuta, Iznos_Kursiran, Portfelj, KuponskaStopa, DatumDospeca) ) AS UNPVT
javlja grešku
The type of column „Iznos_U_Valuti“ conflicts with the type of other columns specified in the UNPIVOT list.
uprkos tome što je urađena konverzija u tip NVARCHAR(MAX) svih kolona u UNPIVOT listi. Pritom, problem se javlja za prvu kolonu u listi (Iznos_u_Valuti) koja nije tipa karakter u izvornoj tabeli.
S obzirom na to da se upit pušta na bazi koja ima kolaciju SQL_Latin1_General_CP1250_CI_AS, SQL server prilikom konverzije u tip karakter, kolonama koje su u izvornoj tabeli bile drugog tipa (Iznos_U_Valuti,Iznos_Kursiran, KuponskaStopa), dodeljuje kolaciju baze, a kolone koje su u izvornoj tabeli bile tipa karakter, preuzimaju tu kolaciju i onda dolazimo u situaciju da imamo tri kolone sa kolacijom baze (SQL_Latin1_General_CP1250_CI_AS) i ostale kolone sa kolacijom servera , tj. kolacijom preuzetom iz tabele #PREPARE (Serbian_Latin_100_CI_AS). UNPIVOT ovu situaciju posmatra kao da su kolone različitog tipa, iako to zapravo nije slučaj i zbog toga javlja grešku. Zbog istih tipova nije lako shvatiti u čemu je tačno problem kada se javi, s obzirom da na prvi pogled deluje sve u redu.
a) Najjednostavnije rešenje za ovaj slučaj je svesti kolaciju svih kolona na istu:
SELECT ORDNL_NUM,CLMN,VAL FROM (SELECT CONVERT (NVARCHAR(MAX),ORDNL_NUM) AS ORDNL_NUM ,ISNULL(CONVERT (NVARCHAR(MAX),Naziv_Bloka),'') AS Naziv_Bloka ,ISNULL(CONVERT (NVARCHAR(MAX),ISIN),'') AS ISIN ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_U_Valuti),'') collate Serbian_Latin_100_CI_AS AS Iznos_U_Valuti ,ISNULL(CONVERT (NVARCHAR(MAX),Valuta),'') AS Valuta ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_Kursiran),'') collate Serbian_Latin_100_CI_AS AS Iznos_Kursiran ,ISNULL(CONVERT (NVARCHAR(MAX),Portfelj),'') AS Portfelj ,ISNULL(CONVERT (NVARCHAR(MAX),KuponskaStopa),'') collate Serbian_Latin_100_CI_AS AS KuponskaStopa ,ISNULL(CONVERT (NVARCHAR(MAX),DatumDospeca),'') AS DatumDospeca FROM #PREPare ) P UNPIVOT (VAL FOR CLMN IN (Naziv_Bloka, ISIN, Iznos_U_Valuti, Valuta, Iznos_Kursiran, Portfelj, KuponskaStopa, DatumDospeca) ) AS UNPVT
U nastavku ću dati još dva interesantna rešenja u kojima se uopšte ne mora voditi računa o kolaciji:
b) Kreiranje druge tabele sa unapred definisanim kolonama tipa karakter nad kojom se radi unpivot. U ovom slučaju, sve kolone tabele se automatski kreiraju sa istom kolacijom – kolacijom servera.
IF OBJECT_ID('TEMPDB..#PREP_1')IS NOT NULL DROP TABLE #PREP_1 CREATE TABLE #PREP_1 (ORDNL_NUM INT,Naziv_Bloka NVARCHAR(MAX), ISIN NVARCHAR(MAX), Iznos_U_Valuti NVARCHAR(MAX), Valuta NVARCHAR(MAX),Iznos_Kursiran NVARCHAR(MAX), Portfelj NVARCHAR(MAX), KuponskaStopa NVARCHAR(MAX),DatumDospeca NVARCHAR(MAX)) INSERT INTO #PREP_1 SELECT * FROM #PREPARE SELECT ORDNL_NUM,CLMN,VAL FROM (SELECT CONVERT (NVARCHAR(MAX),ORDNL_NUM) AS ORDNL_NUM ,ISNULL(CONVERT (NVARCHAR(MAX),Naziv_Bloka),'') AS Naziv_Bloka ,ISNULL(CONVERT (NVARCHAR(MAX),ISIN),'') AS ISIN ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_U_Valuti),'') AS Iznos_U_Valuti ,ISNULL(CONVERT (NVARCHAR(MAX),Valuta),'') AS Valuta ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_Kursiran),'') AS Iznos_Kursiran ,ISNULL(CONVERT (NVARCHAR(MAX),Portfelj),'') AS Portfelj ,ISNULL(CONVERT (NVARCHAR(MAX),KuponskaStopa),'') AS KuponskaStopa ,ISNULL(CONVERT (NVARCHAR(MAX),DatumDospeca),'') AS DatumDospeca FROM #PREP_1 ) P UNPIVOT (VAL FOR CLMN IN (Naziv_Bloka, ISIN, Iznos_U_Valuti, Valuta, Iznos_Kursiran, Portfelj, KuponskaStopa, DatumDospeca) ) AS UNPVT
c) Korišćenje FOR XML PATH
SELECT ORDNL_NUM,CLMN,VAL FROM ( SELECT n.value('ORDNL_NUM[1]','nvarchar(max)') ORDNL_NUM ,n.value('Naziv_Bloka[1]','nvarchar(max)') Naziv_Bloka ,n.value('ISIN[1]','nvarchar(max)') ISIN ,n.value('Iznos_U_Valuti[1]','nvarchar(max)') Iznos_U_Valuti ,n.value('Valuta[1]','nvarchar(max)') Valuta ,n.value('Iznos_Kursiran[1]','nvarchar(max)') Iznos_Kursiran ,n.value('Portfelj[1]','nvarchar(max)') Portfelj ,n.value('KuponskaStopa[1]','nvarchar(max)') KuponskaStopa ,n.value('DatumDospeca[1]','nvarchar(max)') DatumDospeca FROM ( SELECT ( SELECT CONVERT (NVARCHAR(MAX),ORDNL_NUM) AS ORDNL_NUM ,ISNULL(CONVERT (NVARCHAR(MAX),Naziv_Bloka),'') AS Naziv_Bloka ,ISNULL(CONVERT (NVARCHAR(MAX),ISIN),'') AS ISIN ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_U_Valuti),'') AS Iznos_U_Valuti ,ISNULL(CONVERT (NVARCHAR(MAX),Valuta),'') AS Valuta ,ISNULL(CONVERT (NVARCHAR(MAX),Iznos_Kursiran),'') AS Iznos_Kursiran ,ISNULL(CONVERT (NVARCHAR(MAX),Portfelj),'') AS Portfelj ,ISNULL(CONVERT (NVARCHAR(MAX),KuponskaStopa),'') AS KuponskaStopa ,ISNULL(CONVERT (NVARCHAR(MAX),DatumDospeca),'') AS DatumDospeca FROM #Prepare FOR XML PATH('a'), type ) XmlPacket ) x cross apply XmlPacket.nodes('/a') n(n) ) T UNPIVOT(VAL FOR CLMN in (Naziv_Bloka, ISIN, Iznos_U_Valuti, Valuta, Iznos_Kursiran, Portfelj, KuponskaStopa, DatumDospeca )) AS UNPVT