Showing posts with label features. Show all posts
Showing posts with label features. Show all posts

2007/04/18

Partial multi-column reports in Reporting Services

Scenario. You need to create a multicolumn report in reporting services, but unfortunately, you just need the multicolumn behaviour in the details part of the report, not also on the master.

Suppose you have an invoice (master: invoice number, customer, total sum, etc, details: every item being invoiced), or any other type of report that has a master/details structure and you just need the multi column behaviour in the details part of it.

Problem. As stated in Writing Multi-Column Reports:

A multi-column layout applies to the entire report. It is not possible to specify a multi-column layout on the top half of the report, and a tabular layout on the bottom half of the report.

It seems you really have a problem... Microsoft says that it is impossible to do what you need to. Fortunately there is a...

Workaround. The idea behind this workaround is letting RS believe that the report is just a tabular report as usual. In fact, all you need to do is pure T-SQL behind the scenes, the final report just have one column, but will seem to have 2 columns (or any) where you need to. Let's see it with an example. Suppose you have your data:

Invoice Customer Item Description  Qty  Price    Sum
 394483 JOHN DOE    1 FRENCH FRIES  1    3.00  17.00
 394483 JOHN DOE    2 BISCUITS      1    4.00  17.00
 394483 JOHN DOE    3 BEERx6        1    5.00  17.00
 394483 JOHN DOE    4 MILK          2    2.00  17.00
 394483 JOHN DOE    5 HAM           1    1.00  17.00
And you want to make your invoice with the details (Item, Description, Qty and Price) displayed in two columns. We need to transform the data into something like:
Invoice Customer Item Description  Qty  Price    Sum Item2 Description2 Qty2 Price
 394483 JOHN DOE    1 FRENCH FRIES  1    3.00  17.00     2 BISCUITS      1    4.00
 394483 JOHN DOE    3 BEERx6        1    5.00  17.00     4 MILK          2    2.00
 394483 JOHN DOE    5 HAM           1    1.00  17.00  NULL NULL         NULL  NULL
Do yo see the point? You need to create new fields (ended with 2 suffix) that will store the next row of the needed data, and return half the rows (plus one if the count is not pair). Having transformed your underlying data in such a way, you can create your report as usual, placing a table where you need to and doubling (in case you want 2 columns) the number of columns:
Item Description  Qty  Price         Item2 Description2  Qty2  Price2

How can we do such a transormation using T-SQL?

Let's suppose you have prepared a stored procedure for retrieving the underlying data for RS to use. If you have not, you will need to because we will be doing a couple of transformations, not just a simple select query, so you cannot embed the query into the report itself. You will need to use a stored procedure for it, let's call it RS_MyCustomReport.

You might have RS_MyCustomReport defined as something like:

USE [mydatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[RS_MyCustomReport](@Filter1 int = NULL, @Filter2 varchar(8)=NULL)
AS
  SELECT {your list of fields}
  FROM {your tables or views}
  WHERE ({FieldX} = @Filter1 OR @Filter1 IS NULL) AND
        ({FieldY} = @Filter2 OR @Filter2 IS NULL)

Where FieldX and FieldY are the fields to use to filter using the optional parameters @Filter1 and @Filter2. All you need to do is the following (the changes are highlighted):

USE [mydatabase]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[RS_MyCustomReport](@Filter1 int = NULL, @Filter2 varchar(8)=NULL)
AS
  SELECT IDENTITY(int,1,1) AS ID, AUX1.*
  INTO #TEMP
  FROM (
    SELECT {your list of fields}
    FROM {your tables or views}
    WHERE ({FieldX} = @Filter1 OR @Filter1 IS NULL) AND
          ({FieldY} = @Filter2 OR @Filter2 IS NULL)
  ) AUX1

  SELECT * FROM
   ( SELECT T1.*, T2.{FieldA} AS FieldA2, T2.{FieldB} AS FieldB2 {...}
     FROM #TEMP T1 LEFT OUTER JOIN #TEMP T2 ON (T1.ID+1 = T2.ID OR T2.ID IS NULL)
   ) AUX
  WHERE AUX.ID % 2 = 1
  ORDER BY AUX.ID

You can replace the number of joins and the % 2 operator if you need to create multi column reports of 3 or more columns. A whole running script that you can test follows:

SELECT IDENTITY(int,1,1) AS ID, AUX1.*
INTO #TEMP
FROM (SELECT 13 AS Col, 'one' AS Descr UNION
      SELECT 18, 'two' UNION
      SELECT 35, 'three' UNION
      SELECT 51, 'four' UNION
      SELECT 67, 'five') AS AUX1

SELECT * FROM #TEMP

SELECT * FROM (
  SELECT T1.*, T2.Col AS Col2, T2.Descr AS Descr2
  FROM #TEMP T1 LEFT OUTER JOIN #TEMP T2 ON (T1.ID+1 = T2.ID or T2.ID IS NULL)
) AUX WHERE AUX.ID % 2 = 1
ORDER BY AUX.ID

DROP TABLE #TEMP

2006/09/05

Identity columns in a view treated as an ordinary int column

I was experiencing the behavior shown in KB920974: FIX: SQL Server 2005 treats an identity column in a view as an ordinary int column when the compatibility level of the database is set to 80 and asked Microsoft support for the hotfix opening a new support incident. After some hours, technical staff sent me the links for the files and installed them on a virtualised environment (testing is always a good thing).

After having checked that the problem was solved if the database compatibility level was 80, I changed it back to 90 and the problem appeared again. I did not accept the ms incident as being resolved and still complained about it. They told me that the shown behaviour was BY DESIGN. In other words, if you are using a database with compatibility level 80, your identity fields will still behave as identity when viewed through a view (check the following example in how to reproduce the problem), but if you want to use compatibility level 90, the identity behavior is lost by design if the table is viewed through a view. I could not believe it! Why did they do that?

As stated in SQL Server 2005 BOL regarding Modifying Data Through a View:

You can modify the data of an underlying base table through a view, in the same manner as you modify data in a table by using UPDATE, INSERT and DELETE statements or by using the bcp utility and BULK INSERT statement. However, the following restrictions apply to updating views, but do not apply to tables:
  • Any modifications, including UPDATE, INSERT, and DELETE statements, must reference columns from only one base table.
  • The columns that are being modified in the view must reference the underlying data in the table columns directly. They cannot be derived in any other way, such as through:
    • An aggregate function (AVG, COUNT, SUM, MIN, MAX, GROUPING, STDEV, STDEVP, VAR and VARP).
    • A computation; the column cannot be computed from an expression using other columns. Columns formed using set operators (UNION, UNION ALL, CROSSJOIN, EXCEPT, and INTERSECT) amount to a computation and are also not updatable.
  • The columns that are being modified cannot be affected by GROUP BY, HAVING, or DISTINCT clauses.
  • TOP cannot be used anywhere in the select_statement of the view when WITH CHECK OPTION is also specified.
[...] The following additional guidelines apply:[...]
  • INSERT statements must specify values for any columns in the underlying table that do not allow null values and have no DEFAULT definitions.

Steps to reproduce the problem: Create a new [test] database in compatibility mode 90 (2005). Define the following two tables: TMaterials and TMeasureUnits with the script:

CREATE TABLE [dbo].[TMeasureUnits](
 [MeasureUnitId] [int] NOT NULL,
 [Description] [varchar](50) COLLATE Modern_Spanish_CI_AS NOT NULL,
 CONSTRAINT [PK_TMeasureUnits] PRIMARY KEY CLUSTERED 
(
 [MeasureUnitId] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[TMaterials](
 [MaterialId] [int] IDENTITY(1,1) NOT NULL,
 [Description] [varchar](50) COLLATE Modern_Spanish_CI_AS NULL,
 [MeasureUnitId] [int] NOT NULL,
 CONSTRAINT [PK_TMaterials] PRIMARY KEY CLUSTERED 
(
 [MaterialId] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[TMaterials] WITH CHECK
  ADD CONSTRAINT [FK_TMaterials_TMeasureUnits] FOREIGN KEY([MeasureUnitId])
  REFERENCES [dbo].[TMeasureUnits] ([MeasureUnitId])
GO
ALTER TABLE [dbo].[TMaterials] CHECK CONSTRAINT [FK_TMaterials_TMeasureUnits]
GO
INSERT INTO [dbo].[TMeasureUnits] (MeasureUnitId, Description) VALUES (1, 'Units')
INSERT INTO [dbo].[TMeasureUnits] (MeasureUnitId, Description) VALUES (2, 'Boxes')
INSERT INTO [dbo].[TMeasureUnits] (MeasureUnitId, Description) VALUES (3, 'm.')
INSERT INTO [dbo].[TMeasureUnits] (MeasureUnitId, Description) VALUES (4, 'Kg.')

Now we will create the offending view:

CREATE VIEW [dbo].[VMaterials]
AS
SELECT dbo.TMaterials.MaterialId,
       dbo.TMaterials.Description,
       dbo.TMaterials.MeasureUnitId,
       dbo.TMeasureUnits.Description AS MeasureUnit
FROM dbo.TMaterials INNER JOIN
     dbo.TMeasureUnits ON dbo.TMaterials.MeasureUnitId = dbo.TMeasureUnits.MeasureUnitId
GO

Now execute:

SELECT name, is_identity
FROM sys.columns
WHERE object_id = object_id('VMaterials')

And you will see that NONE of the fields have is_identity = 1. This behavior is told to be BY DESIGN if database compatibility level 90. The fix is only intended to restore original behavior when database is in level 80 (2000).

The real problem: Despite this different behavior, the view is still insertable using T-SQL. Our real problem then, is due to MS Access.

We are using MS Access 2003 as front-end for our SQL Server 2005 corporate database. With such a new behavior, MS Access 2003 is not able to do INSERTs in such views. When MS Access asks SQL Server for view's metadata (to know field types, lengths, primary keys, nullable fields, and identities), SQL incorrectly returns that the identity field is not an identity field anymore:

  1. Create a new MS Access 2003 database project and connect to your server and database.
  2. Change to see Queries objects and open VMaterials (note that the expected first field should be marked as (Autonumeric) and it is not).
  3. Try to insert a new record:
    • If you try to leave MaterialId blank, and enter Description='Sand', MeasureId=2, you will get the error: Invalid input parameter values. Check the status values for details.Invalid input parameter values. Check the status values for details.
    • If you try to give any value to MaterialId and Description='Sand', MeasureId=2 as before, you get the following error message: Cannot insert explicit value for identity column in table 'TMaterials' when IDENTITY_INSERT is set to OFFCannot insert explicit value for identity column in table 'TMaterials' when IDENTITY_INSERT is set to OFF

Up to now, our incident is still opened and no solution nor workaround has been given yet (apart from the obvious fall back to compatibility level 80).

2006/09/04

Numbered stored procedures will be deprecated

I was just browsing the BOL regarding CREATE PROCEDURE and found some words about numbered stored procedures:

This feature will be removed in a future version of Microsoft SQL Server. Avoid using this feature in new development work, and plan to modify applications that currently use this feature.
I have some of them in my corporate application. The purpose is to make somewhat like (or behaves like) overloaded functions and methods in OOP (object oriented programming), i.e:
void foo(int x) {
   printf("x is %d\n", x);
}
void foo(char *x) {
   printf("x is '%s'\n", x);
}

In T-SQL we would have:

CREATE PROCEDURE [dbo].[foo];1 @x int AS
  PRINT 'x is ' + CONVERT(varchar(8), @x)
GO
CREATE PROCEDURE [dbo].[foo];2 @x char AS
  PRINT 'x is ' + @x
GO

The fact is that in order to call those stored procedures you need to know the 'suffix' to be applied, such as:

EXEC foo;1 33
-- x is 33
EXEC foo;2 'a'
-- x is a
EXEC foo;1 'a'
--Msg 8114, Level 16, Status 1, Procedure foo, Line 0
--Error converting data type varchar to int.

Thus makes this feature useless from the point of view of behaving like OOP overloaded functions. For that reason and, since Microsoft has planned to deprecate numbered stored procedures for next releases of SQL Server, I will remove them from my application (and so should you do too). The easiest way to achieve the same functionality with a minimum effort is to make:

CREATE PROCEDURE [dbo].[foo_1] @x int AS
  PRINT 'x is ' + CONVERT(varchar(8), @x)
GO
CREATE PROCEDURE [dbo].[foo_2] @x char AS
  PRINT 'x is ' + @x
GO

If you want to know if your database uses numbered stored procedures somewhere you can execute this query:

SELECT DISTINCT sys.objects.name, sys.sql_modules.definition
FROM sys.sql_modules
  INNER JOIN sys.objects on sys.sql_modules.object_id = sys.objects.object_id
WHERE sys.sql_modules.definition LIKE '%;1%'
   OR sys.sql_modules.definition LIKE '%;2%'
   OR sys.sql_modules.definition LIKE '%;3%'

2006/07/16

Breaking changes in SQL Server 2005

This particular change does not affect me, since I do not use Merge Replication. However it is very important to have your triggers prepared to handle multirow inserts/updates/deletes in any case (it does not matter whether you use replication or not). Extracted from http://msdn2.microsoft.com/en-us/library/ms143470.aspx regarding Breaking Changes for Merge Replication
Batching of changes In previous versions of SQL Server, changes made by the Merge Agent were performed on a row-by-row basis. In SQL Server 2005, changes are batched to improve performance; therefore, more than one row can be inserted, updated, or deleted within a single statement. If any published tables in the publication or subscription databases have triggers, ensure that the triggers can handle multi-row inserts, updates, and deletes. For more information, see Multirow Considerations for DML Triggers.
And here is the important info with examples (from SQL Server 2005 BOL, as usual) Multirow Considerations for DML Triggers

2006/06/21

Microsoft Live Messenger released

Press note: Launch of Windows Live Messenger Marks Significant Progress for Microsoft’s Windows Live Era Features:
  • Video call.
  • Windows Live Call with Verizon Web Calling service.
  • Cordless phones designed exclusively for Windows Live Messenger.
  • Microsoft LifeCams optimized for Windows Live Messenger.
  • Sharing Folders. Now sharing is as easy as dragging and dropping a file. With a new, intuitive user interface, customers can more easily share their files and personal photos with family, friends and colleagues on their Windows Live Messenger Contact List.
  • Windows Live Contacts.
  • Integration with Windows Live services.
Even localised versions are available for downloading (at least, Spanish version). After installing it we can see a new service is installed (Start Configuration Control Panel Administrative Tasks Services) called: Messenger Sharing USN Journal Reader service. This service is in charge of monitoring USN Journal changes to file system for the directories shared with your msn contacts. These directories are under %USERPROFILE%\Local settings\Program data\Microsoft\Messenger\<your-msn-email> Under that directory you will find a folder called Sharing Folders and a hidden one called SharingMetadata. If you browse through the hidden one you will find another one called DFSR containing ConflictDelete, Deleted, Installing and Staging. For those of you not in the depths of Microsoft Server systems, I will tell you that this directory structure is used also on Windows Server 2003 R2, the newest release of Microsoft servers. One of the main advantages of this new R2 release is the new replication service, called DFS (Distributed File System). Extracted from Distributed File System Technology Center page from Microsoft:
The Distributed File System (DFS) technologies in Windows Server 2003 R2 offer wide area network (WAN)-friendly replication as well as simplified, fault-tolerant access to geographically dispersed files. The two technologies in DFS are as follows:
  • DFS Replication. New state-based, multimaster replication engine that is optimized for WAN environments. DFS Replication supports replication scheduling, bandwidth throttling, and a new byte-level compression algorithm known as remote differential compression (RDC).
  • DFS Namespaces. Technology that helps administrators group shared folders located on different servers and present them to users as a virtual tree of folders known as a namespace. DFS Namespaces was formerly known as Distributed File System in Windows 2000 Server and Windows Server 2003.
If you are using Windows Server 2003 R2 and want to keep folders synchronized, we recommend using DFS Replication instead of FRS. DFS Replication system in Windows Server 2003 R2 has many benefits over File Replication Service (FRS), including improved management tools, higher performance, and delegated management.
As you can see, it seems that Microsoft is also using their brand-new-fresh-cutting-edge technology for file replication on their new Windows Live Messenger. One of the new features of this file replicating technology is the previously called RDC (Remote Differential Compression), which in summary, only transfers compressed portions of the file that has been modified. If only some chunks of a huge Powerpoint presentation are modified, there is no need to transfer the whole thing again, only the modified portions of the file are compressed and transferred, thus saving time and bandwidth. Since home users do not have the same hight bandwidth as corporate environments have, I'm sure they will be glad to read this. I must congratulate Microsoft for including this new server technology in such an everyday-user product as Microsoft Live Messenger. Note for Live Messenger users: If a shared file is overwritten by both sides at the same time, one of them is selected as looser of the conflict. I suppose that Live Messenger will warn you about this if it ever happens (I haven't tested it wisely). Just for your information, when this happens on a Windows Server 2003 R2, the file that is selected as looser is stored on ConflictAndDeleted. The equivalent for Live Messenger is ConflictDelete.