febbraio 2009 - Posts

Autoincrementanti e numeri negativi. Non piacciono proprio?
26 febbraio 09 10.37 | abenedetti | 3 comment(s)

Spesso ci si dimentica che, nella gestione dei campi contatore, abbiamo a disposizione anche i numeri minori di zero.

Spesso, se ci si trova nell'eventualità di dover memorizzare 3 miliardi e mezzo di righe, si preferisce utilizzare, sulla colonna contatore autoincrementante, un tipo BIGINT.

Perchè?
Perchè il tipo INT arriva, all’estremo positivo, fino a 2.147.483.647

Bene. E' la parte negativa?

Perchè siamo abituati a scrivere qualcosa come questo?

create table test
(
idRecord int primary key identity (1,1),
valore varchar(1)
)
go 

e non qualcosa come questo?

create table test
(
idRecord int primary key identity (-2147483648,1),
valore varchar(1)
)
go 

La domanda che mi faccio spesso è: perchè i numeri negativi non piacciono?
Non sono forse cifre come tutte le altre?

Si tratta solo di leggibilità?
Si ha paura di dire "guarda la riga con ID = -1.235.887.112 ?"

Il punto è sempre e solo questo: I/O.

Dobbiamo risparimiare storage. Non perchè costi lo spazio di per sè (non si parla di euro per hard disk), ma perchè costa tantissimo utilizzarlo (leggerlo / scriverlo).

Se utilizzo un BIGINT significa utilizzare 4 byte in più.
Ovvero, con tre miliardi di righe significa utilizzare 12 miliardi di byte, ovvero più di 11 Gb!!!

E' proprio necessario?
Vogliamo vedere cosa succede "solo" con 50.000 righe (e non miliardi)?

set nocount on
set statistics io off
use tempdb
go 

create table testPos
(
idRecord bigint primary key identity (1,1),
valore char(5)
)
go 

create table testNeg
(
idRecord int primary key identity (-2147483648,1),
valore char(5)
)
go 

declare @i int = 50000
while (@i > 0)
begin
   
insert testPos values ('a')
   
insert testNeg values ('a')
   
set @i -= 1
end 

set statistics io on 

select COUNT(1) from testPos
select COUNT(1) from testNeg 

--> testPos: logical reads 138
--> testNeg: logical reads 114
--> 24 letture di differenza! 

set statistics io off 

;with cte as
(
   
select * from sys.dm_db_index_physical_stats(db_id(), 
       
object_id('dbo.testPos'), NULL, NULL, 'DETAILED') where index_level = 0
   
union all
   
select * from sys.dm_db_index_physical_stats(db_id(), 
       
object_id('dbo.testNeg'), NULL, NULL, 'DETAILED') where    index_level = 0
)
select
   
object_name(object_id), index_depth, page_count, avg_record_size_in_bytes 
from
   
cte
go 

drop table testPos
drop table testNeg
go

image 

Con 50.000 righe, utilizzando un BIGINT, ho 24 data pages in più, ovvero 24 * 8 Kb = 192 Kb buttati.

[SQL 2008] Row Constructor ed il limite di 1000 righe
25 febbraio 09 06.13 | abenedetti | with no comments

Con SQL 2008 viene introdotto il concetto di “costruttore di riga”, ovvero la possibilità di inserire, con una singola istruzione, diverse righe.

Ad esempio:

insert tabella values
('a1', 'b1', 'c1'),
('a2', 'b2', 'c2'),
('a3', 'b3', 'c3')
 
Attenzione però che, come ben specificato nella documentazione:

The maximum number of rows that can be inserted in a single INSERT statement is 1000.

Questo significa che se siamo nella necessità di fare un inserimento massivo gli strumenti che abbiamo a disposizione restano la BCP o la BULK INSERT.

Oppure dobbiamo necessariamente spezzare in blocchi i valori che vogliamo inserire.

Conosci la clausola OUTPUT?
20 febbraio 09 06.42 | abenedetti | with no comments

A partire da SQL Server 2005, è possibile utilizzare la clausola OUTPUT per poter leggere informazioni sulle righe interessate da operazioni DML (insert, update, delete).

Un esempio di utilizzo potrebbe essere questo…

Supponiamo di avere una tabella, con una colonna autoincrementante, che subisce un inserimento simultaneo di più righe.

Qualcosa come:

set nocount on 
use tempdb 
go 

create table importDati 
( 
idRecord int primary key identity(1,1), 
valore int 
) 
go 

insert importDati 
select 11 union all 
select 22 union all 
select 33 union all 
select 44 union all 
select 55 

La mia necessità è quella di conoscere gli “idRecord” generati automaticamente per poterli utilizzare fare altro.

Con la clasuola OUTPUT potrei fare semplicemente qualcosa del genere:

create table #t (idRecord int) 

insert importDati 
output inserted.idRecord into #t 
select 11 union all 
select 22 union all 
select 33 union all 
select 44 union all 
select 55 

select * from #t

 

Qualcuno ha usato i trigger per risolvere questo tipo di problematiche?

Oppure: qualcuno ha usato i cursori? :-)

Filed under: , , ,
Report Viewer, report locali, render diretto
19 febbraio 09 12.06 | abenedetti | with no comments

Il controllo report viewer, come ho già scritto qui, consente la visualizzazione di report, in modalità locale, prelevando dati da un proprio oggetto.

La domanda potrebbe nascere spontanea: visto che il controllo mi permette il salvataggio in formato excel e/o pdf, ho modo di ottenere direttamente il report in uno di questi formati senza passare dal visualizzatore (ovvero senza che sia l’utente a dover scegliere il save)?

Si, tramite l’oggetto LocalReport ed il suo metodo Render.

Ovvero scrivendo qualcosa come:

string reportPath = @"C:\cartellaReport\mioReport.rdlc"; var report = new LocalReport(); report.ReportPath = reportPath; Warning[] warnings; string[] streamids; string mimeType; string encoding; string filenameExtension; byte[] bytes = report.Render("PDF", null, out mimeType, out encoding, out filenameExtension, out streamids, out warnings); salvaReport(bytes);

 

Dove il metodo “salvaReport” va, semplicemente, a scrivere lo stream di byte su file:

private void salvaReport(byte[] reportStream) { var stream = File.Create(@"c:\temp\____report.pdf"); stream.Write(reportStream, 0, reportStream.Length); stream.Close(); }

This Blog

Syndication