PIT Point In Time

PIT Point In Time


We often have descriptive data in our Power BI models. Data may vary over time and have validity dates. It can become a real headache when creating a data model.

What kind of data?

  • A car could change his production plant through time.
  • A car can have different engines over time
  • A customer can have through time:
    • One or more loyalty cards
    • One or more addresses

What data types?

  • Multiple data
  • With a notion of time. (The information is valid from one date to another and potentially followed or overlapped by another)

PIT – Point In Time – Datavault

Point In Time is a concept widely used when creating a data warehouse in a Datavault flavor. Performing a snapshot and historizing the data makes restitution much easier.

(I will not discuss the pros or cons of Datavault, but I have my preferences ^^)

If you want to know more, I invite you to read this article. (Written by Dan Linstedt)



What do we expect?

It is exciting for our users to navigate through the data by applying a time filter (CFR Slicer Date)

How: By creating a DAX measure. (#PIT Shoes)

The example above shows dynamically Pauline’s shoes.


Our data model

The following example is quite simple (The Power BI file is available for download at the end of this article)

Many of you know it, I am passionate about Power BI, running, but many ignore my “passion” with running shoes. (Nobody is perfect !)

The example refers to a list of runners who have one or more pairs of shoes over time.

The Runners table:

The Shoes table

Model specificities

  • RunnerId (The Runner Table Key) is available for all shoes.
  • For performance reasons, the date stamps are duplicated and converted into numeric.
    • ValidFrom – ValidFromInt
    • ValidTo – ValidToInt

More information

This is denormalization of a star schema generally composed of a Bridge and a dimension table.

In a Datawarehouse (Kimball), we should have a Bridge: BridgeRunnerShoes and a dimension: DimShoes


It helps our users to have better comprehension. (Simplicity)
The Vertipaq engine will compress the redundant information
To avoids an extra join / relationship.

You want to know more about Bridges:



A more complete model could look like this one


Graphically analyze the different interactions.

  1. Our user pick a date via the PIT Date slicer
  2. The shoe table changes dynamically and display the one owned on that date.

NB The blue bar drawn on the timeline has been added manually in MS Paint. (If you know a visual component that can display it, do not hesitate to contact me)

Filter: 01/02/2019 | Pauline

Filter: 01/03/2019 | Pauline

Filter: 01/05/2019 | Pauline

Filter: 10/10/2019 | Pauline

Filter: 01/12/2019 | Pauline


Create a Calendar table

We have to create a Date table, which will not be associated with our model. (Called here PIT Calendar)

To quickly create a Calendar table, I suggest you write an M Query in the Query Builder. The following articles will give you the approach.

Generating A Date Dimension Table In Power Query


Create a DAX Measure

This measure Filter the Shoes table by the selected date.

#PIT Shoes =
VAR CurrentDateInt = MAX(PIT_Calendar[DateInt])
Shoes[ValidFromInt] <= CurrentDateInt && Shoes[ValidToInt] >= CurrentDateInt
; ‘Runners’

Understand this DAX expression

VAR CurrentDateInt = MAX(PIT_Calendar[DateInt])

VAR CurrentDateInt =

A variant of the previous expression can test if our user specified a PIT Calendar date and apply the current date if not.


  • The user wants to have the situation of the day (without having to modify the PIT slicer at each opening of the report)
  • The user forgot to create his report with the PIT slicer. Rather than propose the situation in the future, we apply the current one.
Shoes[ValidFromInt] <= CurrentDateInt && Shoes[ValidToInt] >= CurrentDateInt
The variable contains the date selected by the user and is dynamically applied to our dataset. (Shoes)


Now, you know how to make your data alive.

Feel free to comment if you have more ideas, improvements, tips, or comments.

Download the Power BI example file here:



Management Studio – Faster with multiple select

Management Studio – Faster with multiple select

In SQL Server Management Studio (SSMS for close friends) but also in a multitude of text editors (such as Notepad++ for example), you can make multiple selections.

I thought it was something everyone knew, but I realize that I often look like a magician every time I do it.

I’m delighted to pretend to be Harry Potter, but I think it’s time for this little game to stop!

Look by yourself how simple it is!


You should press the keys [ALT] + [Shift] simultaneously and move your cursor [Up] and / or [Down] to select your text.

One useful case

Sometimes we have to surround our text with single quotes. (for example: to add multiple codes to an IN clause in a test query)

No need to add them one by one and make sure you do not have space at the end.
Here is the method:

Like mentioned above, this tip is not an exclusivity in SSMS, you can also do the same in many different text editor.

SSIS – Create Environment from Packages variables

SSIS – Create Environment from Packages variables

I created this script to automate some “not funny” tasks with our SSIS Catalog. If you have several SSIS projects configured in project mode (with a project.params file), when deploy them on your servers you unfortunately have to manually create the different environments.

After deploying SSIS packages, you can run the following query and use the generated SQL code.

the generated SQL script:

  • Create the different environment (Based on the name of the projects)
  • Create variables with default values (value available in packages)
  • Assigning Environments to Projects
  • Assign the environment variables to the project variables.

The following code is not clean, but it does the work! 🙂



    — Configuration
    DECLARE @FolderToExport VARCHAR(50) = ‘BI’

    @folder_id INT
    ,@name VARCHAR(150)
    ,@project_id INT
    ,@sql VARCHAR(MAX) = ”
    ,@sqlTemp VARCHAR(MAX) = ”
    ,@cr CHAR(1) = CHAR(10)
    ,@tab CHAR(4) = SPACE(4)
    ,@ProjectName VARCHAR(50)
    ,@VariableName VARCHAR(150)
    ,@Value VARCHAR(600)

    SET @sql = ”;

    SET @sql += ‘DECLARE @ReturnCode INT=0, @folder_id bigint’ + @cr + @cr;
    SET @sql += ‘– ———————————————‘ + @cr;
    SET @sql += ‘–‘+ @tab + ‘Variable declarations, make any changes here’ + @cr;
    SET @sql += ‘– ———————————————‘ + @cr;
    SET @sql += ‘DECLARE ‘+ @cr;
    SET @sql += @tab + @tab + ‘ @folder sysname’ + @cr;
    SET @sql += @tab + @tab + ‘,@env sysname’ + @cr;
    SET @sql += @tab + @tab + ‘,@project sysname;’ + @cr;

    — Get Folder ID
    SELECT @folder_id = folder_id FROM [internal].[folders] WHERE name = @FolderToExport

    SET @SQL = ”;

    — Export Current package Configuration
    DECLARE db_cursor_variable CURSOR FOR
    CONVERT(NVARCHAR(50), projects.name ) AS ProjectName
    ,CONVERT(NVARCHAR(150), projects.name +’__’+ parameter_name) AS VariableName
    ,CONVERT(NVARCHAR(600), ISNULL(design_default_value, ”) ) AS Value
    FROM internal.object_parameters main
    INNER JOIN internal.projects ON main.project_id = projects.project_id
    WHERE project_version_lsn = (SELECT MAX(project_version_lsn) FROM [SSISDB].[internal].[object_parameters] s WHERE s.project_id = main.project_id)
    AND parameter_name NOT LIKE ‘CM.%’
    AND projects.folder_id = @folder_id
    ORDER BY CONVERT(NVARCHAR(50), projects.name )
    ,CONVERT(NVARCHAR(150), projects.name +’__’+ parameter_name)
    ,CONVERT(NVARCHAR(600), ISNULL(design_default_value, ”))

    OPEN db_cursor_variable
    FETCH NEXT FROM db_cursor_variable

    PRINT ‘DECLARE @’+ @VariableName +’ NVARCHAR(600) = N”’+ @Value +”’;’;

    FETCH NEXT FROM db_cursor_variable

    CLOSE db_cursor_variable
    DEALLOCATE db_cursor_variable

    SET @SQL += ‘SET @folder = ”’+ @FolderToExport +””+ @cr + @cr+ @cr;

    — Begin transaction
    SET @sql += ‘;’ + @cr + ‘/* Starting the transaction */’ + @cr;
    SET @sql += ‘BEGIN TRANSACTION’ + @cr + @cr + @cr;

    —- Test if folder exist -> If not create it
    –SET @sql += @tab + ‘IF NOT EXISTS (SELECT 1 FROM [SSISDB].[catalog].[folders] WHERE name = @folder)’ + @cr;
    –SET @sql += @tab + ‘BEGIN’ + @cr;
    –SET @sql += @tab + @tab + ‘RAISERROR(”Creating folder: %s …”, 10, 1, @folder) WITH NOWAIT;’ + @cr;
    –SET @sql += @tab + @tab + ‘EXEC @ReturnCode = [SSISDB].[catalog].[create_folder] @folder_name=@folder, @folder_id=@folder_id OUTPUT’ + @cr;
    –SET @sql += @tab + @tab + ‘IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;’ + @cr;
    –SET @sql += @tab + ‘END’ + @cr + @cr;

    — Create environment + variables
    DECLARE db_cursor CURSOR FOR
    SELECT [project_id], [name] FROM [internal].[projects] WHERE folder_id = @folder_id

    OPEN db_cursor
    FETCH NEXT FROM db_cursor
    INTO @project_id, @name


    SET @sql += @tab + ‘SET @env = ”’+ @name +”” + @cr;

    SET @sql += @tab + ‘IF NOT EXISTS (SELECT 1 FROM [SSISDB].[catalog].[environments] WHERE name = @env)’ + @cr;
    SET @sql += @tab + ‘BEGIN’ + @cr;

    SET @sql += @tab + @tab +’RAISERROR(”Creating Environment: %s”, 10, 1, @env) WITH NOWAIT;’ + @cr;
    SET @sql += @tab + @tab +’EXEC @ReturnCode = [SSISDB].[catalog].[create_environment] @folder_name=@folder, @environment_name=@env’ + @cr;
    SET @sql += @tab + @tab +’IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;’ + @cr + @cr;

    SET @sql += @tab + ‘END’ + @cr;

    print @sql;
    SET @sql = ”;

    — ———————————————
    — Generate the variable creation
    — ———————————————
    SET @sql += ‘ — ProjectId => ‘+ CONVERT(NVARCHAR(50), @project_id) + @cr;
    SELECT DISTINCT [cmd] = @tab + ‘RAISERROR(” Creating variable: ‘ + parameter_name + ‘ …”, 10, 1) WITH NOWAIT;’ + @cr
    + @tab + ‘EXEC @ReturnCode = [SSISDB].[catalog].[create_environment_variable]’ + @cr
    + @tab + @tab + ‘ @variable_name=N”’ + parameter_name + ”” + @cr
    + @tab + @tab + ‘, @sensitive=’ + CONVERT(varchar(2), sensitive) + @cr
    + @tab + @tab + ‘, @description=N”’ + [description] + ”” + @cr
    + @tab + @tab + ‘, @environment_name=@env’ + @cr
    + @tab + @tab + ‘, @folder_name=@folder’ + @cr
    + @tab + @tab + ‘, @value=@’ + @name +’__’+ parameter_name + @cr
    + @tab + @tab + ‘, @data_type=N”’ + parameter_data_type + ”” + @cr
    + @tab + ‘IF (@@ERROR <> 0 OR @ReturnCode <> 0) GOTO QuitWithRollback;’ + @cr
    , [name] = parameter_name
    INTO #cmd
    FROM [SSISDB].[internal].[object_parameters]
    WHERE project_id = @project_id
    AND project_version_lsn = (SELECT MAX(project_version_lsn) FROM [SSISDB].[internal].[object_parameters] WHERE project_id = @project_id)
    AND parameter_name NOT LIKE ‘CM.%’

    SELECT TOP 1 @sql = cmd, @name = name FROM #cmd ORDER BY name;
    PRINT @sql;

    DELETE FROM #cmd WHERE name = @name;

    DROP TABLE #cmd;
    SET @sql = ”;

    FETCH NEXT FROM db_cursor INTO @project_id, @name

    CLOSE db_cursor
    DEALLOCATE db_cursor

    — Set Environment on each Project
    — + Set Environment variables on each Project Variables
    DECLARE db_cursor CURSOR FOR
    SELECT [project_id], [name] FROM [internal].[projects] WHERE folder_id = @folder_id

    OPEN db_cursor
    FETCH NEXT FROM db_cursor
    INTO @project_id, @name

    SET @sql = ‘DECLARE @reference_id AS BIGINT’+ @cr;
    PRINT @sql;

    SET @sql = @tab + ‘SET @project = ”’+ @name +”’;’+ @cr;
    SET @sql += @tab + ‘SET @env = ”’+ @name +”” + @cr;
    SET @sql += ‘RAISERROR(”Configure Project: %s …”, 10, 1, @project) WITH NOWAIT;’ + @cr + @cr

    SET @sql += ‘– —————————————— ‘+ @cr
    SET @sql += ‘– Set Environment to Project ‘ + @cr
    SET @sql += ‘– —————————————— ‘+ @cr
    SET @sql += ‘RAISERROR(” Set Environment (%s) as Reference …”, 10, 1, @env) WITH NOWAIT;’ + @cr
    SET @sql += ‘EXEC SSISDB.catalog.create_environment_reference’+ @cr
    SET @sql += @tab + @tab + ‘@folder_name = @folder, — Folder (SSIDB >> “FolderName”)’+ @cr
    SET @sql += @tab + @tab + ‘@environment_name = @env, — Environment Name ‘+ @cr
    SET @sql += @tab + @tab + ‘@project_name = @project, — Project Name ‘+ @cr
    SET @sql += @tab + @tab + ‘@reference_type = ”R”,’+ @cr
    SET @sql += @tab + @tab + ‘@reference_id = @reference_id OUTPUT’+ @cr


    — ——————————————
    — Set Environment Variable to Project Variables
    — ——————————————

    IF OBJECT_ID(‘tempdb..#cmd2’) IS NOT NULL DROP TABLE #cmd2

    SET @sql = ‘– —————————————— ‘+ @cr
    SET @sql += ‘– Set Environment Variable to Project Variables ‘+ @cr
    SET @sql += ‘– —————————————— ‘+ @cr
    SET @sql = @tab + ‘SET @project = ”’+ @name +”’;’+ @cr;
    SET @sql += @tab + ‘SET @env = ”’+ @name +”” + @cr;
    PRINT @sql

    [cmd] =
    ‘RAISERROR(” Set Project variable (‘+ parameter_name +’) …”, 10, 1) WITH NOWAIT;’ + @cr
    + ‘EXECUTE [SSISDB].[catalog].set_object_parameter_value ‘+ @cr
    + @tab + @tab + ‘@object_type = ‘+ CONVERT(NVARCHAR(10), object_type) +’,’ + @cr
    + @tab + @tab + ‘@folder_name = @folder, — Folder (SSIDB >> “FolderName”)’+ @cr
    + @tab + @tab + ‘@project_name = @project, — Environment Name’+ @cr
    + @tab + @tab + ‘@parameter_name = N”’+ parameter_name +”’, — Project Variable Name’+ @cr
    + @tab + @tab + ‘@parameter_value = N”’+ parameter_name +”’, — Environment Variable Name’+ @cr
    + @tab + @tab + ‘@object_name = N”’+ object_name +”’,’+ @cr
    + @tab + @tab + ‘@value_type = ”R”’+ @cr
    ,[name] = parameter_name
    INTO #cmd2
    FROM [SSISDB].[internal].[object_parameters]
    WHERE project_id = @project_id
    AND project_version_lsn = (SELECT MAX(project_version_lsn) FROM [SSISDB].[internal].[object_parameters] WHERE project_id = @project_id)
    AND parameter_name NOT LIKE ‘CM.%’

    /*Print out the variable creation procs */
    SELECT TOP 1 @sql = cmd, @name = name FROM #cmd2 ORDER BY name;
    PRINT @sql;

    DELETE FROM #cmd2 WHERE name = @name;

    FETCH NEXT FROM db_cursor INTO @project_id, @name

    CLOSE db_cursor
    DEALLOCATE db_cursor

    — END!
    SET @sql = ”;

    /* finsih the transaction handling */
    SET @sql += ‘COMMIT TRANSACTION’ + @cr;
    –SET @sql += ‘RAISERROR(N”Complete!”, 10, 1) WITH NOWAIT;’ + @cr;

    SET @sql += ‘RAISERROR(N” _____ _ _ _ _ ”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N”/ __ \ | | | | | | |”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N”| / \/ ___ _ __ ___ _ __ | | ___| |_ ___ __| | |”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N”| | / _ \| ””_ ` _ \| ””_ \| |/ _ \ __/ _ \/ _` | |”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N”| \__/\ (_) | | | | | | |_) | | __/ || __/ (_| |_|”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N” \____/\___/|_| |_| |_| .__/|_|\___|\__\___|\__,_(_)”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N” | | ”, 10, 1) WITH NOWAIT;’ + @cr;
    SET @sql += ‘RAISERROR(N” |_| ”, 10, 1) WITH NOWAIT;’ + @cr;

    SET @sql += ‘GOTO EndSave’ + @cr + @cr;
    SET @sql += ‘QuitWithRollback:’ + @cr;
    SET @sql += ‘IF (@@TRANCOUNT > 0) ROLLBACK TRANSACTION’ + @cr;
    SET @sql += ‘RAISERROR(N”Variable creation failed”, 16,1) WITH NOWAIT;’ + @cr + @cr;
    SET @sql += ‘EndSave:’ + @cr;
    SET @sql += ‘GO’;

    PRINT @sql;

    Pie Chart is bad!

    Pie Chart is bad!

          During my presentation at Power Saturday in Paris, I wanted to demonstrate to an already very optimistic public that Power BI is a great tool and that modeling is a must-have for their reports. (Data Modelling is not the subject here)
    Power BI a super tool? Yes, and to complete this idea, I added some slides to highlight each piece of the tool.

    One of the slides showed the ease and elegance of the visuals.
    After a quick search on Google images, I copied a screenshot created by a user in Power BI and added it to my presentation.

    In the audience, two friends looked at me with a horrible face. The damage was done, I had slipped the image of a Pie Chart!

    Nothing crazy, isn’t it? But when you know a bit of the history of the Pie Chart, we realize that this visual component is not recommended in the world of Data Viz. This blog post also follows some more or less tense discussions between colleagues.

    I wrote this article for many reasons:

    • This point is already super documented on internet, but some people never read them before. (I provide links at the end)
    • I wanted to convinced myself first, and created a Power BI file to confirm this point. (CFR the following screenshot)

    I was a Pie Chart lover

    I know that its use is not recommended and I try not to use it.
    But yes, I liked it!
    • It’s beautiful!
    • It’s colorful!
    It gives a professional look to our reports!
    • It shows information!
    It gives me some pride to not use tables or matrix. (Yes, I like numbers, not you?)

    I think we are used to seeing this kind of visualization since our earliest childhood in commercial brochures, professional documents.

    The Pie Chart is visual, and help us to return informations quickly. But unfortunately, this information is not always the right one.


    To compare groups, we evaluate the difference of the amplitude of the angles while a histogram will request us to compare lengths between them.

    A long description is not needed, I would prefer to show you 2 set of data, both represented in a Pie Chart and histogram.

    Your first look will give you some information, but not as much precise compared to the two histograms below. It’s possible to have quick information, but very hard to have a clear interpretation of them.

    If I didn’t convince you, try to sort each portion from the smallest to the biggest. Try to do the same with the histogram and compare the time elapsed for both.

    SSMS – Query Shortcuts : Feel like a superman developer

    SSMS – Query Shortcuts : Feel like a superman developer

    SSMS Query Shortcut

    Dear BI Developer,

    I’m pretty sure you would be happy to improve your productivity. If not, you should at least read this article to look like a superman (superwoman) developer.

    When I start a new mission, the first thing I do is to set up SSMS (SQL Server Management Studio). And because I’m the kind of guy who acts like a Microsoft BI evangelist (and also for running), I replicate my configuration on my colleague’s machines.


    In this example – CTRL + 4 – COUNT

    Select statement you want to execute, Press CTRL and 4

    SSMS will give you Nb impacted rows.

    In BI (and not only!), it’s very important to test if we have unexpected behavior with our joins.

    Does our INNER JOIN filter too much data? Or worst, does our join multiply our result set?

    A quick and easy CTRL + 4 will ensure you to respect your grain.

    How to configure Management Studio

    Open Management Studio, Go to Tools > Option…

    Under Environment > Keyboard > Query Shortcuts

    You have a list of existing shortcuts. (I don’t change them, but I neither use them too).
    You should now fill each text box with a query.

    (See image and table below)

    Queries are available on the next section

    Query Shortcuts

    Do not forget to add a space after each query.


    CTRL + 3

    1000 First Rows

    SELECT TOP 1000 * FROM

    CTRL + 4

    Nb Rows


    CTRL + 5

    All Rows


    CTRL + 6

    Describe Table

    EXEC sp_executesql N' SELECT schemas.name ,tables.name ,columns.name ,types.name ,columns.max_length ,columns.is_nullable ,columns.is_identity FROM sys.tables tables INNER JOIN sys.schemas schemas ON schemas.schema_id = tables.schema_id INNER JOIN sys.all_columns columns ON columns.object_id = tables.object_id INNER JOIN sys.types types ON types.system_type_id = columns.system_type_id WHERE UPPER(RTRIM(LTRIM(tables.name))) = UPPER(RTRIM(LTRIM(REPLACE(REPLACE(@objname, '']'', ''''), ''['', '''')))) ORDER BY tables.object_id, columns.column_id',N'@objname nvarchar(776)', @objname =

    Select a table and show a quick description of attributes (Name, Datatype, size, nullable, identity)

    CTRL + 0

    All Running queries


    a better SP_Who! List all running queries (Process ID, Status (blocked or running), users, …)

    For more informations check this article

    SELECT SPID = er.session_id ,BlkBy = CASE WHEN lead_blocker = 1 THEN -1 ELSE er.blocking_session_id END ,ElapsedMS = er.total_elapsed_time ,CPU = er.cpu_time ,IOReads = er.logical_reads + er.reads ,IOWrites = er.writes ,Executions = ec.execution_count ,CommandType = er.command ,LastWaitType = er.last_wait_type ,ObjectName = OBJECT_SCHEMA_NAME(qt.objectid,dbid) + '.' + OBJECT_NAME(qt.objectid, qt.dbid) ,SQLStatement = qt.text ,STATUS = ses.STATUS ,[Login] = ses.login_name ,Host = ses.host_name ,DBName = DB_Name(er.database_id) ,StartTime = er.start_time ,Protocol = con.net_transport ,transaction_isolation = CASE ses.transaction_isolation_level WHEN 0 THEN 'Unspecified' WHEN 1 THEN 'Read Uncommitted' WHEN 2 THEN 'Read Committed' WHEN 3 THEN 'Repeatable' WHEN 4 THEN 'Serializable' WHEN 5 THEN 'Snapshot' END ,ConnectionWrites = con.num_writes ,ConnectionReads = con.num_reads ,ClientAddress = con.client_net_address ,Authentication = con.auth_scheme ,DatetimeSnapshot = GETDATE() FROM sys.dm_exec_requests er LEFT JOIN sys.dm_exec_sessions ses ON ses.session_id = er.session_id LEFT JOIN sys.dm_exec_connections con ON con.session_id = ses.session_id OUTER APPLY sys.dm_exec_sql_text(er.sql_handle) AS qt OUTER APPLY ( SELECT execution_count = MAX(cp.usecounts) FROM sys.dm_exec_cached_plans cp WHERE cp.plan_handle = er.plan_handle ) ec OUTER APPLY ( SELECT lead_blocker = 1 FROM master.dbo.sysprocesses sp WHERE sp.spid IN (SELECT blocked FROM master.dbo.sysprocesses) AND sp.blocked = 0 AND sp.spid = er.session_id ) lb WHERE er.sql_handle IS NOT NULL AND er.session_id != @@SPID ORDER BY er.blocking_session_id DESC, er.logical_reads + er.reads DESC, er.session_id

    Pin It on Pinterest