One, of the many, features that I wish they would add to SSRS; is an easier way to send in multivalue selected parameters to a stored procedure.
The best solution to this problem, is to use a function that converts a string of delimited values into a table.
Here are the quick low down steps:
1) Go to your SSRS dataset properties -> Parameters -> (select the multivalue parameter function [fx]) and change the parameter to send in a sting of delimited values. For example,
=Join(Parameters!MeasurementID.Value,",")
2) Create the function that splits and creates a table:
CREATE FUNCTION dbo.fn_charlist_to_table
(
@list ntext,
@delimiter nchar(1) = N','
)
RETURNS @tbl TABLE (listpos int IDENTITY(1, 1) NOT NULL,
str varchar(4000),
nstr nvarchar(2000)) AS
BEGIN
DECLARE @pos int,
@textpos int,
@chunklen smallint,
@tmpstr nvarchar(4000),
@leftover nvarchar(4000),
@tmpval nvarchar(4000)
SET @textpos = 1
SET @leftover = ''
WHILE @textpos <= datalength(@list) / 2
BEGIN
SET @chunklen = 4000 - datalength(@leftover) / 2
SET @tmpstr = @leftover + substring(@list, @textpos, @chunklen)
SET @textpos = @textpos + @chunklen
SET @pos = charindex(@delimiter, @tmpstr)
WHILE @pos > 0
BEGIN
SET @tmpval = ltrim(rtrim(left(@tmpstr, @pos - 1)))
INSERT @tbl (str, nstr) VALUES(@tmpval, @tmpval)
SET @tmpstr = substring(@tmpstr, @pos + 1, len(@tmpstr))
SET @pos = charindex(@delimiter, @tmpstr)
END
SET @leftover = @tmpstr
END
INSERT @tbl(str, nstr) VALUES (ltrim(rtrim(@leftover)),
ltrim(rtrim(@leftover)))
RETURN
END
GO
3) In your stored procedure, call the function. For example:
Select * from measurment where
measurement.MeasurementID in (select [str] from fn_charlist_to_table(@MeasurementID,Default))
Interesting programming ideas, solutions, and logic that I have used to solve problems or have come across throughout my career.
About Me
- William Andrus
- Northglenn, Colorado, United States
- I'm primarily a BI Developer on the Microsoft stack. I do sometimes touch upon other Microsoft stacks ( web development, application development, and sql server development).
Thursday, November 04, 2010
Tuesday, October 05, 2010
Mimic Domain for SQL Server or other executables.
One tiresome thing about being a consultant is the lack of ability to use Windows Authentication when on site. Your computer is on a separate domain and you run into the "I need permissions" issue.
One solution to this is to use the run as command in a shortcut's target.
First create a shortcut of the executable.
For this example, I used SQL Server.
C:\Windows\System32\runas.exe /netonly /user:consultantDomain\wandrus "C:\Program Files\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"
The highlighted yellow is what was added, where I set the domain\user_name to what I would like to use. When this shortcut is then clicked, it will prompt for your password.
One solution to this is to use the run as command in a shortcut's target.
First create a shortcut of the executable.
For this example, I used SQL Server.
C:\Windows\System32\runas.exe /netonly /user:consultantDomain\wandrus "C:\Program Files\Microsoft SQL Server\100\Tools\Binn\VSShell\Common7\IDE\Ssms.exe"
The highlighted yellow is what was added, where I set the domain\user_name to what I would like to use. When this shortcut is then clicked, it will prompt for your password.
Labels:
domain,
password,
problem,
SQL Server
Wednesday, April 14, 2010
Only display # rows per page in a SSRS report.
One problem I ran into and tried every solution on the net (that I could find) with no luck. I had to display only 22 rows per page, with blank rows -- if the data is not filled in for the other rows.
Step 1: SQL
So, first thing is to get your sql query to return, not row numbers but page numbers. So in this case, I used the row_number function found in SQL-Server subtracting 1 and then divided by the number of rows I needed per page:
((row_number() over(order by license_no)) -1) /22 as 'Page'
Step 2: Add Parent Group
In my report I needed to display a header, a footer, detail information, and empty rows if not exceeding 22. This is of course done with SSRS's Table.
First add a new parent group and in the Group Properties/General add a group on to the "Page" field. This group should span the whole page.
Don't bother with the page break sections, this will just mess things up.
Step 3: Add Child Group with blank rows
The child group is what I used to hold the blank rows. Within that group I added 22 rows with no information in the textboxes.
Step 4: Change visibility of blank rows
Right-clicking on the row, and select Row Visibility. Select the bullet: "Show or hide based on an expression". Within each row the expression will check if the row count is less than the ((page + 1) * rows_per_page) - (rows_per_page - position)
Example (Note:"section2" is my dataset):
[Row 22 Expression]: =IIF(CountRows("Section2") < ((Fields!Page.Value + 1) * 22),false,true)
[Row 21 Expression]: =IIF(CountRows("Section2") < ((Fields!Page.Value + 1) * 22) - 1,false,true)
...
This is all that is needed to acomplish this task.
If you need to hide a row until the end, I used the following visibility expression:
=IIF(Fields!Page.Value = Last(Fields!Page.Value,"Section2"),false,true)
And of course if you need to repeat the header on each page, like I do. This is found in the Tablix Properites/General and just put a check mark in the "Repeat header rows on each page".
Step 1: SQL
So, first thing is to get your sql query to return, not row numbers but page numbers. So in this case, I used the row_number function found in SQL-Server subtracting 1 and then divided by the number of rows I needed per page:
((row_number() over(order by license_no)) -1) /22 as 'Page'
Step 2: Add Parent Group
In my report I needed to display a header, a footer, detail information, and empty rows if not exceeding 22. This is of course done with SSRS's Table.
First add a new parent group and in the Group Properties/General add a group on to the "Page" field. This group should span the whole page.
Don't bother with the page break sections, this will just mess things up.
Step 3: Add Child Group with blank rows
The child group is what I used to hold the blank rows. Within that group I added 22 rows with no information in the textboxes.
Step 4: Change visibility of blank rows
Right-clicking on the row, and select Row Visibility. Select the bullet: "Show or hide based on an expression". Within each row the expression will check if the row count is less than the ((page + 1) * rows_per_page) - (rows_per_page - position)
Example (Note:"section2" is my dataset):
[Row 22 Expression]: =IIF(CountRows("Section2") < ((Fields!Page.Value + 1) * 22),false,true)
[Row 21 Expression]: =IIF(CountRows("Section2") < ((Fields!Page.Value + 1) * 22) - 1,false,true)
...
This is all that is needed to acomplish this task.
If you need to hide a row until the end, I used the following visibility expression:
=IIF(Fields!Page.Value = Last(Fields!Page.Value,"Section2"),false,true)
And of course if you need to repeat the header on each page, like I do. This is found in the Tablix Properites/General and just put a check mark in the "Repeat header rows on each page".
Labels:
display,
per page,
reporting services,
rows,
ssrs
Friday, February 19, 2010
Precision Timer
Ran into a problem yesterday wth timing issues, this is the first time I ever needed to create a timer with extreme precision.
Note: The precision of multithreaded timers depends on the operating system, and is typically in the 10-20 milliseconds region. This class is used to generate greater precision using the P/Invoke interop and calls the Windows multimedia timer; which has a precision of 1 ms. But that increased responsiveness comes at a cost - since the system scheduler is running more often, the system spends more time scheduling tasks, context switching, etc. This can ultimately reduce overall system performance, since every clock cycle the system is processing "system stuff" is a clock cycle that isn't being spent running your application.
So here is some code I found and changed up a bit that uses the winmm.dll timesetevent:
An example of calling the class:
My event handler
Note: The precision of multithreaded timers depends on the operating system, and is typically in the 10-20 milliseconds region. This class is used to generate greater precision using the P/Invoke interop and calls the Windows multimedia timer; which has a precision of 1 ms. But that increased responsiveness comes at a cost - since the system scheduler is running more often, the system spends more time scheduling tasks, context switching, etc. This can ultimately reduce overall system performance, since every clock cycle the system is processing "system stuff" is a clock cycle that isn't being spent running your application.
So here is some code I found and changed up a bit that uses the winmm.dll timesetevent:
Code Snippet
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Runtime.InteropServices;
- using System.Diagnostics;
- public class PrecisionTimer : IDisposable
- {
- //Lib API declarations
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeSetEvent(uint uDelay, uint uResolution, TimerCallback lpTimeProc, UIntPtr dwUser, uint fuEvent);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeKillEvent(uint uTimerID);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeGetTime();
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeBeginPeriod(uint uPeriod);
- [DllImport("Winmm.dll", CharSet = CharSet.Auto)]
- static extern uint timeEndPeriod(uint uPeriod);
- //Timer type definitions
- [Flags]
- public enum fuEvent : uint
- {
- TIME_ONESHOT = 0, //Event occurs once, after uDelay milliseconds.
- TIME_PERIODIC = 1,
- TIME_CALLBACK_FUNCTION = 0x0000, /* callback is function */
- //TIME_CALLBACK_EVENT_SET = 0x0010, /* callback is event - use SetEvent */
- //TIME_CALLBACK_EVENT_PULSE = 0x0020 /* callback is event - use PulseEvent */
- }
- //Delegate definition for the API callback
- delegate void TimerCallback(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2);
- private fuEvent f;
- private uint ms;
- //IDisposable code
- private bool disposed = false;
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- private void Dispose(bool disposing)
- {
- if (!this.disposed)
- {
- if (disposing)
- {
- Stop();
- }
- }
- disposed = true;
- }
- ~PrecisionTimer()
- {
- Dispose(false);
- }
- ///
- /// The current timer instance ID
- ///
- uint id = 0;
- ///
- /// The callback used by the the API
- ///
- TimerCallback thisCB;
- ///
- /// The timer elapsed event
- ///
- public event EventHandler Timer;
- protected virtual void OnTimer(EventArgs e)
- {
- if (Timer != null)
- Timer(this, e);
- }
- ///
- /// Initialize
- ///
- ///
- ///
- public PrecisionTimer(uint ms, bool repeat)
- {
- //Initialize the API callback
- thisCB = CBFunc;
- this.ms = ms;
- //Set the timer type flags
- f = fuEvent.TIME_CALLBACK_FUNCTION(repeat ? fuEvent.TIME_PERIODIC : fuEvent.TIME_ONESHOT);
- //Tell OS that we are about to need a precision timer.
- PrecisionTimer.timeBeginPeriod(1);
- }
- ///
- /// Stop the current timer instance
- /// VERY IMPORTANT TO CALL
- ///
- public void Stop()
- {
- lock (this)
- {
- if (id != 0)
- {
- timeKillEvent(id);
- Trace.WriteLine("Timer " + id.ToString() + " stopped " + DateTime.Now.ToString("HH:mm:ss.ffff"));
- id = 0;
- }
- }
- //Tell OS that we are done using the precision timer and that it can continue back to normal.
- PrecisionTimer.timeEndPeriod(1);
- }
- ///
- /// Start a timer instance
- ///
- /// Timer interval in milliseconds
- /// If true sets a repetitive event, otherwise sets a one-shot
- public void Start()
- {
- //Kill any existing timer
- //Stop();
- lock (this)
- {
- id = timeSetEvent(ms, 0, thisCB, UIntPtr.Zero, (uint)f);
- if (id == 0)
- throw new Exception("timeSetEvent error");
- Trace.WriteLine("Timer " + id.ToString() + " started " + DateTime.Now.ToString("HH:mm:ss.ffff"));
- }
- }
- void CBFunc(uint uTimerID, uint uMsg, UIntPtr dwUser, UIntPtr dw1, UIntPtr dw2)
- {
- //Callback from the PrecisionTimer API that fires the Timer event. Note we are in a different thread here
- OnTimer(new EventArgs());
- }
- }
An example of calling the class:
PrecisionTimer timer = new PrecisionTimer(8500, false); //will time for 8.5 seconds before triggering an event
timer.Timer += new EventHandler(timer_Timer);
timer.Start();
//Do Stuff or something until event
My event handler
void timer_Timer(object sender, EventArgs e){
timer.Stop();
DoStuff();
}
Labels:
dllimport,
multimedia extension,
precision,
timer,
timesetevent,
windows,
winmm.dll
Monday, January 11, 2010
List Creator
This is one of my favorite pieces of code I've stolen borrowed from another site. I've had made some minor code changes and additions from the original, but basically I just need to send in a DataReader after running a sql command and it will return a list of objects of that type. (Very similar to Linq to Sql)
You just need to make sure that anything that is return through the query is defined in the object as a write property.
So here is the List Creator code, which does the creation via reflections:
So in my DAL I might have something like:
You just need to make sure that anything that is return through the query is defined in the object as a write property.
So here is the List Creator code, which does the creation via reflections:
Code Snippet
- public class ListCreator where T : new()
- {
- public List FindAll(IDataReader iDataReader)
- {
- List returnList = new List();
- try
- {
- //need a Type and PropertyInfo object to set properties via reflection
- Type tType = new T().GetType();
- PropertyInfo pInfo;
- //x will hold the instance of until it is added to the list
- T x;
- //use reader to populate list of objects
- while (iDataReader.Read())
- {
- x = new T();
- //set property values
- //for this to work, command’s column names must match property names in object
- for (int i = 0; i < iDataReader.FieldCount; i++)
- {
- pInfo = tType.GetProperty(iDataReader.GetName(i));
- pInfo.SetValue(x, (iDataReader[i] == DBNull.Value? null:iDataReader[i]), null);
- }
- //once instance of is populated, add to list
- returnList.Add(x);
- }
- }
- catch (Exception ex)
- {
- Logging.Logging.Error(ex.ToString());
- }
- return returnList;
- }
- }
Code Snippet
- public List<PartsOrdering> GetAllPartsOrdering()
- {
- List<PartsOrdering> li = new List<PartsOrdering>();
- try
- {
- using (SqlConnection cn = new SqlConnection(connString))
- {
- ListCreator<PartsOrdering> PartsOrders = new ListCreator<PartsOrdering>();
- SqlCommand cmd = new SqlCommand("dbo.selPartsOrdering", cn);
- cmd.CommandType = CommandType.StoredProcedure;
- cn.Open();
- li = PartsOrders.FindAll(ExecuteReader(cmd));
- }
- }
- catch (Exception ex)
- {
- Logging.Error(ex.ToString());
- }
- return li;
- }
Code Snippet
- [Serializable]
- public class PartsOrdering : IFormattable
- {
- #region Properties
- [DefaultValue(-1)]
- public int PartsOrderingID
- {
- get;
- set;
- }
- [DefaultValue("")]
- public string ToolID
- {
- get;
- set;
- }
- [DefaultValue("")]
- public string PartNumber
- {
- get;
- set;
- }
- [DefaultValue("")]
- public string Description
- {
- get;
- set;
- }
- [DefaultValue(0)]
- public int Quantity
- {
- get;
- set;
- }
- [DefaultValue(1)]
- public int PartsOrderingLocationID
- {
- get;
- set;
- }
- [DefaultValue(1)]
- public int PartsOrderingUrgencyID
- {
- get;
- set;
- }
- [DefaultValue("")]
- public string Comment
- {
- get;
- set;
- }
- public DateTime RequestedDateTime
- {
- get;
- set;
- }
- [DefaultValue("")]
- public string RequestedUser
- {
- get;
- set;
- }
- public string LocationName
- {
- get
- {
- List<PartsOrderingLocation> locations = new PartsOrderingLocation().List();
- return locations.Where(n => n.PartsOrderingLocationID.Equals(this.PartsOrderingLocationID)).Select(n => n.Name).First();
- }
- set
- {
- List<PartsOrderingLocation> locations = new PartsOrderingLocation().List();
- this.PartsOrderingLocationID = locations.Where(n => n.Name.Equals(value)).Select(n => n.PartsOrderingLocationID).First();
- }
- }
- public string UrgencyName
- {
- get
- {
- List<PartsOrderingUrgency> locations = new PartsOrderingUrgency().List();
- return locations.Where(n => n.PartsOrderingUrgencyID.Equals(this.PartsOrderingUrgencyID)).Select(n => n.Name).First();
- }
- set
- {
- List<PartsOrderingUrgency> locations = new PartsOrderingUrgency().List();
- this.PartsOrderingUrgencyID = locations.Where(n => n.Name.Equals(value)).Select(n => n.PartsOrderingUrgencyID).First();
- }
- }
- public int UrgencyRank
- {
- get
- {
- List<PartsOrderingUrgency> locations = new PartsOrderingUrgency().List();
- return locations.Where(n => n.PartsOrderingUrgencyID.Equals(this.PartsOrderingUrgencyID)).Select(n => n.Rank).First();
- }
- }
- [DefaultValue(-1)]
- public int PartsOrderingGroupID
- {
- get;
- set;
- }
- [DefaultValue(true)]
- public bool IsActive
- {
- get;
- set;
- }
- }
Labels:
business layer,
C#,
creator,
data access layer,
list creator,
object,
reflection
Friday, January 08, 2010
How to update large count of rows without locking them
Found this interesting, how to update large databases, so that you don't have to lock rows
http://blogs.msdn.com/sqlpfe/archive/2010/01/06/tsql-coding-patterns-i.aspx
So instead of the usual:
Try:
http://blogs.msdn.com/sqlpfe/archive/2010/01/06/tsql-coding-patterns-i.aspx
So instead of the usual:
UPDATE dbo.Foo
SET Column = 'Value'
Try:
DECLARE @UpdatedRows(PK_Id int NOT NULL PRIMARY KEY)
DECLARE @var INT
SELECT @var=0 -- this resets @@ROWCOUNT=1
WHILE @@ROWCOUNT >0
BEGIN
UPDATE TOP(1500) BAR
SET Column='Value'
OUTPUT inserted.PK_ID
INTO @UpdatedRows
FROM dbo.BAR as BAR
WHERE NOT EXISTS (SELECT 1 FROM @UpdatedRows UPD WHERE UPD.PK_ID=BAR.PK_ID)
END
I prefer one of the ways the commentator offered: (easier to follow)
WHILE @@ROWCOUNT >0
BEGIN
UPDATE TOP(1500) BAR
SET Column='Value'
WHERE
Column <> 'Value'
END
Labels:
coding pattern,
large,
rows,
SQL Server,
tsql,
update
Subscribe to:
Posts (Atom)