Optimizing XML Queries in SQL Server

In creating a process where SQL Server takes XML data that is stored in a table and populates a table, I have learned a few things on how to get SQL Server to more efficiently query XML data.

Setup:

Let’s first create a table to store the data:

CREATE TABLE dbo.XMLDataStore(
  link nvarchar(100) NULL,
  data xml NULL
) ON [PRIMARY]
GO

Let’s take a look at the XML that we are going to query (yes I know is is a large xml document, but I wanted to show the peformance in a real world situation, not a 3 node xml document that is normally demonstrated)

Now let’s insert it into the table:

INSERT INTO [XMLTutorial].[dbo].[XMLDataStore]
           ([link]
           ,[data])
     VALUES
           ('ABCDEFGHIJ'
           ,'<ns0:ORU_R01_231_GLO_DEF xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ns0="http://labratory/DB/2X">
               ...
            </ns0:ORU_R01_231_GLO_DEF>')
GO

The first query starts at the Observation record (all 64 records) and traverses the xml document and creates the necessary columns in 16 seconds.

select  Observation.ref.value('((../../Patient/PID_PatientIdentificationSegment/PID_5_PatientName/XPN_1_GivenName/text())[1])','nvarchar(100)') as FirstName,
    Observation.ref.value('((../../Patient/PID_PatientIdentificationSegment/PID_5_PatientName/XPN_0_FamilyLastName/XPN_0_0_FamilyName/text())[1])','nvarchar(100)') as LastName,
    Observation.ref.value('((../../Patient/PID_PatientIdentificationSegment/PID_7_DateTimeOfBirth/TS_0_TimeOfAnEvent/text())[1])','nvarchar(100)') as BirthDate,
    Observation.ref.value('((../../Patient/PID_PatientIdentificationSegment/PID_2_PatientId/CX_0_Id/text())[1])','nvarchar(100)') as InsuranceNumber,
    Observation.ref.value('((../OBR_ObservationRequestSegment/OBR_1_SetIdObr/text())[1])','nvarchar(100)')as [OBRID],
    Observation.ref.value('((../OBR_ObservationRequestSegment/OBR_7_ObservationDateTime/TS_0_TimeOfAnEvent/text())[1])','nvarchar(10)') as ObservationDate,    
    Observation.ref.value('((../OBR_ObservationRequestSegment/OBR_4_UniversalServiceId/CE_1_Text/text())[1])','nvarchar(100)') as LabTestName,
    null as LabTestCode,
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_1_SetIdObx/text())[1])','nvarchar(100)') as [OBXID],
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_3_ObservationIdentifier/CE_4_AlternateText/text())[1])','nvarchar(100)') as LabResultName,
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_3_ObservationIdentifier/CE_0_Identifier/text())[1])','nvarchar(100)') as LabResultCode,
    Observation.ref.value('((./OBR_5_PriorityObr/text())[1])','nvarchar(100)') as [Priority],
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_14_DateTimeOfTheObservation/TS_0_TimeOfAnEvent/text())[1])','nvarchar(100)') as [ResultDate],
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_5_ObservationValue/CE_4_AlternateText/text())[1])','nvarchar(100)') as [ResultValue],
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_6_Units/CE_0_Identifier/text())[1])','nvarchar(100)') as [UnitOfMeasure],
    Observation.ref.value('((./OBX_ObservationResultSegment/OBX_7_ReferencesRange/text())[1])','nvarchar(100)') as [ReferenceRange]
from XMLDataStore x cross apply x.data.nodes('//Observation') Observation(ref)
where x.link='ABCDEFGHIJ'

QueryResults1

The first optimization step is instead of using the Observation node and deriving all of the other columns from that, you can further use CROSS APPLY to create separate nodes to extract data from coupled with not using wild cards and diving directly to the exact node I need to. I went to the OBX_ObservationResultSegment as the originating node, and then from that node (named Observation), I derived two other nodes to reference in the query; Patient and Request.

This time the query completed in 4 seconds:

WITH XMLNAMESPACES ('http://labratory/DB/2X' AS "ns0")
select  Patient.node.value('(PID_5_PatientName/XPN_1_GivenName/text())[1]','nvarchar(100)') as FirstName,
    Patient.node.value('(PID_5_PatientName/XPN_0_FamilyLastName/XPN_0_0_FamilyName/text())[1]','nvarchar(100)') as LastName,
    Patient.node.value('(PID_7_DateTimeOfBirth/TS_0_TimeOfAnEvent/text())[1]','nvarchar(100)') as BirthDate,
    Patient.node.value('(PID_2_PatientId/CX_0_Id/text())[1]','nvarchar(100)') as InsuranceNumber,
    Request.node.value('(OBR_1_SetIdObr/text())[1]','nvarchar(100)')as [OBRID],
    Request.node.value('(OBR_7_ObservationDateTime/TS_0_TimeOfAnEvent/text())[1]','nvarchar(10)') as ObservationDate,    
    Request.node.value('(OBR_4_UniversalServiceId/CE_1_Text/text())[1]','nvarchar(100)') as LabTestName,
    null as LabTestCode,
    Observation.node.value('(OBX_1_SetIdObx/text())[1]','nvarchar(100)') as [OBXID],
    Observation.node.value('(OBX_3_ObservationIdentifier/CE_4_AlternateText/text())[1]','nvarchar(100)') as LabResultName,
    Observation.node.value('(OBX_3_ObservationIdentifier/CE_0_Identifier/text())[1]','nvarchar(100)') as LabResultCode,
    Observation.node.value('(OBR_5_PriorityObr/text())[1]','nvarchar(100)') as [Priority],
    Observation.node.value('(OBX_14_DateTimeOfTheObservation/TS_0_TimeOfAnEvent/text())[1]','nvarchar(100)') as [ResultDate],
    Observation.node.value('(OBX_5_ObservationValue/CE_4_AlternateText/text())[1]','nvarchar(100)') as [ResultValue],
    Observation.node.value('(OBX_6_Units/CE_0_Identifier/text())[1]','nvarchar(100)') as [UnitOfMeasure],
    Observation.node.value('(OBX_7_ReferencesRange/text())[1]','nvarchar(100)') as [ReferenceRange]
from XMLDataStore x 
cross apply x.data.nodes('ns0:ORU_R01_231_GLO_DEF/CompleteOrder/Order/Observation/OBX_ObservationResultSegment') Observation(node)
cross apply Observation.node.nodes('../../../Patient/PID_PatientIdentificationSegment') Patient(node)
cross apply Observation.node.nodes('../../OBR_ObservationRequestSegment') Request(node)
where x.link='ABCDEFGHIJ'

QueryResults2

Never being satisfied, let’s add an index to the data. However, to add an xml index to the data, we need to create a clustered index on the table. If we simply try to add an xml index to the current table with this command:

CREATE PRIMARY XML INDEX PrimaryXMLIndex ON
dbo.XMLDataStore(data)
GO

We get the following error:

Msg 6332, Level 16, State 201, Line 1
Table 'dbo.XMLDataStore' needs to have a clustered primary key with less than 16 columns in it in order to create a primary XML index on it.

Not descriptive, so let’s create a new table:

CREATE TABLE dbo.OptimizedXMLDataStore(
  id INT IDENTITY PRIMARY KEY,
  link nvarchar(100) NOT NULL,
  data xml NOT NULL
) ON [PRIMARY]
GO

And creating the following indexes in the database:

CREATE PRIMARY XML INDEX PrimaryXMLIndex ON
dbo.OptimizedXMLDataStore(data)
GO
CREATE XML INDEX
XMLDataStore_XmlCol_PATH ON dbo.OptimizedXMLDataStore(data) 
USING XML INDEX PrimaryXMLIndex FOR PATH
GO

Now that the indexes are created, let’s insert the data into this table:

INSERT INTO [XMLTutorial].[dbo].[OptimizedXMLDataStore]
           ([link]
           ,[data])
     VALUES
           ('ABCDEFGHIJ'
           ,'<ns0:ORU_R01_231_GLO_DEF xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ns0="http://labratory/DB/2X">
               ...
            </ns0:ORU_R01_231_GLO_DEF>')
GO

Now let’s run the same query (except pointing to the indexed table):

WITH XMLNAMESPACES ('http://labratory/DB/2X' AS "ns0")
select  Patient.node.value('(PID_5_PatientName/XPN_1_GivenName/text())[1]','nvarchar(100)') as FirstName,
    Patient.node.value('(PID_5_PatientName/XPN_0_FamilyLastName/XPN_0_0_FamilyName/text())[1]','nvarchar(100)') as LastName,
    Patient.node.value('(PID_7_DateTimeOfBirth/TS_0_TimeOfAnEvent/text())[1]','nvarchar(100)') as BirthDate,
    Patient.node.value('(PID_2_PatientId/CX_0_Id/text())[1]','nvarchar(100)') as InsuranceNumber,
    Request.node.value('(OBR_1_SetIdObr/text())[1]','nvarchar(100)')as [OBRID],
    Request.node.value('(OBR_7_ObservationDateTime/TS_0_TimeOfAnEvent/text())[1]','nvarchar(10)') as ObservationDate,    
    Request.node.value('(OBR_4_UniversalServiceId/CE_1_Text/text())[1]','nvarchar(100)') as LabTestName,
    null as LabTestCode,
    Observation.node.value('(OBX_1_SetIdObx/text())[1]','nvarchar(100)') as [OBXID],
    Observation.node.value('(OBX_3_ObservationIdentifier/CE_4_AlternateText/text())[1]','nvarchar(100)') as LabResultName,
    Observation.node.value('(OBX_3_ObservationIdentifier/CE_0_Identifier/text())[1]','nvarchar(100)') as LabResultCode,
    Observation.node.value('(OBR_5_PriorityObr/text())[1]','nvarchar(100)') as [Priority],
    Observation.node.value('(OBX_14_DateTimeOfTheObservation/TS_0_TimeOfAnEvent/text())[1]','nvarchar(100)') as [ResultDate],
    Observation.node.value('(OBX_5_ObservationValue/CE_4_AlternateText/text())[1]','nvarchar(100)') as [ResultValue],
    Observation.node.value('(OBX_6_Units/CE_0_Identifier/text())[1]','nvarchar(100)') as [UnitOfMeasure],
    Observation.node.value('(OBX_7_ReferencesRange/text())[1]','nvarchar(100)') as [ReferenceRange]
from OptimizedXMLDataStore x 
cross apply x.data.nodes('ns0:ORU_R01_231_GLO_DEF/CompleteOrder/Order/Observation/OBX_ObservationResultSegment') Observation(node)
cross apply Observation.node.nodes('../../../Patient/PID_PatientIdentificationSegment') Patient(node)
cross apply Observation.node.nodes('../../OBR_ObservationRequestSegment') Request(node)
where x.link='ABCDEFGHIJ'

The results came back in 0 seconds

QueryResults3

Things I did not do:

  1. Actually see Clark Kent (I think he was born before June of 1938, but it was the first time he was writtent about)
  2. Question why the “The Last Son of Krypton” was actually getting lab work done
  3. Imported schemas into the database

HIPAA 5010 support now officially available for BizTalk

The HIPAA 5010 Support KB Article 973415 explains the new ability within BizTalk to support the next version of HIPAA standards for EDI data interchange.

Note: as noted in the Applies To section at the bottom of the article, this only works with BizTalk 2009, sorry BizTalk 2006 R2 users, looks like you will be migrating to 2009 sooner than you thought.

This also fixes an issue where the claim separation mechanism (multiple schema) was not working.

Bubble up searching within a BizTalk map

A recent client had a high profile issue that took some interesting mapping to get working. I thought that I would write about it, as it is the second client that I have come across that has needed it. Since it has happened twice, it warrants a blog entry.

Requirement: I don’t know where a particular number is going to show up, but I need to find it and place it in this particular column in the output.

Specifically: I know that the Medicaid or Medicare number is going to show up in 1 of 4 places, I need to look for it and where ever it is, I need to place it in these two places in the output.

It is going to show up in the REF02 where the REF01 is 1C (Medicare) and 1D (Medicaid) in the following locations: 2420A, 2310B, 2010BA, 2010AA. I want to look first in the 2420A level, if it is not there, then I want to search the 2310B level, 2010BA, and finally the 2010AA and since there might (most likely) be multiple REF segments, I need it to search all of the REF segments (not just the first one).

Here is a few snippets of the xml where the data can reside:

              <ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A>
                <REF01__ReferenceIdentificationQualifier>1D</REF01__ReferenceIdentificationQualifier>
                <REF02__RenderingProviderSecondaryIdentifier>2420A Medicaid</REF02__RenderingProviderSecondaryIdentifier>
              </ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A>
              <ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A>
                <REF01__ReferenceIdentificationQualifier>1C</REF01__ReferenceIdentificationQualifier>
                <REF02__RenderingProviderSecondaryIdentifier>2420A Medicare</REF02__RenderingProviderSecondaryIdentifier>
              </ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A>

and

            <ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B>
              <REF01__ReferenceIdentificationQualifier>1D</REF01__ReferenceIdentificationQualifier>
              <REF02__RenderingProviderSecondaryIdentifier>2310B Medicaid</REF02__RenderingProviderSecondaryIdentifier>
            </ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B>
            <ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B>
              <REF01__ReferenceIdentificationQualifier>1C</REF01__ReferenceIdentificationQualifier>
              <REF02__RenderingProviderSecondaryIdentifier>2310B Medicare</REF02__RenderingProviderSecondaryIdentifier>
            </ns0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B>

and

        <ns0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB>
          <REF01__ReferenceIdentificationQualifier>1D</REF01__ReferenceIdentificationQualifier>
          <REF02__PaytoProviderIdentifier>2010AB Medicaid</REF02__PaytoProviderIdentifier>
        </ns0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB>
        <ns0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB>
          <REF01__ReferenceIdentificationQualifier>1C</REF01__ReferenceIdentificationQualifier>
          <REF02__PaytoProviderIdentifier>2010AB Medicare</REF02__PaytoProviderIdentifier>
        </ns0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB>

and

          <ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop>
            <ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA>
              <REF01__ReferenceIdentificationQualifier>1D</REF01__ReferenceIdentificationQualifier>
              <REF02__BillingProviderAdditionalIdentifier>2010AA Medicaid</REF02__BillingProviderAdditionalIdentifier>
            </ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA>
            <ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA>
              <REF01__ReferenceIdentificationQualifier>1C</REF01__ReferenceIdentificationQualifier>
              <REF02__BillingProviderAdditionalIdentifier>2010AA Medicare</REF02__BillingProviderAdditionalIdentifier>
            </ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA>
          </ns0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop>

So for the purposes of this blog entry I have created an output schema that creates an output row for each service line:

SampleOutputSchema

I first create some mapping so we can ensure that the map is creating the output correctly.

xsltmap1

Which creates this (so far so good):

<ns0:Root xmlns:ns0="http://BubbleUp.Output">
  <ServiceLine ChargeAmt="914"></ServiceLine>
  <ServiceLine ChargeAmt="5299"></ServiceLine>
</ns0:Root>

Now we get into the ‘real work’

What we need to do is make the map query various portions of the xml document and because I am lazy, lets have the mapper do the heavy lifting for us:

Lets drag the REF02 from the 2420A loop into the MedicaidId and lets look at the XSL:

xsltmap2

and the resulting xsl is:

<xsl:attribute name="MedicaidId">
  <xsl:value-of select="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A/REF02__RenderingProviderSecondaryIdentifier/text()" />
</xsl:attribute>

and since we are going to want it from the other loops, let’s get the other REF segments in xsl:

<xsl:attribute name="MedicaidId">
  <xsl:value-of select="../s0:TS837Q2_2310_SubLoop/s0:TS837Q2_2310B_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B/REF02__RenderingProviderSecondaryIdentifier/text()" />
</xsl:attribute>

<xsl:attribute name="MedicaidId">
  <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AB_Loop/s0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB/REF02__PaytoProviderIdentifier/text()" />
</xsl:attribute>

<xsl:attribute name="MedicaidId">
  <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AA_Loop/s0:REF_TS837Q2_2010AA_SubLoop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA/REF02__BillingProviderAdditionalIdentifier/text()" />
</xsl:attribute>

Now we need to write the ‘bubble up’ logic:

The first thing is we need to query the correct REF01 qualifier

<xsl:value-of select="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A[REF01__ReferenceIdentificationQualifier/text()='1D']/REF02__RenderingProviderSecondaryIdentifier/text()" />

and the other values in the other segments the same logic.

After that we merge all of these queries into a variable in a choose function:

<!--Lets create the Mediciad ID variable-->
<xsl:variable name="medicaidID">
  <xsl:choose>
    <xsl:when test="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A/REF01__ReferenceIdentificationQualifier/text()='1D'">
      <xsl:value-of select="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A[REF01__ReferenceIdentificationQualifier/text()='1D']/REF02__RenderingProviderSecondaryIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../s0:TS837Q2_2310_SubLoop/s0:TS837Q2_2310B_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B/REF01__ReferenceIdentificationQualifier/text()='1D'">
      <xsl:value-of select="../s0:TS837Q2_2310_SubLoop/s0:TS837Q2_2310B_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B[REF01__ReferenceIdentificationQualifier/text()='1D']/REF02__RenderingProviderSecondaryIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AB_Loop/s0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB/REF01__ReferenceIdentificationQualifier/text()='1D'">
      <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AB_Loop/s0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB[REF01__ReferenceIdentificationQualifier/text()='1D']/REF02__PaytoProviderIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AA_Loop/s0:REF_TS837Q2_2010AA_SubLoop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA/REF01__ReferenceIdentificationQualifier/text()='1D'">
      <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AA_Loop/s0:REF_TS837Q2_2010AA_SubLoop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA[REF01__ReferenceIdentificationQualifier/text()='1D']/REF02__BillingProviderAdditionalIdentifier/text()" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="'Mediciad Id Not found'" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>

Now lets take the result of the result of the choose function and populate the attribute:

<!--Now that we have found it, lets put the value as the attribute-->
<xsl:attribute name="MedicaidId">
  <xsl:value-of select="$medicaidID" />
</xsl:attribute>

So the last step in this is to implement the code in the map, because like all of the maps I have ever worked in, I already have logic I have created, I just need to enhance what I already have there. I copy the above xsl into a scripting (inline xslt) functiod and attach it to the MediaidId attribute:

xsltmap3

Now I need to find the MedicareId, so I simply replace the ID with 1C and change the variable name and attribute name I would have the following code:

<xsl:variable name="medicareID">
  <xsl:choose>
    <xsl:when test="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A/REF01__ReferenceIdentificationQualifier/text()='1C'">
      <xsl:value-of select="s0:TS837Q2_2420_SubLoop/s0:TS837Q2_2420A_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2420A[REF01__ReferenceIdentificationQualifier/text()='1C']/REF02__RenderingProviderSecondaryIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../s0:TS837Q2_2310_SubLoop/s0:TS837Q2_2310B_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B/REF01__ReferenceIdentificationQualifier/text()='1C'">
      <xsl:value-of select="../s0:TS837Q2_2310_SubLoop/s0:TS837Q2_2310B_Loop/s0:REF_RenderingProviderSecondaryIdentification_TS837Q2_2310B[REF01__ReferenceIdentificationQualifier/text()='1C']/REF02__RenderingProviderSecondaryIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AB_Loop/s0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB/REF01__ReferenceIdentificationQualifier/text()='1C'">
      <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AB_Loop/s0:REF_PaytoProviderSecondaryIdentificationNumber_TS837Q2_2010AB[REF01__ReferenceIdentificationQualifier/text()='1C']/REF02__PaytoProviderIdentifier/text()" />
    </xsl:when>
    <xsl:when test="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AA_Loop/s0:REF_TS837Q2_2010AA_SubLoop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA/REF01__ReferenceIdentificationQualifier/text()='1C'">
      <xsl:value-of select="../../../s0:TS837Q2_2010A_SubLoop/s0:TS837Q2_2010AA_Loop/s0:REF_TS837Q2_2010AA_SubLoop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA_Loop/s0:REF_BillingProviderSecondaryIdentificationNumber_TS837Q2_2010AA[REF01__ReferenceIdentificationQualifier/text()='1C']/REF02__BillingProviderAdditionalIdentifier/text()" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="'Medicare Id Not found'" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:variable>
<xsl:attribute name="MedicareId">
  <xsl:value-of select="$medicareID" />
</xsl:attribute>

Put it into a scripting functiod and you would have the following map:

xsltmap4

The output looks like this:

<ns0:Root xmlns:ns0="http://BubbleUp.Output">
  <ServiceLine ChargeAmt="914" MedicaidId="2420A Medicaid" MedicareId="2420A Medicare"></ServiceLine>
  <ServiceLine ChargeAmt="5299" MedicaidId="2310B Medicaid" MedicareId="2010AB Medicare"></ServiceLine>
</ns0:Root>

As a bonus: the client wanted to sum up Deductible and CoInsurance amounts from the CAS segment where the qualifier was 1 and 2 respectively, so the following example has that xsl included in it.

Here is the solution (BizTalk 2009 solution only: please don’t ask for 2006, as I don’t have time to make a version for that!)

What I would look for in a BizTalk Healthcare class/Healthcare book for BizTalk

I have been thinking of BizTalk healthcare classes or writing a book for BizTalk in relation to healthcare, and I thought that I would put the things that I would look for if I was going to pay for a class or heaven forbid; buy a morbid book.

1. The teacher/writer has to have healthcare experience.

2. I want to have a brief understanding of the ‘flow’ of document in a healthcare situation:

     837 (Claims) are submitted to a health plan for payment
     997 (Acknowledgments) are returned to know if they pass Type 1 and Type 2 validation
     824 (Application Notification) can be returned for Type 3 through Type 7 validation, depending on the capability of the backend system
     835 (Claim Status) are sent back to notify of Claim status
     270 (Eligibility Request) are sent to health plan to ask for status of patient eligibility for service
     271 (Eligibility Response) are sent from health plan in response to patient eligibility request
     276 (Claim Status Request) are sent to health plan to as for status of claim
     277 (Claim Status Response) are sent from health plan to in response of claim request
     820 (Payment Notification) are sent from the health plan to notify of payment of the claims
     834 (Eligibility) are sent for notification of eligibility

3. I want to map a 837, as that will be the transaction I will most deal with, spend a majority of the the time mapping the 837 transaction if I have to, because that is my biggest pain point.

4. Explain HL7, where it is used

    Provider transactions, largely used in intra department messaging

5. Create a sample ORU^R01 message from a flat file and create a flat file from a ORU^R01 message so I can learn the following HL7 nuances:

    Schema Generation (partner specific schema creation)
    Multipart Messages 
    MSH Segment 
    BodySegments 
    ZSegments       
    How to map the flat XML message that the BTAHL7 pipeline (DASM) creates / (ASM) expects 
    Explain MLLP, integrate it into one of the ORU labs

Things I want to briefly want to learn:

    Pipelines
    Orchestrations
    Adapters
    (I need to know what they are, but it should not be the focus of a the class/book)

Things I don’t want to learn:

BAM: BizTalk in a Healthcare Scenario should never be the system of record, the Claim Payment application should be, the out-of-the-box EDI reporting functionality that comes with BizTalk accounts for 99% of what I need
BRE: BizTalk would not be used for message modification/routing/anything else the BRE can be used for, as there are entire workflows designed in every claim payment processing system that deals with changing business rules
AS2: It is too big of a beast to teach along with all of the things that I listed above, and second: NO HEALTHCARE COMPANY TRANSMITS DATA USING AS2
Purchase Orders, Invoices, Advanced Shipment Notifications: I can look at a whole plethora of examples that Microsoft publish. Maybe a MCO deals with ordering supplies for an office, but by in large, I want to know about HIPAA transactions; not transactions I would never see at work.

Anything that I missed? I am thinking of putting together an online class (or via CD) or a book, what would you like to see? Is there any interest in a BizTalk/Healthcare book?

Tell me your experiences!

Preserve Interchange – what is it, and why use it

The below setting is somewhat misunderstood:

Preserve1

If you are to set this setting, what is passed to the Message Box, and what is the behavior.

To simulate this, I created a send port with the following setting:

image

When I ran the 867 (found in the samples folder of the SDK) I got the following error message:

There was a failure executing the send pipeline: "Microsoft.BizTalk.DefaultPipelines.XMLTransmit, Microsoft.BizTalk.DefaultPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "XML assembler" Send Port: "EDI Drop Off" URI: "C:\Users\Administrator\Documents\Visual Studio 2008\Projects\EDI\Output\%MessageID%.xml" Reason: This Assembler cannot retrieve a document specification using this type: http://schemas.microsoft.com/BizTalk/EDI/X12/2006/InterchangeXML#X12InterchangeXml.

I could not find the schema in the BizTalk 2009 folder.

Here is the data that is created by the EDI DASM with the Preserve Interchange setting:

<ins0:X12InterchangeXml DelimiterSetSerializedData="126:13:10:42:58:-1:-1:-1:-1" xmlns:ins0="http://schemas.microsoft.com/BizTalk/EDI/X12/2006/InterchangeXML">
  <ns0:ISA xmlns:ns0="http://schemas.microsoft.com/Edi/X12ServiceSchema">
    <ISA01>00</ISA01>
    <ISA02>          </ISA02>
    <ISA03>00</ISA03>
    <ISA04>          </ISA04>
    <ISA05>ZZ</ISA05>
    <ISA06>7654321        </ISA06>
    <ISA07>ZZ</ISA07>
    <ISA08>1234567        </ISA08>
    <ISA09>991221</ISA09>
    <ISA10>1226</ISA10>
    <ISA11>U</ISA11>
    <ISA12>00401</ISA12>
    <ISA13>000000025</ISA13>
    <ISA14>0</ISA14>
    <ISA15>T</ISA15>
    <ISA16>:</ISA16>
  </ns0:ISA>
  <FunctionalGroup DocType="http://schemas.microsoft.com/BizTalk/EDI/X12/2006#X12_00401_864">
    <ns0:GS xmlns:ns0="http://schemas.microsoft.com/Edi/X12ServiceSchema">
      <GS01>TX</GS01>
      <GS02>01</GS02>
      <GS03>1234567</GS03>
      <GS04>19991221</GS04>
      <GS05>1226</GS05>
      <GS06>2</GS06>
      <GS07>X</GS07>
      <GS08>004010</GS08>
    </ns0:GS>
    <TransactionSet DocType="http://schemas.microsoft.com/BizTalk/EDI/X12/2006#X12_00401_864">
      <ns0:X12_00401_864 xmlns:ns0="http://schemas.microsoft.com/BizTalk/EDI/X12/2006">
        <ST>
          <ST01>864</ST01>
          <ST02>3313</ST02>
        </ST>
        <ns0:BMG>
          <BMG01>00</BMG01>
        </ns0:BMG>
        <ns0:DTM>
          <DTM01>001</DTM01>
        </ns0:DTM>
        <ns0:N1Loop1>
          <ns0:N1>
            <N101>001</N101>
          </ns0:N1>
          <ns0:N2>
            <N201>N201</N201>
          </ns0:N2>
          <ns0:N3>
            <N301>N301</N301>
          </ns0:N3>
          <ns0:N4>
            <N401>N401</N401>
          </ns0:N4>
          <ns0:REF>
            <REF01>01</REF01>
            <ns0:C040>
              <C04001>01</C04001>
              <C04002>C04002</C04002>
            </ns0:C040>
          </ns0:REF>
          <ns0:PER>
            <PER01>1A</PER01>
          </ns0:PER>
        </ns0:N1Loop1>
        <ns0:MITLoop1>
          <ns0:MIT>
            <MIT01>MIT01</MIT01>
          </ns0:MIT>
          <ns0:N1Loop2>
            <ns0:N1_2>
              <N101>001</N101>
            </ns0:N1_2>
            <ns0:N2_2>
              <N201>N201</N201>
            </ns0:N2_2>
            <ns0:N3_2>
              <N301>N301</N301>
            </ns0:N3_2>
            <ns0:N4_2>
              <N401>N401</N401>
            </ns0:N4_2>
            <ns0:REF_2>
              <REF01>01</REF01>
              <ns0:C040_2>
                <C04001>01</C04001>
                <C04002>C04002</C04002>
              </ns0:C040_2>
            </ns0:REF_2>
            <ns0:PER_2>
              <PER01>1A</PER01>
            </ns0:PER_2>
          </ns0:N1Loop2>
          <ns0:MSG>
            <MSG01>MSG01</MSG01>
          </ns0:MSG>
        </ns0:MITLoop1>
        <SE>
          <SE01>18</SE01>
          <SE02>3313</SE02>
        </SE>
      </ns0:X12_00401_864>
    </TransactionSet>
    <ns0:GE xmlns:ns0="http://schemas.microsoft.com/Edi/X12ServiceSchema">
      <GE01>1</GE01>
      <GE02>2</GE02>
    </ns0:GE>
  </FunctionalGroup>
  <ns0:IEA xmlns:ns0="http://schemas.microsoft.com/Edi/X12ServiceSchema">
    <IEA01>1</IEA01>
    <IEA02>000000025</IEA02>
  </ns0:IEA>
</ins0:X12InterchangeXml>

So what can consume this message?

If you look at Preserving a Received Batched EDI Interchange, it does not explain about what happens to the document.

If you look at Sending a Preserved Batch Interchange, it will recreate the interchange based on the settings described on the page.

I asked why there is this option, to preserve interchange, getting a document that is only consumable by the EDI ASM, and was told that you can create a map that transforms all of the EDI transactions from one to another and keep the batch together, however this does not seem correct, because a map would be looking for a MessageType of http://schemas.microsoft.com/BizTalk/EDI/X12/2006#X12_00401_864, not http://schemas.microsoft.com/BizTalk/EDI/X12/2006/InterchangeXML#X12InterchangeXml

It seems that this is only for validation, or re-enveloping.

Google Chrome Operating System

I know this is VERY off subject, but I wanted to see what all of the hubub was about with the new OS that is going to be hitting the market in late 2010.

I installed it and ran it. Not terribly interesting. Here are two of the main shots:

Chromium0

and the really interesting screen

Chromium1

My take on it:

I guess it will be good for those who love to browse the internet and that is it. I am a big fan of Android, but an OS that is centered simply around the web? I guess the time is coming … “welcome to cloud computing.”

Trim leading characters from a string in TSQL

The easiest way to do this is multiply by 1, but if it is a (n)varchar data type, it is a little more challenging.

This was the query I had to run:

DECLARE @insurancenumber nvarchar(100),@charactertopurge nvarchar(1)
SET @charactertopurge='0'
SET @insurancenumber='00PK102947'
SELECT @GMPI=GMPI
from Patient.dbo.PatientAlias
where AliasField=substring(@insurancenumber, patindex('%[^'+@charactertopurge+']%',@insurancenumber), len(@insurancenumber))

DTCTester

According to the DTCTest tool documentation, you create a DSN, and run the DTCTester.exe

When I ran it, I got the following error:

SQLSTATE=IM002,Native error=0,msg='[Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified'

The tool is 32 bit, and access the ODBC for the 32 bit client, not the 64 bit client that is in the Administration Console. To access the 32 bit client, you have to open up: c:\windows\syswow64\odbcad32.exe

XPATH 1.0 XSLT 1.0 Return elements or attributes based upon a max date

While working for a client we had to use some custom  XSLT within BizTalk to complete some requirements that was outside the bounds of the BizTalk Mapper and orchestrations. Along with what we were already doing the client came to us and wanted a value assigned to an attribute based up on the most recent input file that had been dropped so essentially based upon the a max date from an array of dates. Come to find out that XPATH 1.0 and XSLT 1.0 can’t select records based upon a max date. Here is how I solved this problem.

 

I made some generic samples to help explain. Here is a sample input document.



<?xml version="1.0" encoding="utf-8"?>
<BatchedFile>
  <Record>
    <InformationElement Date="12/09/2008" ID="TryAgain" />
  </Record>
  <Record>
    <InformationElement Date="11/11/2008" ID="TryAgaine" />
  </Record>
  <Record>
    <InformationElement Date="01/08/2008" ID="TryAgain" />
  </Record>
  <Record>
    <InformationElement Date="03/09/2008" ID="TryAgain" />
  </Record>
  <!-- I Want to pick the following record ID because it is the most recent date- -->

  <Record>
    <InformationElement Date="01/09/2009" ID="YouPickedTheRightOne" />
  </Record>
  <Record>
    <InformationElement Date="07/09/2008" ID="TryAgain" />
  </Record>
  <Record>
    <InformationElement Date="12/09/2008" ID="TryAgain" />
  </Record>  
</BatchedFile>

 

The trick is to try and compare the dates. XSLT can’t make an array exactly but you can use variables in a similar way and use the node-set() function to select the values within the variable.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl userCSharp" xmlns:userCSharp="http://schemas.microsoft.com/BizTalk/2003/userCSharp" >
    <xsl:output method="xml" indent="yes"/>

    <xsl:template match="BatchedFile">
      <!-- First Create a variable that stores the Date Attribute along with its corresponding absolute value-->
      <xsl:variable name="MaxDateRecord">
        <xsl:for-each select ="./Record/InformationElement/@Date">
          <TotalDays>
            <xsl:attribute name ="RecordDate">
              <xsl:value-of select ="self::node()"/>
            </xsl:attribute>
            <!--Please see below for explanation of totalDays function-->
            <xsl:value-of select ="userCSharp:totalDays(self::node())"/>            
          </TotalDays>          
        </xsl:for-each>       
      </xsl:variable>
      <!--I decided to create a variable that stored the most recent date, its easier to debug-->
      <xsl:variable name ="MostRecentDate">
        <!--The predicate after TotalDays is what picks the highest number value, which then corresponds to a date-->
        <xsl:value-of select ="msxsl:node-set($MaxDateRecord)/TotalDays[not(. &lt; preceding-sibling::TotalDays or . &lt; following-sibling::TotalDays)]/@RecordDate"/>
      </xsl:variable>
      <!-- Here is where the output is generated, -->
      <MaxDate>
        <xsl:attribute name="DidIPickTheRightOne">
          <xsl:value-of select ="//InformationElement[@Date=$MostRecentDate]/@ID"/>
        </xsl:attribute>
      </MaxDate>
    </xsl:template>
  <msxsl:script language="C#" implements-prefix="userCSharp">
    <!-- This function takes a date like 09/01/2008 
    and returns the number days that exist between 01/01/1990 
    and the current date selected. Any date would work as a reference
    date.-->
    <![CDATA[
 public static string totalDays(string date)
        {
            DateTime d1 = new DateTime();
            DateTime d2 = new DateTime(1990, 01, 01);
            TimeSpan span = new TimeSpan();
            DateTime.TryParse(date,out d1);            
            span = d1 - d2;
            return span.TotalDays.ToString();
            
        }       
]]>
  </msxsl:script>
</xsl:stylesheet>

Okay here is an explanation of what is going on.

1. I first create a variable (MaxDateRecord) that stores the date value of each record as well as the number of days between that date and Jan 1, 1990. This gives me a numeric value that XSLT can compare.

a. Here is what MaxDateRecord would look like essentially after it had ran through all the iterations of the Record element.

<MaxDateRecord>
<TotalDays RecordDate="12/09/2008">6917</TotalDays>
<TotalDays RecordDate="11/11/2008">6889</TotalDays>
<TotalDays RecordDate="01/08/2008">6581</TotalDays>
<TotalDays RecordDate="03/09/2008">6642</TotalDays>
<TotalDays RecordDate="01/09/2009">6948</TotalDays>
<TotalDays RecordDate="07/09/2008">6764</TotalDays>
<TotalDays RecordDate="12/09/2008">6917</TotalDays>
</MaxDateRecord>

2. I then compare all of the TotalDays and find the largest one and select out the RecordDate that matches.

a. The MostRecentDate variable outputs “01/09/2009” in Visual Studio.

3. After that its an easy process of selecting the Record element with the corresponding MostRecentDate or in this case the ID of that element.

<?xml version="1.0" encoding="utf-8"?>
<!-- This is what my output should be -->
<MaxDate DidIPickTheRightOne="YouPickedTheRightOne" />

I hope I have saved some of you some time.  Since BizTalk 2009 still uses the 1.0 specification of XSLT and XPath, I was somewhat disappointed to find out there was no direct way to get this done. I know there are better ways to handle the logic I wrote in XSLT so if you have suggestions or criticisms, post them.

I have to give some thanks to Jirka Kosek for the article “Understanding the node-set() Function” http://www.xml.com/pub/a/2003/07/16/nodeset.html

as well as lhaeger for the article “Again! The power of XPATH or the missing max() function” http://www.novell.com/communities/node/6431/again-power-xpath-or-missing-max-function

Notification when Receive Locations/Send Ports change

Instead of scanning the Event Log and capturing when the Receive Locations or Send Ports go down, you can use the following triggers to send out an email when they change status:

Receive Location:

CREATE TRIGGER [dbo].[ReceiveLocationChangeNotification] 
   ON [dbo].[adm_ReceiveLocation]
   AFTER UPDATE
AS 
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

    DECLARE @oldstatus int
  DECLARE @newstatus int
  DECLARE @ReceiveLocationName nvarchar(256)
  DECLARE @status nvarchar(10)
  DECLARE @message nvarchar(300)
  --Was the status not changed
  IF NOT UPDATE([Disabled])
  BEGIN
  RETURN
  END
  --Otherwise send out email
  select @oldstatus=(select [Disabled] from Deleted)
  select @newstatus=(select [Disabled] from Inserted)
  select @ReceiveLocationName=(select [Name] from Inserted)
  SET @message=@ReceiveLocationName+' recieve location changed from '+ case
                     when @oldstatus=-1 then 'Disabled'
                    when @oldstatus=0 then 'Enabled'
                    END + ' to ' +
                    case
                    when @newstatus=-1 then 'Disabled'
                    when @newstatus=0 then 'Enabled'
                    END 
                EXEC msdb.dbo.sp_send_dbmail @recipients='person@company.com',
                @subject = @message,
                @body = @message,
                @body_format = 'HTML' ;

--  print @message

END
GO

Send Port:

CREATE TRIGGER dbo.SendPortChangeNotification 
   ON dbo.bts_sendport
   AFTER UPDATE
AS 
BEGIN
  -- SET NOCOUNT ON added to prevent extra result sets from
  -- interfering with SELECT statements.
  SET NOCOUNT ON;

    DECLARE @oldstatus int
  DECLARE @newstatus int
  DECLARE @PortName nvarchar(256)
  DECLARE @status nvarchar(10)
  DECLARE @message nvarchar(300)
  --Was the status not changed
  IF NOT UPDATE(nPortStatus)
  BEGIN
  RETURN
  END
  --Otherwise send out email
  select @oldstatus=(select nPortStatus from Deleted)
  select @newstatus=(select nPortStatus from Inserted)
  select @PortName=(select nvcName from Inserted)
  SET @message=@PortName+' changed from '+ case
                     when @oldstatus=1 then 'Unenlisted'
                    when @oldstatus=2 then 'Stopped'
                    when @oldstatus=3 then 'Started'
                    END + ' to ' +
                    case
                    when @newstatus=1 then 'Unenlisted'
                    when @newstatus=2 then 'Stopped'
                    when @newstatus=3 then 'Started'
                    END 
                EXEC msdb.dbo.sp_send_dbmail @recipients='person@company.com',
                @subject = @message,
                @body = @message,
                @body_format = 'HTML' ;
  
    --print @message

END
GO

(Did I mention the disclaimer noted on the right side of this blog?)

Unable to get binding type for binding extension sqlBinding

While promoting code from one 64 bit environment to another, I can across the following error while changing the configuration

Unable to get binding type for binding extension "sqlBinding".
Verify the binding extension is registered in machine.config."

Since I have come across this a couple of times, I might as well put a reminder out for myself:

The SQL adapter is a WCF custom binding, which is registered under System.ServiceModel in the machine.config file. A 64-bit platform has two machine.config files, one used by the 32-bit applications and the other used by the 64-bit applications. So, when you install the 64-bit version of the BizTalk Adapter Pack, the setup wizard registers the bindings in the 64-bit version of the machine.config file. However, BizTalk Server Administration console runs as a 32-bit process and hence when you configure a port for the adapter, it checks for the bindings in the 32-bit version of the machine.config file and fails giving an error.

Resolution

Install both the 32-bit and 64-bit versions of the BizTalk Adapter Pack on a 64-bit WCF LOB Adapter SDK installation.

Executing OLEDB SQL statement from C#

Here is a way to execute a sql statement that returns one column of one row. If there is more than one row returned, it will return nothing.

        public static string CreateReader(string connectionString, string queryString)
        {
            string result="";
            int rowCount = 0;
            using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(connectionString))
            {
                System.Data.OleDb.OleDbCommand command = new System.Data.OleDb.OleDbCommand(queryString, connection);
                connection.Open();
                System.Data.OleDb.OleDbDataReader reader = command.ExecuteReader();
                while (reader.Read())
                {
                    rowCount = rowCount + 1;
                    result = reader[0].ToString();
                }
                reader.Close();
                return (rowCount == 0 || rowCount > 1) ? "" : result;
            }
        }

Selecting all rows from a stored procedure

I needed to use a select to return data, however the data resided via a call to a stored procedure.

For a simple example, I will use the the following stored procedure

exec sp_who 'sa'

original.sp_who.results

So how do I select * from a stored a procedure?

Use OPENROWSET

select *
from openrowset('sqloledb','Server=(local);TRUSTED_CONNECTION=YES','set fmtonly off exec master.sys.sp_who ''sa''')

which returns this result

selected.sp_who.results

Which means that I can filter what I want:

select *
from openrowset('sqloledb','Server=(local);TRUSTED_CONNECTION=YES','set fmtonly off exec master.sys.sp_who ''sa''') [x]
where x.status='sleeping'

subselected.sp_who.results

Visual Studio failed to create project

After a recent MS update, when I attempted to create a new project in Visual Studio 2008, on the left side of the status bar, it stated that the creation of the new project failed.

I followed the instructions from developers point and dot net slackers to no avail.

The solution was to simply repair the BizTalk Server 2009 installation.

A co-worker experienced this same problem, and repairing it did not fix it, you need to have C# installed for the BizTalk projects to be created successfully.

WebSphere Transformation Extender vs BizTalk

I am not sure if the war over ‘which product is better’ will ever be over, but the price battle sure has been won:

WebSphere TX

BizTalk

IBM Passport Advantage Express Microsoft BizTalk Server Pricing and Licensing Overview

 

Microsoft sure has made it easier to understand what you are getting, having worked for Mercator in my past, I sure am stumped as to what a client would need to buy.

(Thanks Barry for pointing this out)

Suspended Messages

Here is the stored procedure that needs to placed on the same server (but different database) as the MessageBoxDb. Then you simply have a job that runs this stored procedure (again, it never should need to be turned off)

This will check for new suspended messages every 30 seconds.

CREATE PROCEDURE [dbo].[MonitorBTSMessages] 
AS
BEGIN
    SET NOCOUNT ON;
    while(1=1)
    BEGIN
    WAITFOR DELAY '00:00:30'
        DECLARE @SUSPENDEDMESSAGES INT
        SELECT @SUSPENDEDMESSAGES=COUNT(*)
        FROM [BizTalkMsgBoxDb].[dbo].[InstancesSuspended]
        WHERE DtCreated between DATEADD(s,-30,GETUTCDATE()) and GETUTCDATE()
        IF (@SUSPENDEDMESSAGES>0)
        BEGIN
                declare @thisSubject varchar(100)
                select top 1 @thisSubject='Suspended message on '+  nvcErrorProcessingServer
                FROM [BizTalkMsgBoxDb].[dbo].[InstancesSuspended]
                WHERE DtCreated between DATEADD(s,-30,GETUTCDATE()) and GETUTCDATE()
                and nErrorCategory=0
                Declare @body1 varchar(8000)
                SELECT @body1 = ISNULL(@body1+'Adapter: '+nvcAdapter+' at '+nvcURI+'<br />Description: '+nvcErrorDescription+'<br /><br />','')
                 FROM [BizTalkMsgBoxDb].[dbo].[InstancesSuspended]
                WHERE DtCreated between DATEADD(s,-30,GETUTCDATE()) and GETUTCDATE()
                and nErrorCategory=0
                ORDER BY nvcErrorDescription
                EXEC msdb.dbo.sp_send_dbmail @recipients='support@company.com',
                @copy_recipients ='supervisor@company.com;manager@company.com',
                @subject = @thisSubject,
                @body = @body1,
                @body_format = 'HTML' ;
        END
    END
END

Excel CSV to string[]

While building a Excel CSV to Claim application for a client I needed to take a comma delimited record and place it into a string array.

The challenge is that there is a wrap character only for the fields that have the delimiter in the data.

Example:

012346,012346001,SMITH              ,SCOTT,J
012345,012345001,”WILLIAMS, III                 ”,HENRY                 ,R
012344,012344001,DAHL                ,MICHELLE                      ,T

I could not find an easy way to parse this into an array, this is what I came up with:

public string[] Split(string expression, string delimiter, string qualifier, bool ignoreCase)
{
    string _Statement = String.Format("{0}(?=(?:[^{1}]*{1}[^{1}]*{1})*(?![^{1}]*{1}))",Regex.Escape(delimiter), Regex.Escape(qualifier));

    RegexOptions _Options = RegexOptions.Compiled | RegexOptions.Multiline;
    if (ignoreCase) _Options = _Options | RegexOptions.IgnoreCase;

    Regex _Expression = new Regex(_Statement, _Options);
    ArrayList finalresult=new ArrayList();
    foreach (string result in _Expression.Split(expression))
    {
        string tempData=result.Replace(qualifier, "");
        finalresult.Add(tempData.Trim());
    }
    string[] strArray = finalresult.ToArray(Type.GetType("System.String")) as string[];
    return strArray;
}

I am sure it can be done better, yes, I know, two passes through isn’t quite the best to remove the “ from the quoted field, and I could have done it a little better to remove the spaces, but it does what I needed, and the client was not paying for how fast it runs, he was paying me for how fast I could code it!

Covast HIPAA accelerator (004010A1) to 5010

By popular demand, there are different versions of the conversion tool available, you can purchase one or all of them, whatever you need.

Here is a screen shot of the context menu after installing all conversion plug-ins.

AllVersionsContext

HIPAA conversion maps 4010A1 to 5010 and 5010 to 4010A1

Even though I have created a conversion tool that takes existing maps and change them to the 5010 version with the right click of a mouse button. There is still going to be the need to simply convert the 4010A1 data to the 5010 data, or vise versa from 5010 to 4010A1.

We have developed a series of maps that do the conversion of the data, not a conversion of the maps.

Here are some screen shots of the maps that can be used in orchestrations, or in receive or send ports.

ConversionMaps

Here is two images of it being implemented in an orchestration to convert the data from 4010A1 to 5010

4010Source

5010Destination

You receive the source map files so you can add/modify the mapping logic to fit your business needs:

Old2NewSample

HIPAA 4010A1 to HIPAA 5010 Converter

We have developed a HIPAA 4010A1 to HIPAA 5010 converter for use in Visual Studio 2008.

After installing the converter, the steps to use the converter are as follows:

  1. Place the appropriate 5010 HIPAA schema in the same project as your current 4010 HIPAA schema
    Both schemas in the same project
  2. Compile the schema project
  3. Open up map project that references the HIPAA schema project
  4. Right click on the map that needs to be converted
  5. Choose ‘Convert to 5010’

It is complete.

Things the converter will do:

  • Create a backup of the current map as {map name}_4010.btm and add it to the project
  • Change the type name of the 4010 map to {type_name}.4010

Here are the screen shots of the tool:

MapContext

Don’t worry, you can only convert maps!

NonMapContext

After pressing the Convert to 5010 context menu item the project has the following archived map.

Resulting map that is created

Here is a page from the initial map:

Original 4010 map

Here is the converted map:

Converted 5010 map 

Through the contact page, you can pre order this 5010 converter.

I will not be delivering this however, until Microsoft officially releases the HIPAA 5010 schemas.

BizTalk 2009 Setup

Here are the steps to configure a dual computer setup where SQL Server and BizTalk are hosted on two separate computers.

Steps to configure SQL Server (just the pictures where not the default settings are chosen)

Steps to configure the BizTalk machine after the SQL Server is installed correctly (just the pictures where not the default settings are chosen)

BizTalk Service takes forever to restart

Perhaps you are restarting a host instance, maybe you are deploying, whatever, it takes forever. The problem is really that there is a spike in CPU usage.

CPU Spike

The hot fix can be downloaded here:

http://support.microsoft.com/kb/943165

FIX: A CPU usage spike occurs on all the BizTalk hosts after you install security bulletin MS07-040 on a server that is running Microsoft BizTalk Server 2006 or Microsoft BizTalk Server 2006 R2

Port Authentication with AS2

Don’t ever set port authentication if you are doing AS2, for your love that is all holy.

Event Type:    Error
Event Source:    BizTalk Server 2006
Event Category:    BizTalk Server 2006
Event ID:    5719
Date:        6/13/2009
Time:        12:54:19 AM
User:        N/A
Computer:   
Description:
There was a failure executing the receive pipeline: "Microsoft.BizTalk.EdiInt.DefaultPipelines.AS2EdiReceive, Microsoft.BizTalk.Edi.EdiIntPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "Pipeline " Receive Port: "AS2Receive" URI: "/BTSHTTPReceive.dll" Reason: The party corresponding to the inbound message cannot be identified.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

The fix is to set the receive port to No Authentication

 Port Authentication

AS2 pipeline components can’t run under 64 bit mode

You need to create a 32 bit host and its subsequent host instance, otherwise you will get the following error.

Event Type:    Error
Event Source:    BizTalk Server 2006
Event Category:    BizTalk Server 2006
Event ID:    5754
Date:        6/12/2009
Time:        7:37:50 PM
User:        N/A
Computer:    HAL64
Description:
A message sent to adapter "HTTP" on send port "SendPort4" with URI http://server/BTSHTTPReceive.dll is suspended.
Error details: There was a failure executing the send pipeline: "Microsoft.BizTalk.EdiInt.DefaultPipelines.AS2Send, Microsoft.BizTalk.Edi.EdiIntPipelines, Version=3.0.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Source: "Unknown " Send Port: "SendPort4" URI: http://server/BTSHTTPReceive.dll Reason: Retrieving the COM class factory for component with CLSID {254B4003-2AA7-4C82-BB2E-18BA7F22DCD2} failed due to the following error: 80040154
MessageId:  {9B3DB552-386E-4C46-859F-732AC39E1281}
InstanceID: {24442524-9A98-4778-A29A-93FA21AFC92B}

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

New Send Feature in BTS 2009

I am sure y’all know (yes, I can say that, I am in Texas) that on the send side, there is a new advanced property. I discovered the following new line the administration console:

scheduled service instance admin

After looking into it further, there is a new feature in the advanced options called Schedule

Schedule in send port

I am not sure however, if there is a a way in which you can use this setting dynamically though.

«February»
SunMonTueWedThuFriSat
31123456
78910111213
14151617181920
21222324252627
28123456
78910111213