Calcolo giorni lavorativi

Segnalo una query che ho pubblicato nel mio blog in inglese che consente di realizzare una tabella calendario (per es. per la dimensione Data, ma non solo) in grado di calcolare in maniera semplice (in una query SQL) il numero di giorni lavorativi tra due date.

L'idea è di avere una tabella con le festività ricorrenti (annuali) e una con quelle che non hanno una data fissa, generando una tabella con un record per giorno e un flag "giorno lavorativo", più una colonna con il totale dei giorni lavorativi già trascorsi a partire dalla prima data in tabella. Facendo la differenza di quest'ultimo valore tra due date si ottiene il numero di giorni lavorativi dato un intervallo temporale. Molto comodo per qualunque tipo di reportistica, anche al di fuori di un data warehouse…

Paginazione in LINQ to SQL

Una delle tecniche di paginazione dei dati usata da LINQ to SQL è quella di comporre la query usando Skip e Take, dove Skip riceve come parametro il numero di righe da scartare e Take quelle visualizzate in una pagina. In pratica, se abbiamo 20 righe per pagina e vogliamo andare alla pagina 15, bisognerà saltare 300 righe [Skip(300)] e prendere le successive 20 [Take(20)].

Per esempio:

            var query =

                (from c in db.GetTable<Customer>()

                 orderby c.CustomerID

                 select c).Skip(30).Take(10);

Su SQL Server 2005 la query precedente genera codice T-SQL che sfrutta il ROWNUMBER(). Se invece inviamo la stessa query LINQ a SQL Server 2000 o SQL Server CE, questa è la query che otteniamo:

SELECT TOP 10

        [t0].[CustomerID],

        [t0].[CompanyName],

        [t0].[ContactName],

        [t0].[ContactTitle],

        [t0].[Address],

        [t0].[City],

        [t0].[Region],

        [t0].[PostalCode],

        [t0].[Country],

        [t0].[Phone],

        [t0].[Fax]

FROM    [dbo].[Customers] AS [t0]

WHERE   NOT ( EXISTS ( SELECT   NULL AS [EMPTY]

                       FROM     ( SELECT TOP 30

                                            [t1].[CustomerID]

                                  FROM      [dbo].[Customers] AS [t1]

                                  ORDER BY  [t1].[CustomerID]

                                ) AS [t2]

                       WHERE    [t0].[CustomerID] = [t2].[CustomerID] ) )

ORDER BY [t0].[CustomerID]

Personalmente avrei usato un'altra sintassi, ma come vedremo tra poco non è così ovvio cosa sia meglio fare… Ecco cosa avrei scritto io:

SELECT  *

FROM    ( SELECT TOP 10

                    [t0].[CustomerID],

                    [t0].[CompanyName],

                    [t0].[ContactName],

                    [t0].[ContactTitle],

                    [t0].[Address],

                    [t0].[City],

                    [t0].[Region],

                    [t0].[PostalCode],

                    [t0].[Country],

                    [t0].[Phone],

                    [t0].[Fax]

          FROM      ( SELECT TOP 40

                                [t1].[CustomerID],

                                [t1].[CompanyName],

                                [t1].[ContactName],

                                [t1].[ContactTitle],

                                [t1].[Address],

                                [t1].[City],

                                [t1].[Region],

                                [t1].[PostalCode],

                                [t1].[Country],

                                [t1].[Phone],

                                [t1].[Fax]

                      FROM      [dbo].[Customers] AS [t1]

                      ORDER BY  [t1].[CustomerID]

                    ) t0

          ORDER BY  [t0].[CustomerID] DESC

        ) x

ORDER BY CustomerID

— Context: SqlProvider(Sql2000) Model: AttributedMetaModel Build: 3.5.21022.8

Con il mio approccio, su Northwind.Customers vado più veloce. In realtà però io vinco solo perché la tabella è piccola. Anche provando con una tabella più grande, Order Details, e andando su una delle ultime pagine, ottengo risultati migliori della query generata da LINQ. In realtà, però, si possono fare alcune considerazioni: la query LINQ to SQL potrebbe essere più efficiente se, oltre ad avere tante righe, avessimo anche tante colonne. Il che non è strano, se pensiamo che stiamo lavorando sul risultato di una query che potrebbe contenere la join di più tabelle.

Ho fatto una prova con una tabella di 130.000 righe e soprattutto con moltissime colonne. Questo il risultato:

(10 row(s) affected)

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Righe Ordini'. Scan count 2, logical reads 28813, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 

(1 row(s) affected)

 

SQL Server Execution Times:

   CPU time = 2485 ms,  elapsed time = 3088 ms.

 

(10 row(s) affected)

Table 'Righe Ordini'. Scan count 1, logical reads 14407, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

 

(1 row(s) affected)

 

SQL Server Execution Times:

   CPU time = 5877 ms,  elapsed time = 3259 ms. 

Questa volta vince LINQ to SQL, soprattutto su machine mono-processore. Il parallelismo nella seconda query (la mia) comporta un più alto consumo di CPU (quasi doppio!), per ottenere un tempo di risposta comunque peggiore, anche se di poco, su un PC dual core.

CONSIDERAZIONI FINALI

Non me la sento di dire che la query fatta da LINQ to SQL sia non ottimale, lo è con piccoli volumi, ma su grandi volumi diventa più efficiente. Senza contare che, per come è costruita, probabilmente consuma anche meno memoria temporanea per i riordinamenti (ma questa è un’analisi che non ho fatto).

Visual Studio 2008 RTM – prime impressioni

Ho finalmente installato Visual Studio 2008 (già disponibile agli abbonati MSDN) su una macchina fisica. Finora ho sempre usato le versioni CTP/Beta in macchine virtuali, quindi alcune sensazioni sulle prestazioni potevano essere fuorvianti.

Ecco le prime impressioni dopo un giorno di utilizzo

  • Lentezza del setup: non è migliorata, o meglio, non è veloce. Credo 1 ora e mezza o quasi 2 (a qualche domanda ho risposto un po' in ritardo). Mezz'ora solo per installare il .NET Framework 3.5.
  • Avviamento in partenza: sembra quasi dimezzato, poi chiaramente dipende dagli AddIn e siccome per ora ne ho di meno, può anche essere che non conta
  • Prestazioni delle nuove librerie: è presto per dirlo, ci sono delle cose di LINQ to SQL in particolare che voglio approfondire perché a prima vista ci sono delle latenze che non mi piacciono sull'esecuzione di query nel db.
  • Impressione complessiva: non è più lento di 2005, nonostante abbia molta roba in più. L'apertura di un file sorgente da disco a volte mette qualche frazione di secondo in più di quanto sono abituato.
  • Una delle cose più importanti: ho installato Visual Studio 2008 side-by-side con Visual Studio 2005 (e con tutti i tool di disegno per Analysis Services e Integration Services) e al momento non ho notato alcuna incompatibilità… aspetto ancora una settimana per dirlo, ma sembra che sia andata liscia…

Qualcuno ha altri feebdack (soprattutto quelli negativi)?

F# diventa un prodotto

Poche settimane fa ho citato un post su una caratteristica di F# che è interessante anche se non si è competenti su tale linguaggio (si parlava di parallelismo). Pochi giorni dopo è arrivata la notizia che F# diventerà un prodotto integrato in Visual Studio. Questo significa, in soldoni, poter usare tale linguaggio per costruire moduli di un'applicazione mantenendo il pieno supporto da parte di Microsoft.

La direzione che prenderanno i linguaggi dalla versione successiva a Orcas (dopo il 2010 insomma…) sembra quindi essere la seguente: C# come linguaggio object-oriented,VB come linguaggio dinamico, F# come linguaggio funzionale, C++/CLI come linguaggio per programmazione in codice "nativo" e per risolvere problemi di interoperabilità.

Avremo un solo framework ma tanti linguaggi. Quanti sapranno usare al meglio tutti questi strumenti? So già la risposta di molti… ma vedendo le cose da un punto di vista più "generazionale", il punto è che non sarà importante sufficiente conoscere un linguaggio, diventerà cruciale saper scegliere quello migliore per il problema da affrontare. Fermo restando che il Framework resta lo stesso.

Certezze per il futuro ce n'è poche, l'unica che ho io è che restare arroccati a difendere il fortino dove ci si è rifugiati sia la cosa più sbagliata da fare. Meglio, molto meglio, "perdere" un po' di tempo per guardarsi in giro e capire come cambia (o potrebbe cambiare) lo scenario. Il fine di tutto è la produttività e l'aumento del parallelismo richiederà cambiamenti anche nei linguaggi di programmazione per diventare economicamente accessibile su larga scala..,