Sql-server – Update incoming rows based on % calculation

sql-server-2008

Below is the business requirement which I have to achieve…

As a Sales Data Operations process/product owner, I want to be able to identify share class conversions (aka "wash trades") coded as purchases and redemption and research whether the transaction types of the trades need to be updated to exchanges in and exchanges out, respectively.

Share class conversion ("wash trades") is defined as a purchase and redemption trade where the system source, account number, product name, and settle date match and the trade amount is within +/- 5% threshold. The scope pertains to non-omnibus trades.

Rule:

  1. If 'Channel' is not equal to "Omnibus" where the 'Trans Description' is equal to "Purchase" and "Redemption" for one purchase and one redemption that match on 'System' , 'Account TA Number' , 'Product Name' , 'Settled Date' , and where the 'Trade Amount' of the purchase and redemption is within 5%, then display the potential wash trades on the dashboard for NB Sales Data Analyst review.

— Display the above set of records

  1. If deemed wash trades, allow user to update the purchase and redemption pair 'Transaction Code Override Description' from "Purchase" to "Exchange In" and 'Trans Description' from "Redemption" with "Exchange Out".

— In case business agrees that they are washed, then I have to update those records.

CREATE TABLE [dbo].[fact](
[System] [nvarchar](255) NULL,
[Channel] [nvarchar](255) NULL,
[Product Name] [nvarchar](255) NULL,
[Product Share Class] [nvarchar](255) NULL,
[Trade ID] [float] NULL,
[Settled Date] [datetime] NULL,
[Account TA Number] [nvarchar](50) NULL,
[Trans Description] [nvarchar](255) NULL,
[Trade Amount] [money] NULL,
) ON [PRIMARY]

INSERT INTO [SRDB].[dbo].[fact]
           ([System]
           ,[Channel]
           ,[Product Name]
           ,[Product Share Class]
           ,[Trade ID]
           ,[Settled Date]
           ,[Account TA Number]
           ,[Trans Description]
           ,[Trade Amount]
        )
     VALUES
           ('SCHWAB'
           ,'US-ASG'
           ,'US MF'
           ,'Trust'
           ,'123456'
           ,'2015-01-30'
           ,'1111111'
           ,'Purchase'
           ,$ 2568,458.15)
GO

Sample Data Set.

System  Channel Product Name    Product Share Class Trade ID    Settled Date    Account TA Number   Trans Description    Trade Amount 
SCHWABPORTAL    US - ASG    Strategic Income Fund   A   29806259    30-Jan-15   000BY00F2RW Redemption   $        2,568,458.15
SCHWABPORTAL    US - ASG    Strategic Income Fund   Institutional   29806263    30-Jan-15   000BY00F2RW Purchase     $        2,568,488.04
DST             US - ASG    Socially Responsive Fund    Institutional   29711418    21-Jan-15   8010000418  Redemption   $        9,982,249.13
DST             US - ASG    Socially Responsive Fund    R6  29712099    21-Jan-15   1283100037  Purchase     $      10,057,328.32
DST             US - ASG    Socially Responsive Fund    Trust   29459917    2-Jan-15    5870002374  Redemption   $      13,985,707.98
DST             US - ASG    Socially Responsive Fund    Institutional   29459703    2-Jan-15    8010000338  Purchase     $      13,296,637.40
SCHWABPORTAL    US - ASG    High Income Bond Fund   Institutional   25615910    16-Jan-14   000BY00F351 Redemption   $      83,711,868.09
SCHWABPORTAL    US - ASG    High Income Bond Fund   R6  25615963    16-Jan-14   000BY00F351 Purchase     $      83,711,868.09

Desired Result Set

System  Channel Product Name    Product Share Class Trade ID    Settled Date    Account TA Number   Trans Description    Trade Amount   Wash Trades
SCHWABPORTAL    US - ASG    Strategic Income Fund   A   29806259    30-Jan-15   000BY00F2RW Redemption   $        2,568,458.15  Yes
SCHWABPORTAL    US - ASG    Strategic Income Fund   Institutional   29806263    30-Jan-15   000BY00F2RW Purchase     $        2,568,488.04  Yes
DST US - ASG    Socially Responsive Fund    Institutional   29711418    21-Jan-15   8010000418  Redemption   $        9,982,249.13  NO
DST US - ASG    Socially Responsive Fund    R6  29712099    21-Jan-15   1283100037  Purchase     $      10,057,328.32   NO
DST US - ASG    Socially Responsive Fund    Trust   29459917    2-Jan-15    5870002374  Redemption   $      13,985,707.98   NO
DST US - ASG    Socially Responsive Fund    Institutional   29459703    2-Jan-15    8010000338  Purchase     $      13,296,637.40   NO
SCHWABPORTAL    US - ASG    High Income Bond Fund   Institutional   25615910    16-Jan-14   000BY00F351 Redemption   $      83,711,868.09   Yes
SCHWABPORTAL    US - ASG    High Income Bond Fund   R6  25615963    16-Jan-14   000BY00F351 Purchase     $      83,711,868.09   Yes

Best Answer

The desired output can be achieved using a union of recursive joins on the same table. It's a bit ugly, but the following query will do it. The first SELECT returns Purchase transactions, the second SELECT returns Redemption transactions, and the third SELECT returns the rows that aren't part of a transaction pair.

SELECT
    fact.[System]
    ,fact.[Channel]
    ,fact.[Product Name]
    ,fact.[Product Share Class]
    ,fact.[Trade ID]
    ,fact.[Settled Date]
    ,fact.[Account TA Number]
    ,fact.[Trans Description]
    ,fact.[Trade Amount]
    ,CASE WHEN rfact.[Trade Amount]/fact.[Trade Amount] BETWEEN 0.95 AND 1.05 THEN 'Yes' ELSE 'No' END
        AS [Wash Trades]
FROM [dbo].fact
INNER JOIN [dbo].fact AS rfact
ON fact.[System] = rfact.[System]
AND fact.[Account TA Number] = rfact.[Account TA Number]
AND fact.[Product Name] = rfact.[Product Name]
AND fact.[Settled Date] = rfact.[Settled Date]
WHERE fact.[Channel] != 'Omnibus'
AND rfact.[Channel] != 'Omnibus'
AND fact.[Trans Description] = 'Purchase'
AND rfact.[Trans Description] = 'Redemption'
UNION ALL
SELECT
    fact.[System]
    ,fact.[Channel]
    ,fact.[Product Name]
    ,fact.[Product Share Class]
    ,fact.[Trade ID]
    ,fact.[Settled Date]
    ,fact.[Account TA Number]
    ,fact.[Trans Description]
    ,fact.[Trade Amount]
    ,CASE WHEN pfact.[Trade Amount]/fact.[Trade Amount] BETWEEN 0.95 AND 1.05 THEN 'Yes' ELSE 'No' END
        AS [Wash Trades]
FROM [dbo].fact
INNER JOIN [dbo].fact AS pfact
ON pfact.[System] = fact.[System]
AND pfact.[Account TA Number] = fact.[Account TA Number]
AND pfact.[Product Name] = fact.[Product Name]
AND pfact.[Settled Date] = fact.[Settled Date]
WHERE fact.[Channel] != 'Omnibus'
AND pfact.[Channel] != 'Omnibus'
AND fact.[Trans Description] = 'Redemption'
AND pfact.[Trans Description] = 'Purchase'
UNION ALL
SELECT
    fact.[System]
    ,fact.[Channel]
    ,fact.[Product Name]
    ,fact.[Product Share Class]
    ,fact.[Trade ID]
    ,fact.[Settled Date]
    ,fact.[Account TA Number]
    ,fact.[Trans Description]
    ,fact.[Trade Amount]
    ,'No' AS [Wash Trades]
FROM [dbo].fact
WHERE fact.[Trade ID] IN (
    SELECT
        MAX(fact.[Trade ID]) AS [Trade ID]
    FROM [dbo].fact
    GROUP BY fact.[Account TA Number], fact.[Settled Date]
    HAVING COUNT(fact.[Trade ID]) = 1 )
AND fact.[Channel] != 'Omnibus'
ORDER BY [Account TA Number], [Trans Description]

Result set:

System  Channel Product Name    Product Share Class Trade ID    Settled Date    Account TA Number   Trans Description   Trade Amount    Wash Trades
SCHWABPORTAL    US - ASG    Strategic Income Fund   Institutional   29806263    2015-01-30 00:00:00.000 000BY00F2RW Purchase    2568488.04  Yes
SCHWABPORTAL    US - ASG    Strategic Income Fund   A   29806259    2015-01-30 00:00:00.000 000BY00F2RW Redemption  2568458.15  Yes
SCHWABPORTAL    US - ASG    High Income Bond Fund   R6  25615963    2014-01-16 00:00:00.000 000BY00F351 Purchase    83711868.09 Yes
SCHWABPORTAL    US - ASG    High Income Bond Fund   Institutional   25615910    2014-01-16 00:00:00.000 000BY00F351 Redemption  83711868.09 Yes
DST US - ASG    Socially Responsive Fund    R6  29712099    2015-01-21 00:00:00.000 1283100037  Purchase    10057328.32 No
DST US - ASG    Socially Responsive Fund    Trust   29459917    2015-01-02 00:00:00.000 5870002374  Redemption  13985707.98 No
DST US - ASG    Socially Responsive Fund    Institutional   29459703    2015-01-02 00:00:00.000 8010000338  Purchase    13296637.40 No
DST US - ASG    Socially Responsive Fund    Institutional   29711418    2015-01-21 00:00:00.000 8010000418  Redemption  9982249.13  No

This is only my opinion, but I find that viewing data in pairs or groups of rows pyschologically separates the transactions and makes them more difficult to comprehend. If I were viewing this data in a dashboard, I'd like it to be as concise as possible with one row per pair of transactions giving all the necessary details of both. That can be accomplished with the following, which uses a CTE to select Purchases and joins back to the fact table selecting Redemptions:

WITH Purchases AS (
SELECT
    fact.[System]
    ,fact.[Channel]
    ,fact.[Product Name]
    ,fact.[Product Share Class]
    ,fact.[Trade ID] AS [Purchase ID]
    ,fact.[Settled Date]
    ,fact.[Account TA Number]
    ,fact.[Trade Amount] AS [Purchase Amount]
FROM [dbo].fact
WHERE [Channel] != 'Omnibus'
AND [Trans Description] = 'Purchase' )

SELECT
    pfact.[Purchase ID]
    ,rfact.[Trade ID] AS [Redemption ID]    
    ,rfact.[System]
    ,rfact.[Channel]
    ,rfact.[Product Name]
    ,pfact.[Product Share Class] AS [Purchase Share Class]
    ,rfact.[Product Share Class] AS [Redemption Share Class]
    ,rfact.[Settled Date]
    ,rfact.[Account TA Number]
    ,pfact.[Purchase Amount]
    ,rfact.[Trade Amount] AS [Redemption Amount]
    ,pfact.[Purchase Amount]/rfact.[Trade Amount] AS [Transaction Ratio]
FROM [dbo].fact AS rfact
INNER JOIN Purchases AS pfact
ON pfact.[System] = rfact.[System]
AND pfact.[Account TA Number] = rfact.[Account TA Number]
AND pfact.[Product Name] = rfact.[Product Name]
AND pfact.[Settled Date] = rfact.[Settled Date]
AND pfact.[Purchase Amount]/rfact.[Trade Amount] BETWEEN 0.95 AND 1.05
WHERE rfact.[Channel] != 'Omnibus'
AND rfact.[Trans Description] = 'Redemption'

This returns only the trades that meet the requirements for a "wash trade", with each pair as a single row but all necessary data for the update. Output below, formatted with extra tabs for readability:

Purchase ID Redemption ID   System          Channel     Product Name            Purchase Share Class    Redemption Share Class  Settled Date            Account TA Number   Purchase Amount     Redemption Amount   Transaction Ratio
29806263    29806259        SCHWABPORTAL    US - ASG    Strategic Income Fund   Institutional           A                       2015-01-30 00:00:00.000 000BY00F2RW         2568488.04          2568458.15          1.00
25615963    25615910        SCHWABPORTAL    US - ASG    High Income Bond Fund   R6                      Institutional           2014-01-16 00:00:00.000 000BY00F351         83711868.09         83711868.09         1.00