Con SQL Server 2000, vi siete mai imbattuti nel messaggio di errore numero 104 ?
Server: Msg 104, Level 15, State 1, Line 1
ORDER BY items must appear in the select list if the statement contains a UNION operator.
Questo messaggio viene restituito quando si utilizza l’operatore UNION per combinare i risultati di due o più istruzioni SELECT nelle cui dichiarazioni è presente una clausola ORDER BY e le colonne citate nell’ordinamento non sono parte (anche) della SELECT.
L’errore è riproducibile con un semplice esempio che consiste di una tabella clienti dbo.customer e della relativa tabella ordini (testate) dbo.orderheader.
Con i seguenti comandi T-SQL vengono create le strutture di esempio sul tempdb:
use [tempdb];
go
— create table dbo.customer
create table dbo.customer
(
customerid int identity(1, 1) not null,
modifieddate datetime default (getdate() + datepart(ss, getdate())) not null
primary key(customerid)
);
go
— create table dbo.orderheader
create table dbo.orderheader
(orderid int identity(1, 1) not null,
orderdate datetime default getdate() not null,
ordernumber as (isnull(N’SO’ + convert([nvarchar](23), [orderid], 0), N’*** error ***’)),
customerid int default 1 not null,
shipname varchar(20) default ‘name’,
shipaddress varchar(40) default ‘address’,
shipvia varchar(40) default ‘via’,
shipcity varchar(20) default ‘city’,
shipregion varchar(20) default ‘region’,
shippostalcode varchar(20) default ‘postal code’,
shipcountry varchar(20) default ‘country’,
deliverydate datetime default (getdate() + datepart(ss, getdate())) not null,
deliverynote varchar(40)
primary key(orderid)
);
go
alter table dbo.orderheader
add constraint fk_customer_customerid foreign key (customerid)
references dbo.customer(customerid);
go
Popoliamo le tabelle con alcuni dati di prova:
/* Popoliamo le tabelle con dati di prova */
declare
@i as int,
@customerid int
set @i = 0
while (@i < 1000)
begin
set nocount on
— insert dbo.customer
insert into dbo.customer default values;
select
@customerid = (select scope_identity());
— dbo.orderheader
insert into dbo.orderheader
(
orderdate,
customerid,
shipname,
shipaddress,
shipvia,
shipcity,
shipregion,
shippostalcode,
shipcountry,
deliverydate,
deliverynote
)
values
(
default,
@customerid,
default,
default,
default,
default,
default,
default,
default,
default,
default
);
set @i = (@i + 1)
set nocount on
end
Per riprodurre l’errore è sufficiente scrivere un’istruzione di SELECT in cui:
- E’ presente l’operatore UNION
- Sono presenti due o più comandi di SELECT
- E’ presente una clausola ORDER BY e le colonne citate nell’ordinamento non sono parte (anche) della SELECT
Il seguente comando verifica i tre requisiti:
select
h.orderid
from
dbo.orderheader h
where
h.customerid = (select top 1
c.customerid
from
dbo.customer c
order by
c.modifieddate desc)
union all
select
top 10 percent
h1.orderid
from
dbo.orderheader h1
Se eseguiamo la SELECT con SQL Server 2000 riceviamo l’errore che ci aspettiamo:
Server: Msg 104, Level 15, State 1, Line 1
ORDER BY items must appear in the select list if the statement contains a UNION operator.
Proviamo ora lo stesso esempio utilizzando però un’istanza SQL Server 2005, il precedente comando di SELECT viene processato correttamente senza errori. Oltre al dataset restituito osserviamo, nella figura seguente, il piano di esecuzione generato per la query:
Figura 1 – Piano di esecuzione con SQL Server 2005
Osserviamo i dettagli dei task Clustered Index Scan e Sort nelle figure seguenti:
Figura 2 – Task: Clustered Index Scan
Con SQL Server 2005 il task di estrazione dati per la tabella dbo.customer (Clustered Index Scan illustrato in figura 2) fornisce implicitamente in output per il successivo Sort task (illustrato in figura 3) sia la colonna customerid, estratta nella SELECT, che la colonna modifieddate, estratta soltanto nella clausola ORDER BY.
Figura 3 – Task: Sort
Con SQL Server 2005 il Sort task esegue l’ordinamento per la colonna modifieddate specificata nella clausola ORDER BY anche se tale colonna non compare nella SELECT, anche in presenza dell’operatore UNION.
Per ovviare al messaggio di errore, con SQL Server 2000 è necessario riscrivere la SELECT in questo modo:
select
h.orderid
from
dbo.orderheader h
where
h.customerid = (select
t.customerid
from
(select top 1
c.customerid,
c.modifieddate
from
dbo.customer c
order by
c.modifieddate desc) as t)
union all
select
top 10 percent
h1.orderid
from
dbo.orderheader h1
Pulizia del tempdb
use [tempdb];
go
if (object_id(‘orderheader’, ‘U’) is not null)
drop table dbo.orderheader;
go
if (object_id(‘customer’, ‘U’) is not null)
drop table dbo.customer;