Persisted computed column not acting very persisted...

  • The short version of the story is this...

    I have a Column called "BillID" that is a VARCHAR(20) data type. A typical value looks something like this... A6304158-2

    The problem is that the numeric portion between the last alpha character and the dash, is an invoice number and we need to be able join that back to our actual invoice tables.

    So, I had the bright idea to simply write a scalar udf and use that to create a persisted computed column...

    So far so good... I have verified that the column is indeed persisted.

    The problem is that SQL Server is balking at any attempt to include this column in an index.

    AND

    Looking at the execution plan for a newly written query, the UDF is being called rather than simply looking at the persisted value.

    Clearly my understanding of persisted columns is lacking here. I'm hoping that someone may be able to fill in the blanks.

    Would I be better off dropping the computed column and simply use an insert/update trigger to populate this column?

    The function code is below.

    CREATE FUNCTION dbo.GetInvoiceIDFromBillID

    /* ===========================================================

    10/08/2015 JL, Created to strip the InvoiceID out or the BillID

    to be used to create a persisted, computed column on the BlahBlahBlah_PRI table

    =========================================================== */

    (

    @BillID VARCHAR(20)

    )

    RETURNS INT WITH SCHEMABINDING

    AS

    BEGIN

    DECLARE @InvoiceID INT;

    IF TRY_CAST(@BillID AS INT) > 0

    BEGIN

    SET @InvoiceID = @BillID;

    END

    ELSE

    BEGIN

    SELECT @InvoiceID = dbo.CLR_RemoveNonNumeric(sc.Item) FROM dbo.SplitCSVToTable8K(@BillID, '-') sc WHERE sc.ItemNumber = 1;

    END

    RETURN @InvoiceID

    END;

    Note: dbo.CLR_RemoveNonNumeric is a CLRfunction that simply strips out any non-numeric characters and dbo.SplitCSVToTable8K is a renamed version of Jeff's DelimitedSplit8K function.

    The error I get when trying to create an index is as follows:

    Msg 10137, Level 16, State 1, Line 4

    Cannot create index on view "RevenueCycle.dbo.SplitCSVToTable8K" because it references common table expression "E1". Views referencing common table expressions cannot be indexed. Consider not indexing the view, or removing the common table expression from the view definition.

    The statement has been terminated.

    Baffling since "RevenueCycle.dbo.SplitCSVToTable8K" is a iTVF not a view and the fact that I'm attempting to index is persisted.

    Let me know if you required test tables and sample data...

    Thanks in advance,

    Jason

  • Who am I kidding? Test table & data is always required...

    USE RandomTests;

    GO

    CREATE TABLE dbo.PersistedColumnTest (

    BillID VARCHAR(20)

    );

    INSERT dbo.PersistedColumnTest (BillID) VALUES

    ('5345227-2'),('6209822-2'),('6209822-2'),('6721796-2'),('6721796-2'),('6721796-2'),('6721796-2'),('6719008-2'),('6210153-2'),('6203656-2'),

    ('6209822-2'),('6209822-2'),('6700546-2'),('6680192-2'),('6700546-2'),('6619450-2'),('6721796-2'),('6145204-2'),('6145204-2'),('5906198-2'),

    ('7390138'),('7390138'),('7390130'),('7390138'),('7390138'),('7390138'),('7390130'),('7390138'),('7390130'),('7390130'),('7390130'),('7389910'),

    ('7389910'),('7390501'),('7390101'),('7390101'),('7390101'),('7395511'),('7395511'),('7390101'),('A6761374'),('A6761374'),('A6761374'),('A6761374'),

    ('A6760682'),('A6760682'),('A6760682'),('A6760682'),('A6745076'),('A6745076'),('A6745076'),('A6745076'),('A6745076'),('A6745076'),('A6757524'),

    ('A6757524'),('A6757524'),('A6757524'),('A6760741'),('A6762905'),('A6762905'),('A6371075-2'),('A6371075-2'),('A6371075-2'),('A6371075-2'),

    ('A6376568-2'),('A6376568-2'),('A6376568-2'),('A6376568-2'),('A6315422-2'),('A6315422-2'),('A6315422-2'),('A6315422-2'),('A6363316-2'),('A6363316-2'),

    ('A6363316-2'),('A6193029-3'),('A6193029-3'),('A6089801-3'),('A6089801-3'),('A6315423-2');

    GO

    CREATE FUNCTION dbo.GetInvoiceIDFromBillID

    /* ===========================================================

    10/08/2015 JL, Created to strip the InvoiceID out or the BillID

    to be used to create a persisted, computed column on the BlahBlahBlah_PRI table

    =========================================================== */

    (

    @BillID VARCHAR(20)

    )

    RETURNS INT WITH SCHEMABINDING

    AS

    BEGIN

    DECLARE @InvoiceID INT;

    IF TRY_CAST(@BillID AS INT) > 0

    BEGIN

    SET @InvoiceID = @BillID;

    END

    ELSE

    BEGIN

    SELECT @InvoiceID = dbo.CLR_RemoveNonNumeric(sc.Item) FROM dbo.SplitCSVToTable8K(@BillID, '-') sc WHERE sc.ItemNumber = 1;

    END

    RETURN @InvoiceID

    END;

    GO

    ALTER TABLE dbo.PersistedColumnTest ADD InvoiceNumber AS dbo.GetInvoiceIDFromBillID(BillID) PERSISTED;

    GO

    Unfortunately, I don't have direct access to our CLR assemblies, so that part will be tougher to replicate. Sorry about that...

  • Jason A. Long (10/9/2015)


    The short version of the story is this...

    I have a Column called "BillID" that is a VARCHAR(20) data type. A typical value looks something like this... A6304158-2

    The problem is that the numeric portion between the last alpha character and the dash, is an invoice number and we need to be able join that back to our actual invoice tables.

    So, I had the bright idea to simply write a scalar udf and use that to create a persisted computed column...

    So far so good... I have verified that the column is indeed persisted.

    The problem is that SQL Server is balking at any attempt to include this column in an index.

    AND

    Looking at the execution plan for a newly written query, the UDF is being called rather than simply looking at the persisted value.

    Clearly my understanding of persisted columns is lacking here. I'm hoping that someone may be able to fill in the blanks.

    Would I be better off dropping the computed column and simply use an insert/update trigger to populate this column?

    The function code is below.

    CREATE FUNCTION dbo.GetInvoiceIDFromBillID

    /* ===========================================================

    10/08/2015 JL, Created to strip the InvoiceID out or the BillID

    to be used to create a persisted, computed column on the BlahBlahBlah_PRI table

    =========================================================== */

    (

    @BillID VARCHAR(20)

    )

    RETURNS INT WITH SCHEMABINDING

    AS

    BEGIN

    DECLARE @InvoiceID INT;

    IF TRY_CAST(@BillID AS INT) > 0

    BEGIN

    SET @InvoiceID = @BillID;

    END

    ELSE

    BEGIN

    SELECT @InvoiceID = dbo.CLR_RemoveNonNumeric(sc.Item) FROM dbo.SplitCSVToTable8K(@BillID, '-') sc WHERE sc.ItemNumber = 1;

    END

    RETURN @InvoiceID

    END;

    Note: dbo.CLR_RemoveNonNumeric is a CLRfunction that simply strips out any non-numeric characters and dbo.SplitCSVToTable8K is a renamed version of Jeff's DelimitedSplit8K function.

    The error I get when trying to create an index is as follows:

    Msg 10137, Level 16, State 1, Line 4

    Cannot create index on view "RevenueCycle.dbo.SplitCSVToTable8K" because it references common table expression "E1". Views referencing common table expressions cannot be indexed. Consider not indexing the view, or removing the common table expression from the view definition.

    The statement has been terminated.

    Baffling since "RevenueCycle.dbo.SplitCSVToTable8K" is a iTVF not a view and the fact that I'm attempting to index is persisted.

    Let me know if you required test tables and sample data...

    Thanks in advance,

    Jason

    Question, using this as an example: A6304158-2, is there always a single alpha character at the beginning followed by one or more numeric values (0 - 9) followed by a dash then one or more numeric values. Also, you only want the numeric value between the single alpha character and the dash, correct?

  • I found a 'GetNumbers' function here [/url] and that, when combined with Jeff's function, allows you to take the CLR out of the equation ... at least for the data you provide.

    create function dbo.GetNumbers ( @data varchar(8000) )

    returns varchar(8000)

    as

    begin

    return left(

    substring(@Data, patindex('%[0-9.-]%', @data), 8000),

    patindex('%[^0-9.-]%', substring(@Data, patindex('%[0-9.-]%', @data), 8000) + 'X')-1);

    end;

    go

    select BillID ,

    dbo.GetNumbers(split.Item)

    from dbo.PersistedColumnTest

    cross apply dbo.SplitCSVToTable8K(BillID, '-') split

    where split.ItemNumber = 1;

    The absence of evidence is not evidence of absence.
    Martin Rees

    You can lead a horse to water, but a pencil must be lead.
    Stan Laurel

  • Question, using this as an example: A6304158-2, is there always a single alpha character at the beginning followed by one or more numeric values (0 - 9) followed by a dash then one or more numeric values. Also, you only want the numeric value between the single alpha character and the dash, correct?

    No. It can be a straight up numeric value that requires no cleansing, a leading alpha with no dash, a leading alpha with a dash or no leading alpha with the dash.

    So basically 4 different variants (that I've found so far).

  • This might seem weird, but have you tried to recreate the table?

    I kept getting an error which seemed to be replicating your problem saying that the column couldn't be indexed because it used a cte. When I moved the column to the CREATE TABLE statement, the error stopped.

    Luis C.
    General Disclaimer:
    Are you seriously taking the advice and code from someone from the internet without testing it? Do you at least understand it? Or can it easily kill your server?

    How to post data/code on a forum to get the best help: Option 1 / Option 2
  • Phil Parkin (10/9/2015)


    I found a 'GetNumbers' function here [/url] and that, when combined with Jeff's function, allows you to take the CLR out of the equation ... at least for the data you provide.

    Phil - I don't think it's the CLR that's causing the problem. I only mentioned I figured that it would make it difficult for answers to replicate the function in their own test environment. The index error appears to be balking at the splitter function, not the CLR.

    The real question in my mind, why is the function coming into play at all when the column values are persisted?

    What's the point in persisting a value if SQL Server is going to ignore the value and recalculate it on the fly every time it's included in a query?

  • Here's an iTVF version of the query I posted earlier.

    create function dbo.GetNumbers ( @data varchar(8000) )

    returns table

    return

    select nums = left(substring(@Data, patindex('%[0-9.-]%', @data), 8000),

    patindex('%[^0-9.-]%',

    substring(@Data, patindex('%[0-9.-]%', @data),

    8000) + 'X') - 1);

    go

    select BillID ,

    nums.*

    from dbo.PersistedColumnTest

    cross apply dbo.SplitCSVToTable8K(BillID, '-') split

    cross apply dbo.GetNumbers(split.Item) nums

    where split.ItemNumber = 1;

    The absence of evidence is not evidence of absence.
    Martin Rees

    You can lead a horse to water, but a pencil must be lead.
    Stan Laurel

  • Jason A. Long (10/9/2015)


    Phil Parkin (10/9/2015)


    I found a 'GetNumbers' function here [/url] and that, when combined with Jeff's function, allows you to take the CLR out of the equation ... at least for the data you provide.

    Phil - I don't think it's the CLR that's causing the problem. I only mentioned I figured that it would make it difficult for answers to replicate the function in their own test environment. The index error appears to be balking at the splitter function, not the CLR.

    The real question in my mind, why is the function coming into play at all when the column values are persisted?

    What's the point in persisting a value if SQL Server is going to ignore the value and recalculate it on the fly every time it's included in a query?

    Oops. I read 'CTE' and converted it to 'CLR' in my mind. Time to log off and down a beer :hehe:

    The absence of evidence is not evidence of absence.
    Martin Rees

    You can lead a horse to water, but a pencil must be lead.
    Stan Laurel

  • Luis Cazares (10/9/2015)


    This might seem weird, but have you tried to recreate the table?

    I kept getting an error which seemed to be replicating your problem saying that the column couldn't be indexed because it used a cte. When I moved the column to the CREATE TABLE statement, the error stopped.

    Interesting... I'll give that a go in test and see if I get the same results. Thanks Luis!

  • Here's an alternative that will work, but not got around to performance testing yet, sorry.

    CREATE FUNCTION dbo.GetInvoiceIDFromBillID(@Input VARCHAR(20))

    RETURNS VARCHAR(20)

    WITH SCHEMABINDING

    AS

    BEGIN

    -- Function to extract all characters from the first numeric (0-9) up to but not including the first hyphen (-).

    RETURN

    ISNULL((

    SELECT TOP(1) SUBSTRING(@Input, N, CHARINDEX('-', @Input+'-', N)- N) VALUE

    FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)) T(N)

    WHERE SUBSTRING(@Input, N, 20) >= '0' AND SUBSTRING(@Input, N, 20) <= '9'

    ORDER BY N),'');

    END;

    Edit:Change Function Name

    MM



    select geometry::STGeomFromWKB(0x0106000000020000000103000000010000000B0000001000000000000840000000000000003DD8CCCCCCCCCC0840000000000000003DD8CCCCCCCCCC08408014AE47E17AFC3F040000000000104000CDCCCCCCCCEC3F9C999999999913408014AE47E17AFC3F9C99999999991340000000000000003D0000000000001440000000000000003D000000000000144000000000000000400400000000001040000000000000F03F100000000000084000000000000000401000000000000840000000000000003D0103000000010000000B000000000000000000143D000000000000003D009E99999999B93F000000000000003D009E99999999B93F8014AE47E17AFC3F400000000000F03F00CDCCCCCCCCEC3FA06666666666FE3F8014AE47E17AFC3FA06666666666FE3F000000000000003D1800000000000040000000000000003D18000000000000400000000000000040400000000000F03F000000000000F03F000000000000143D0000000000000040000000000000143D000000000000003D, 0);

  • Forum Etiquette: How to post Reporting Services problems
  • [/url]
  • Forum Etiquette: How to post data/code on a forum to get the best help - by Jeff Moden
  • [/url]
  • How to Post Performance Problems - by Gail Shaw
  • [/url]

  • Phil Parkin (10/9/2015)


    Here's an iTVF version of the query I posted earlier.

    Correct me if I'm wrong (a strong possibility), but I don't think we're able to reference TVFs in computed columns.

    If I am wrong, what is the correct syntax?

  • Phil Parkin (10/9/2015)


    Jason A. Long (10/9/2015)


    Phil Parkin (10/9/2015)


    I found a 'GetNumbers' function here [/url] and that, when combined with Jeff's function, allows you to take the CLR out of the equation ... at least for the data you provide.

    Phil - I don't think it's the CLR that's causing the problem. I only mentioned I figured that it would make it difficult for answers to replicate the function in their own test environment. The index error appears to be balking at the splitter function, not the CLR.

    The real question in my mind, why is the function coming into play at all when the column values are persisted?

    What's the point in persisting a value if SQL Server is going to ignore the value and recalculate it on the fly every time it's included in a query?

    Oops. I read 'CTE' and converted it to 'CLR' in my mind. Time to log off and down a beer :hehe:

    LOL... No worries. I appreciate you (and everyone else) taking the time to look at this with me. 🙂

  • Jason A. Long (10/9/2015)


    Phil Parkin (10/9/2015)


    Here's an iTVF version of the query I posted earlier.

    Correct me if I'm wrong (a strong possibility), but I don't think we're able to reference TVFs in computed columns.

    If I am wrong, what is the correct syntax?

    I didn't check that. As you are already referencing dbo.SplitCSVToTable8K, I assumed it was OK.

    The absence of evidence is not evidence of absence.
    Martin Rees

    You can lead a horse to water, but a pencil must be lead.
    Stan Laurel

  • mister.magoo (10/9/2015)


    Here's an alternative that will work, but not got around to performance testing yet, sorry.

    CREATE FUNCTION dbo.GetInvoiceIDFromBillID(@Input VARCHAR(20))

    RETURNS VARCHAR(20)

    WITH SCHEMABINDING

    AS

    BEGIN

    -- Function to extract all characters from the first numeric (0-9) up to but not including the first hyphen (-).

    RETURN

    ISNULL((

    SELECT TOP(1) SUBSTRING(@Input, N, CHARINDEX('-', @Input+'-', N)- N) VALUE

    FROM (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16),(17),(18),(19),(20)) T(N)

    WHERE SUBSTRING(@Input, N, 20) >= '0' AND SUBSTRING(@Input, N, 20) <= '9'

    ORDER BY N),'');

    END;

    Edit:Change Function Name

    Absolute genius Magoo!

    A simple inline tally split that stops on the dash... No deed for cross applied CTEs because we know the max length of 20 in advance. Simple and brilliant.

    I'll replace my existing function and test before I attempt to recreate the entire table with the function in the table definition.

    I have a meeting right now but I'll test as soon as that over and post the results.

  • Viewing 15 posts - 1 through 15 (of 20 total)

    You must be logged in to reply to this topic. Login to reply