Archive for the ‘Szakmai élet’ Category

Task.Run Etiquette Examples: Don’t Use Task.Run in the Implementation

Friday, May 8th, 2015

Az async – await dolgokkal fel lehet szabadítani pl. as ASP.NET által is használt ThreadPool szálakat, hogy míg egy hosszú ideig tartó nem CPU hanem IO intenzív folyamat fut, addig legyen szabad szál kiszolgálni a rendes, kicsi, gyors kéréseket.

De ha úgy aszinkronítunk egy blokkoló, IO intenzív kérést, hogy becsomagoljuk Task.Run-ba, akkor adtunk a sznak egy pofont, mert pont ugyanabból a ThreadPoolból vettünk el szálat, mint amit az ASP.NET is használ (feltételezve az alap TaskSchedulert használjuk). Ráadásul még context switch is lesz a szálak között, stb.

Az igazi aszinkron cuccosok (pl. .NET szerviz hívó osztályok és adatbázis kezelő osztályok) IO completion portot használnak, amivel sok blokkoló folyamatot tudnak monitorozni kevés szálon, nem minden egyes folyamathoz egy szálat használva, mint a Task.Run-os megoldás.

Bővebben a témáról itt.

Fragmentation from using snapshot isolation

Wednesday, May 6th, 2015

Ez érdekes.

Visual Studio 2015 debugger

Wednesday, May 6th, 2015

Amellett, hogy nagyon frankón néz ki a kis chartokkal a tetején, amit kiemelnék, alul a watch ablak. Működnek a lambdák a watchban, lehet debug time linqzgatni! :)

VS2015Debugger

Új tanfolyam ötlet – Winternals

Tuesday, May 5th, 2015

Két új tanfolyamon töröm a fejem, kíváncsi vagyok, volna-e rá érdeklődés?

Az egyik egy Windows Internals jellegű tanfolyam. Annak idején le is vizsgáztam a témából, ideje lenne mindenkinek átadni a téma érdekes és hasznos részeit. A témák adottak, Russinovich-ék könyve az alap, ezt egészíteném ki developer specifikus dolgokkal, mint WinDB, SOS, mi látszik az egészből .NET-ből programozva, GC és Win mem kapcsolata, a sysinternalsos toolok mire valók, stb. Még nincs kialakult kép a fejemben a pontos tematikáról, ez még csak puhatolózás. Szóval, érdekelne valakit? Esetleg témajavaslat, mi az, ami miatt befizetnél egy ilyen tanfolyamra?

A másikról majd egy külön bejegyzésben írok.

MVC tanfolyam vége – Aurelia jön

Sunday, April 19th, 2015

A héten MVC tanfolyamot tartottam Szegeden egy cégnek, és nagyon élveztem. Az utóbbi pár évben inkább desktop alkalmazásokkal dolgoztam, ezért marha sokat tanultam rá, gyúrtam az MVC forrását, stb. de jó érzés, hogy végre újra felszívtam magam webes tudásból is. Ahol még hiány van, az a javascript, látom, hogy erre még igen sokat kell gyúrni, mivel bármilyen fura is, ez a nyelv tűnik annak, ami mindenen futni fog. A java is ezt ígérte persze sok évvel ezelőtt, de most úgy nézni ki, a js közelebb fog kerülni ehhez.

A cimbik is javasolták, illetve az internetes infók alapján is az Aurelia lesz a következő, amit alaposan meg fogok tanulni.

Mire használja az MVC a machinekeyt?

Thursday, April 16th, 2015

A source alapján nekem úgy tűnik csak az AntiForgeryTokenhez. Van még szerintetek valami más szerepe is (nem webformsról beszélünk).

Tovább nézve látom az ASP.NET Identity is használja, a Tokeneket védeni.

Resharper most már C++-hoz is

Thursday, April 9th, 2015

Kedvenc eszközöm most már megy C++-ra is. :)

Startcom SSL cert importálása IIS alá

Friday, April 3rd, 2015

A Startcom SSL-lel ingyen lehet teljesen valid certificate-eket generálni. A generálás kimenete viszont nem egy Windwos által fogyasztható cert pár lesz, hanem egy cert kulcs nélkül és külön egy kulcs fájl.

Itt leírják, hogy lehet ezt az IIS által is ehetővé alakítani, a lényeg:

openssl pkcs12 -export -in foo.crt -inkey foo.key -out foo.p12

EF őrület

Wednesday, March 18th, 2015

Ha az NHibernate tudja, hogy identity id generálási stratégia mellett, ha egy ojjektumnak nem 0 az idja, akkor az perzisztens, így nem kell újra beszúrni, az miért nem megy az EF-nek?

Próbálom használni az EF-et anélkül, hogy lemappelném az FK-kat property-ként, de látom széllel szemben megyek. Nincs cascade szabályozás sem, így ez úton nem mondhatom neki, hogy csak a gyereket szúrd be, a szülőt ne, mert az már perzisztens. Ah. Értem én, hogy nem ebbe az irányba kell haladnom, mert szembe fúj a szél, de nem szeretem azokat a modelleket, ahol az fk le van mappelve. Leaky abstraction. Az EF inkontinens, ez kell neki, a fene a hugyos józsiját.

Védje meg valaki, vagy mindjárt visszatérek NHibre, és kivágom a projektből az EF-et.

EF gyerek kollekció rendezés

Wednesday, March 18th, 2015

Néha szeretnénk nem csak egy entitás listát, hanem annak gyerekeit is rendeztetni, azaz az egy szülő alá tartozó gyerekeket order by-olni.

Egy lehetséges megoldás:

using (var c = new EdbContext())
{
    var jobs = c.LoaderJobs
        .Include("LoaderJobSteps")
        .Include("LoaderJobSteps.ExtractPathLocation")
        .Include("LoaderJobSteps.FormatFilePathLocation")
        .OrderBy(j => j.ExecutionOrder).ToList();

    //A bit complicated to be able to order LoaderJobSteps properly
    var x = jobs.Select(job => new { J = job, JS = job.LoaderJobSteps.OrderBy(js => js.ExecutionOrder).ToList() });

    return x.Select(f => f.J).ToList();
}

Szándékaim szerint a gyerekkollekciók order by-át is az adatbázissal végeztettem volna, de jól látható a kódból, hogy itt .netből történik a gyerekek (LoaderJobSteps) rendezése.
Ha kiveszem az első ToList()-et, akkor az EF helyesen áttolja a 2. order by-t is az SQL Serverre, de akkor meg nem tölti be az unokákat (LoaderJobSteps.ExtractPathLocation).
Ebben a példában nincs jelentősége hol rendezek, de ha valaki tudja, mitől nem megy ilyenkor az Include, érdekelne a megoldás.

Ha benn van az első ToList(), akkor az SQL ok, benne van minden eagerly loaded entitás, de nincs benne order by a gyerekekre, az a LINQ2Objects fogja végrehajtani. (Extent2-re nincs order by).

SELECT 
    [Project1].[Id] AS [Id], 
    [Project1].[Description] AS [Description], 
    [Project1].[ExecutionOrder] AS [ExecutionOrder], 
    [Project1].[C1] AS [C1], 
    [Project1].[Id1] AS [Id1], 
    [Project1].[StepName] AS [StepName], 
    [Project1].[ExecutionOrder1] AS [ExecutionOrder1], 
    [Project1].[ProcedureNamePrepare] AS [ProcedureNamePrepare], 
    [Project1].[ProcedureNameImport] AS [ProcedureNameImport], 
    [Project1].[ProcedureNameLoad] AS [ProcedureNameLoad], 
    [Project1].[ExtractFileName] AS [ExtractFileName], 
    [Project1].[FailOnMissingFile] AS [FailOnMissingFile], 
    [Project1].[FormatFileName] AS [FormatFileName], 
    [Project1].[Id2] AS [Id2], 
    [Project1].[FolderPath] AS [FolderPath], 
    [Project1].[Id3] AS [Id3], 
    [Project1].[FolderPath1] AS [FolderPath1], 
    [Project1].[LoaderJobId] AS [LoaderJobId]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Description] AS [Description], 
        [Extent1].[ExecutionOrder] AS [ExecutionOrder], 
        [Join2].[Id1] AS [Id1], 
        [Join2].[StepName] AS [StepName], 
        [Join2].[ExecutionOrder] AS [ExecutionOrder1], 
        [Join2].[ProcedureNamePrepare] AS [ProcedureNamePrepare], 
        [Join2].[ProcedureNameImport] AS [ProcedureNameImport], 
        [Join2].[ProcedureNameLoad] AS [ProcedureNameLoad], 
        [Join2].[ExtractFileName] AS [ExtractFileName], 
        [Join2].[FailOnMissingFile] AS [FailOnMissingFile], 
        [Join2].[FormatFileName] AS [FormatFileName], 
        [Join2].[LoaderJobId] AS [LoaderJobId], 
        [Join2].[Id2] AS [Id2], 
        [Join2].[FolderPath1] AS [FolderPath], 
        [Join2].[Id3] AS [Id3], 
        [Join2].[FolderPath2] AS [FolderPath1], 
        CASE WHEN ([Join2].[Id1] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM  [dbo].[LoaderJob] AS [Extent1]
        LEFT OUTER JOIN  (SELECT [Extent2].[Id] AS [Id1], [Extent2].[StepName] AS [StepName], [Extent2].[ExecutionOrder] AS [ExecutionOrder], [Extent2].[ProcedureNamePrepare] AS [ProcedureNamePrepare], [Extent2].[ProcedureNameImport] AS [ProcedureNameImport], [Extent2].[ProcedureNameLoad] AS [ProcedureNameLoad], [Extent2].[ExtractFileName] AS [ExtractFileName], [Extent2].[FailOnMissingFile] AS [FailOnMissingFile], [Extent2].[FormatFileName] AS [FormatFileName], [Extent2].[LoaderJobId] AS [LoaderJobId], [Extent3].[Id] AS [Id2], [Extent3].[FolderPath] AS [FolderPath1], [Extent4].[Id] AS [Id3], [Extent4].[FolderPath] AS [FolderPath2]
            FROM   [dbo].[LoaderJobStep] AS [Extent2]
            LEFT OUTER JOIN [dbo].[PathLocation] AS [Extent3] ON [Extent2].[ExtractPathLocationId] = [Extent3].[Id]
            LEFT OUTER JOIN [dbo].[PathLocation] AS [Extent4] ON [Extent2].[FormatFilePathLocationId] = [Extent4].[Id] ) AS [Join2] ON [Extent1].[Id] = [Join2].[LoaderJobId]
    )  AS [Project1]
    ORDER BY [Project1].[ExecutionOrder] ASC, [Project1].[Id] ASC, [Project1].[C1] ASC

Az első ToList() nélkül:

SELECT 
    [Extent1].[Id] AS [Id], 
    [Extent1].[Description] AS [Description], 
    [Extent1].[ExecutionOrder] AS [ExecutionOrder]
    FROM [dbo].[LoaderJob] AS [Extent1]
    ORDER BY [Extent1].[ExecutionOrder] ASC

Hm. Tippek?

Közben rájöttem, megválaszolom magamnak a kérdést. :) A végére kell rakni az include-okat:

using (var c = new EdbContext())
{
var jobs = c.LoaderJobs
.OrderBy(j => j.ExecutionOrder);

//A bit complicated to be able to order LoaderJobSteps properly
var x = jobs.Select(job => new { J = job, JS = job.LoaderJobSteps.OrderBy(js => js.ExecutionOrder) });

return x.Select(f => f.J).Include(“LoaderJobSteps”)
.Include(“LoaderJobSteps.ExtractPathLocation”)
.Include(“LoaderJobSteps.FormatFilePathLocation”).ToList();
}
[/source]

SELECT 
    [Project1].[Id] AS [Id], 
    [Project1].[Description] AS [Description], 
    [Project1].[ExecutionOrder] AS [ExecutionOrder], 
    [Project1].[C1] AS [C1], 
    [Project1].[Id1] AS [Id1], 
    [Project1].[StepName] AS [StepName], 
    [Project1].[ExecutionOrder1] AS [ExecutionOrder1], 
    [Project1].[ProcedureNamePrepare] AS [ProcedureNamePrepare], 
    [Project1].[ProcedureNameImport] AS [ProcedureNameImport], 
    [Project1].[ProcedureNameLoad] AS [ProcedureNameLoad], 
    [Project1].[ExtractFileName] AS [ExtractFileName], 
    [Project1].[FailOnMissingFile] AS [FailOnMissingFile], 
    [Project1].[FormatFileName] AS [FormatFileName], 
    [Project1].[Id2] AS [Id2], 
    [Project1].[FolderPath] AS [FolderPath], 
    [Project1].[Id3] AS [Id3], 
    [Project1].[FolderPath1] AS [FolderPath1], 
    [Project1].[LoaderJobId] AS [LoaderJobId]
    FROM ( SELECT 
        [Extent1].[Id] AS [Id], 
        [Extent1].[Description] AS [Description], 
        [Extent1].[ExecutionOrder] AS [ExecutionOrder], 
        [Join2].[Id1] AS [Id1], 
        [Join2].[StepName] AS [StepName], 
        [Join2].[ExecutionOrder] AS [ExecutionOrder1], 
        [Join2].[ProcedureNamePrepare] AS [ProcedureNamePrepare], 
        [Join2].[ProcedureNameImport] AS [ProcedureNameImport], 
        [Join2].[ProcedureNameLoad] AS [ProcedureNameLoad], 
        [Join2].[ExtractFileName] AS [ExtractFileName], 
        [Join2].[FailOnMissingFile] AS [FailOnMissingFile], 
        [Join2].[FormatFileName] AS [FormatFileName], 
        [Join2].[LoaderJobId] AS [LoaderJobId], 
        [Join2].[Id2] AS [Id2], 
        [Join2].[FolderPath1] AS [FolderPath], 
        [Join2].[Id3] AS [Id3], 
        [Join2].[FolderPath2] AS [FolderPath1], 
        CASE WHEN ([Join2].[Id1] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM  [dbo].[LoaderJob] AS [Extent1]
        LEFT OUTER JOIN  (SELECT [Extent2].[Id] AS [Id1], [Extent2].[StepName] AS [StepName], [Extent2].[ExecutionOrder] AS [ExecutionOrder], [Extent2].[ProcedureNamePrepare] AS [ProcedureNamePrepare], [Extent2].[ProcedureNameImport] AS [ProcedureNameImport], [Extent2].[ProcedureNameLoad] AS [ProcedureNameLoad], [Extent2].[ExtractFileName] AS [ExtractFileName], [Extent2].[FailOnMissingFile] AS [FailOnMissingFile], [Extent2].[FormatFileName] AS [FormatFileName], [Extent2].[LoaderJobId] AS [LoaderJobId], [Extent3].[Id] AS [Id2], [Extent3].[FolderPath] AS [FolderPath1], [Extent4].[Id] AS [Id3], [Extent4].[FolderPath] AS [FolderPath2]
            FROM   [dbo].[LoaderJobStep] AS [Extent2]
            LEFT OUTER JOIN [dbo].[PathLocation] AS [Extent3] ON [Extent2].[ExtractPathLocationId] = [Extent3].[Id]
            LEFT OUTER JOIN [dbo].[PathLocation] AS [Extent4] ON [Extent2].[FormatFilePathLocationId] = [Extent4].[Id] ) AS [Join2] ON [Extent1].[Id] = [Join2].[LoaderJobId]
    )  AS [Project1]
    ORDER BY [Project1].[ExecutionOrder] ASC, [Project1].[Id] ASC, [Project1].[C1] ASC

Hm, mégse jó, nincs benne az sql-ben a 2. order by. Szóval a kérdés nyitott, át lehet tolni adatbázis oldalra a child kollekció order by-t?

SQL teljesítményoptimalizálás – imádom

Thursday, March 12th, 2015

Az elmúlt 3 hétben egy fejlesztési projektből kicsípve 3 napot három cégnél is SQL Server teljesítményoptimalizáltam (és még vannak cégek a queue-ban, ilyen erős évindulásom még soha nem volt :). Azt kell mondjam, ez a legkedvesebb munkám mindenek felett. 1 nap alatt általában igen látványos eredményeket lehet elérni, eddig még soha nem csalódtak bennem ügyfelek.

Az áprilisom és a májusom teljesen tele van már, de ha valakinek hasonlóra van igénye, jelezze nekem, ha becsúszik valahol egy luk, egy napra lehet el tudok ugrani. Júniustól egyelőre még laza a naptáram, oda könnyebb tervezni.

A teljes üzleti transzparencia jegyében, és hogy könnyű legyen kalkulálni leírom az óradíjamat: 20000 Ft + ÁFA / óra. Azaz egy nap igen intenzív (lóg a nyelvem a nap végére tényleg, nagyon intenzív gondolkodást igényel a munka) optimalizálás 160e + ÁFA. Valaki erre biztos azt mondja, ez sok, megértem. Valaki viszont, aki már találkozott 30-40 ezres konzulenssel, annak ez olcsónak számít, tudva, hogy nem szoktam az ügyfeleknél húzni az időt, hanem nyomom a munkát nagy erővel. Egy nap alatt ki lehet végezni legalább 5-15 top lekérdezést, amitől nagyon meg szoktak könnyebbülni a szerverek. Azaz ennyi pénzből garantáltan jelentősen fel fog gyorsulni a szerver. Nyilván csodát nem tudok tenni, ha egy alkalmazás több ezer apró lekérdezéssel old megy egy feladatot, akkor a network roundtrip idején nem tudok segíteni, hiába gyorsítok fel egy lekérdezést pár msra. Vagy ha össze kell szummázni egymilliárd sort, az nehéz gyorsan megcsinálni. Azaz architekurális problémákon nem tud segíteni a db oldali hangolás.

A másik, amire fel kell készülni, hogy néha módosítani kell a hívó kódokon is, illetve időnként módosítani kell az adatbázis szerkezetét is. Én demonstrálom, mivel jár, ha ezt megteszi az ügyfél, aztán a döntés az övé, meglépi-e? Mivel ezeket nem lehet azonnal meglépni, amikor ott vagyok, gyakori, hogy az éles rendszerbe bevezetett módosításokat még egyszer át kell néznem. Ezt már legtöbbször TeamViewerrel vagy RDP-vel szoktam itthonról megtenni, mivel ez 1-2 óránál már nem visz el többet, nem éri meg ennél többet utazni miatta.

Hogy hatékony legyen a munka annyit szoktam kérni, hogy legyen jogom monitorozni az éles szervert, és legyen egy tesztszerver, ami egy restorolt adatbázist vagy adatbázisokat tartalmaz az éles rendszerről.
Ezen dokumentáltam be tudom mutatni, hogy ha az éles szerveren végrehajtják azokat a változtatásokat, amiket javaslok, akkor mennyivel lesz gyorsabb a rendszerük.

A munka része még, amikor átbeszéljük a fejlesztők vagy üzemeltetők fejében felmerült kérdéseket.

Az optimalizálási munkának egy hátránya van: mivel mindig az ügyféllel együtt végzem a munkát, és közben részletesen elmondom, mit és miért csinálok, általában már nem hívnak legközelebb, mivel kitanulják, mit kell tenni a lassulás esetén. :)

Ps. jövő héten lejár a 25%-os, 150e-es TDD tanfolyam akció, utána már csak ősszel fogok indítani tanfolyamot, mivel kilátásban van egy hosszabb projektem, így nem lesz rá időm. Aki akar, most szálljon fel a vonatra.

SQL fejtörő 11.

Wednesday, March 11th, 2015

Feladat: van egy trading accountunk, ezen hol nyerünk, hol vesztünk. Az account nem mehet 0 alá, de ha nagyobb a veszteségünk mint az account értéke, a broker behajtja rajtunk a veszteséget, vissza kell pótolni a pénzt. A feladat számla mozgásainak összegzése.

Az elvárt kimenet az SQL tartalomban látható.

USE tempdb;

IF OBJECT_ID(N'dbo.Trades', N'U') IS NOT NULL DROP TABLE dbo.Trades;

CREATE TABLE dbo.Trades
(
  tradeId INT NOT NULL PRIMARY KEY,
  amount  INT NOT NULL
);

INSERT INTO dbo.Trades(tradeId, amount)
   values (1,12),(2,-25),(3,40),(4,10),(5,-23),(6,-32),(7,14),(8,-23),(9,12),(10,-20),(11,13),(12,-90);
GO

--Elvárt kimenet:
--tradeId	amount	accountValue	moneyBack
--1	12	12	0
--2	-25	0	13
--3	40	40	0
--4	10	50	0
--5	-23	27	0
--6	-32	0	5
--7	14	14	0
--8	-23	0	9
--9	12	12	0
--10	-20	0	8
--11	13	13	0
--12	-90	0	77

A megfejtéseket szokás szerint kommentben várom, amelyeket 4 nap múlva engedek ki.

A Test Driven Development tanfolyam következő felvonása április 20-án lesz, jövő hét szerdáig még 25% kedvezménnyel lehet jelentkezni!

SQL fejtörő 10.

Monday, March 9th, 2015

Feladat: az alábbi lekérdezés nagyon lassan fut le, nálam a költsége SQL Server 2014 alatt 1145 sec. Hozzá kellene írni a selecthez valamit, amitől a költsége leesik sokkal kisebbre.

USE TempDB
GO

CREATE TABLE Table1 (A CHAR(5) NOT NULL)
CREATE TABLE Table2 (B VARCHAR(1000) NOT NULL)
GO

INSERT Table1
SELECT LEFT(AddressLine1, 5) AS A
FROM AdventureWorks2014.Person.Address

INSERT Table2
SELECT AddressLine1 AS B
FROM AdventureWorks2014.Person.Address
GO

SELECT *
FROM Table1
INNER JOIN Table2 ON
    Table2.B LIKE Table1.A + '%'

A megfejtéseket szokás szerint kommentben várom, amelyeket 4 nap múlva engedek ki.

A Test Driven Development tanfolyam következő felvonása április 20-án lesz, jövő hét szerdáig még 25% kedvezménnyel lehet jelentkezni!

SQL fejtörő 8. – megoldás

Monday, March 9th, 2015

Kérdés.

Az IO műveleteket csak SQL Server 2014-től lehet hathatósan kordában tartani, a Resource Governor képes tetszőleges számú IO művelet/sec-re bekorlátozni a zabolátlan processzt.

Korábbi verziókban értünk el sikereket azzal, hogy a MAXDOP-ot vettük kisebbre egy lekérdezéshez. Igaz, ez nem az IO-t fogja vissza, de egy szálon csak nem tud annyi IO-t kihasítani magának egy folyamat, mint többön. Ez csak a ha ló nincs jó a szamár is megoldás.

Valójában persze a legjobb megoldás megpróbálni optimalizálni a lékérdezést/módosítást, nem futtatni olyan sűrűn (cache-elés), éjszakára időzíteni ha nem sürgős, esetleg Always On-os vagy sima replikációs másodpéldányon futtatni.

Entity Framework bulk műveletek

Friday, March 6th, 2015

Amint ezt beépítik az EF-be, attól a pillanattól kezdve az EF végre rendes versenytársa lesz az NHibernate-nek.

Ráadásul, a doski azt sejteti, hogy menni fog identity oszlopokra is, az nem megy nhibbel.

Izgalmas fejlemények, meg kéne venni az egész motyót az msnek, de gyorsan.

FORCESEEK hint – szeretem

Thursday, March 5th, 2015

Mindig imádom, ha valami geekséget be tudok vetni a gyakorlatban. SQL Server 2012-ben jött be az az újdonság, hogy a FORCESEEK hintnek meg lehet adni egy index oszlopait is.

A tanfolyamok kedvéért lehet találni példákat, aminek segít egy hint, de végre találtam valamit, ami élőben is demonstrálja a dolgot.

Az alábbi lekérdezés 1 perces tőzsdei adatokban keres adathiányt. Az AllTH összeszedi minden napra a nyitvatartási időket.

;with AllTH as
(
	select 
	D, 
	dateadd(DAY, 1, D) D1,
	cast(DATEADD(HOUR, DATEPART(HOUR, OpeningTime), DATEADD(MINUTE, DATEPART(MINUTE, OpeningTime), cast(D as datetime2))) as datetime2) ExpectedOpeningTimeAsDate, 
	cast(DATEADD(HOUR, DATEPART(HOUR, ClosingTime), DATEADD(MINUTE, DATEPART(MINUTE, ClosingTime), cast(D as datetime2))) as datetime2) ExpectedClosingTimeAsDate, 
	OpeningTime, ClosingTime from TradingHoursFlat
	where TickerId = @thTickerId
	and D >= (select min(TradeTime) from Tick where TickerID = @tickerId and tradetime > dateadd(day, -180, getdate()))
	and D < dateadd(day, -1, GETDATE())
),
FilteredBars as
(
	select cast(TradeTime as datetime) TradeTime,
	t.D, t.OpeningTime, t.ClosingTime
	from AllTH t
	join Tick b
	with(forceseek (IX_Natural_Key (TickerId, TradeTime)))
	--on t.D = cast(TradeTime as date) 
	on TradeTime between t.D and t.D1
	--on TradeTime between t.D and t.D+1
	where b.TickerID = @tickerId
	--and cast(TradeTime as time) between t.OpeningTime and t.ClosingTime
	and tradetime between t.ExpectedOpeningTimeAsDate and t.ExpectedClosingTimeAsDate
),
T as
(
	select a.D, min(TradeTime) ActualOpeningTime, max(TradeTime) ActualClosingTime from FilteredBars b 
	right join AllTH a
	on a.D = cast(TradeTime as date)
	group by a.D
), U as
(
	select a.D, ExpectedOpeningTimeAsDate, ActualOpeningTime, ExpectedClosingTimeAsDate, ActualClosingTime,
	DATEDIFF(MINUTE, ExpectedOpeningTimeAsDate, ActualOpeningTime) OpeningGap,
	DATEDIFF(MINUTE, ActualClosingTime, ExpectedClosingTimeAsDate) ClosingGap
	from T
	right join AllTH a
	on a.D = cast(t.ActualOpeningTime as date)
)
,V as
(
	select * from U
	where OpeningGap > @tolerance or ClosingGap > @tolerance
	or ActualOpeningTime is null or ActualClosingTime is null
)
select 
ROW_NUMBER() over(order by D) Id,
ExpectedOpeningTimeAsDate ExpectedOpeningTime,
ActualOpeningTime,
ExpectedClosingTimeAsDate ExpectedClosingTime,
ActualClosingTime,
case when ActualOpeningTime is null then 'MissingDay' else 'MissingIntradayData' end GapKind 
from V
order by D

A hivatkozott Tick táblában ebben a pillanatban ennyi sor van: 61646572157. Nem írtam el, ez 61 milliárd sor!

Itt van az SQL 2012-es hint barátunk:
join Tick b with(forceseek (IX_Natural_Key (TickerId, TradeTime)))

Furcsa módon hint nélkül a becsült plan 60x olcsóbb, de mégis, a hinttel rákényszerített (számomra, aki ismeri az adatokat logikus) plan sokkal gyorsabban fut le.
Ha nem írom ki az oszlopokat, vagy csak foreceseek vagy forcessek + index név nem veszi rá, hogy az én tervemet valósítsa meg.

Ezzel nem azt sugallom, hogy hinteljetek mint az állat, csak azt, hogy egyes határesetekben jól jöhet, ha tudunk róla.

Egy tipp még. Vigyázni kell, hogy ne keverjük a datetime2 különböző hosszúságú változatait, mert ezeket implicit konvertálni fogja a szerver, megint elesve a seekektől.

.NET fejtörő 6. – megoldás

Friday, February 27th, 2015

Feladat.

Nincs mit írnom a megoldásról, Varga Tamás kolléga (ezek köszönet érte) annyira részletesen, alaposan kielemezte a témát a kommentek között, hogy ennél én csak kevesebbet tudnék írni, övé a szó, olvassátok.

TDD akció újra

Friday, February 27th, 2015

Tavaly már volt egy ilyen akcióm, az megtöltötte a termet (150k < 200k). Gyere, ne maradj le róla.

.NET fejtörő 5. – megoldás

Sunday, February 22nd, 2015

A fejtörő.

Molnár Csaba olyan teljesen megválaszolta a kérdést (köszönet érte), hogy a kommentjét egy az egyben bepasztázom:

C#-ban az idexer egy indexelt property, aminek alapértelmezetten Item a neve. Ezért fordítási hibát kapunk ennél a kódnál, hogy már van definíció az Item-re.
Ezt kétféle módon tudjuk elkerülni:
1. Az Item property-t átnevezzük valami másra.
2. Használjuk az indexeren az IndexerNameAttribute-ot, amivel megadhatjuk, hogy milyen néven jöjjön létre az indexer (ne Item néven).

Én személy szerint nem erőltetném a névütközést, inkább választanék más nevet.

Ami pluszként érdekes a példában (nekem nem volt magától értetődő), hogy lehet használni a params módosítót indexer esetén is.

SQL fejtörő 7. – megoldás

Sunday, February 22nd, 2015

Kérdés: meg tudom-e nézni egy hosszan futó SQL DML lekérdezésnél, hogy a mögötte futó operátorok (join, index seek, stb.) hány százaléknál járnak?

SQL Server 2014-től igen!

Nézzük a következő (buta) lekérdezést:

SET STATISTICS PROFILE ON;
GO
--Next, run your query in this session
select * from [dbo].[Nums] n1 cross join dbo.Nums n2;

Ez egy lassú, sok soros cross join, csak demó célra. A vizsgálandó lekérdezést olyan sessionben kell futtatni, amiben a STATISTICS PROFILE be van kapcsolva.

Így néz ki a végrehajtási terve:

CrossJoinPlan

A terv fontos a következőkhöz, azért raktam ide. És most jön a lényeg: sys.dm_exec_query_profiles. Ő mutatja meg, belül mi zajlik:

SELECT  
   node_id,physical_operator_name, SUM(row_count) row_count, SUM(estimate_row_count) AS estimate_row_count, 
   CAST(SUM(row_count)*100 AS float) / SUM(estimate_row_count) PercentComplete
FROM sys.dm_exec_query_profiles 
--WHERE session_id=54
GROUP BY node_id,physical_operator_name
ORDER BY node_id;

A végrehajtás kezdetén így néz ki a kimenete (ha valaki tud tippet adni, hogy lehet ezt SyntaxHighlighterrel jól formázni megköszönöm):

node_id	physical_operator_name	row_count	estimate_row_count	PercentComplete
0	Parallelism		1320422		4294967295		0.030743470422631
1	Nested Loops		1323284		4294967296		0.0308101065456867
2	Clustered Index Scan	15		100000			0.015
3	Table Spool		1323284		1410065408		0.0938455757082157
4	Clustered Index Scan	400000		400000			100

Egy perccel később:

node_id	physical_operator_name	row_count	estimate_row_count	PercentComplete
0	Parallelism	15917761	4294967295	0.370614254002137
1	Nested Loops	15920504	4294967296	0.370678119361401
2	Clustered Index Scan	161	100000	0.161
3	Table Spool	15920504	1410065408	1.12906138322911
4	Clustered Index Scan	400000	400000	100

Mit jelen ez? A terv alapján van két clustered index scanünk. Az egyik felolvasta a táblában található 400000 sort, azzal ő végzett is. Aztán a cilusos join elkezdi hozzávenni a tábla sorait, minden sorhoz 400000-et. Az egész eredménye belekerül a Table Spoolba, majd csurog ki a Parallelism operátoron keresztül (ami Gather Stream műveletet hajt végre, azaz összegyűjti a több szál által összerakott sorokat egy streambe, mivel párhuzamos a terv).

Lássuk be, marha érdekes látni, hogy áll belül egy nagyobb DML művelet, nem?