Some way to create a deterministic computed column that only has to count once?

  • I have a table that has a non unique column called ClientId

    What I need is that every time a new record is inserted a column gets incremented by one so each entry keeps track of which number client it is wthin the table

    the first client inserted has a 1

    the next client with the same client id has a 2

    a different clientid gets inserted it has a 1

    etc.....

    if I did this with a trigger it'd basically be something like this

    select @ClientNumber = MAX(isnull(ClientNumber, 0)) + 1 FROM Site where ClientId = @ClientId

    update SITE SET ClientNumber = @ClientNumber where SiteId = @SiteId

    which is fine and all but in order to do that I have to make clientnumber nullable or have it insert a dummy value

    the other things I thought of were

    -Use a Before Insert Trigger (haven't really used these before not sure the risk involved)

    -use a computed column

    the only issue with a computed column is that I can't figure out for the life of me how to make it persist because I have to use an aggregate that accesses the table data so it just won't fly. Ideally I'd like a computed column cause it's pretty quick and easy and I mean the value in the field will NEVER change so it'd be cool if there was a way to say HEY compute this ONCE on insert than don't ever compute it again...

    Curious if anyone had any other thoughts?

  • Sounds like you just want to use an IDENTITY. Take a look in Books Online.

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • Yea I figure someone was going to answer that I tried to explain it well but I guess I didn't or maybe there's some other identity I need

    To quote myself again

    What I need is that every time a new record is inserted a column gets incremented by one so each entry keeps track of which number client it is wthin the table

    the first client inserted has a 1

    the next client with the same client id has a 2

    a different clientid gets inserted it has a 1

    etc.....

    So the table has an incrementing ID column called SITEID it has another ID called CLIENT ID

    so lets say I insert 4 records with Client ID 2 and 2 records with ClientID 3

    the table would look like this

    SITEID ClientID ClientNum

    1 2 1

    2 2 2

    3 2 3

    4 2 4

    5 3 1

    6 3 2

    what I'm trying to achieve is that ClientNum incrementing that's based off the count of clientids at the given time of the insert

    so the first siteid inserted for client 2 is clientnum 1

    the second siteid inserted for client 2 is clientnum 2

    the first siteid inserted for client 3 is client num 1

    it's not a straight unique identity increment

  • What you're describing is bad design ... it's not normalized and it's going to be confusing and have performance issues for the life of the database. You should have one table that defines site ID's, another table that defines client ID's and another table that establishes the relationship between the two.

    If any given site ID can only possibly be related to a single client ID then you can do away with the third table. In this case the site table would simply have an additional column that is a foreign key for the client ID and that identity you're looking for isn't actually created in that table, but in the client table that's being referenced. If there is a possibility that a site would be related to more than one client it would be necessary to maintain that third table to define the relationships between sites and clients.

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • Since ClientID is not unqiue, then I assume the purpose of this ClientNum is to create a composite primary key. You can persist a calculated column using the PERSISTED clause, and from what I've read, you can even include a persisted calculted column as part of of the primary key. However, that could be problematic and is not necessay.

    Instead, you can just insert your Client table using a select with an inline sub-query that returns the next ClientNum for the ClientID you're inserting.

    It's unclear what purpose SiteID would serve, but if needed, it would just be an additional identity column.

    create table #client

    (

    primary key (clientid, clientnum),

    clientid int not null,

    clientnum int not null,

    SiteID int not null identity(1,1)

    );

    declare @clientid int;

    select @clientid = 2;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select @clientid = 2;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select @clientid = 2;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select @clientid = 2;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select @clientid = 3;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select @clientid = 3;

    insert into #client (clientid,clientnum)

    select @clientid, (select isnull(max(clientnum),0)+1 from #client where clientid = @clientid);

    select SiteID, ClientID, ClientNum from #client;

    drop table #client;

    SiteID ClientID ClientNum

    ----------- ----------- -----------

    1 2 1

    2 2 2

    3 2 3

    4 2 4

    5 3 1

    6 3 2

    "Do not seek to follow in the footsteps of the wise. Instead, seek what they sought." - Matsuo Basho

  • Yea I'm sorry I did such a bad job of setting up the data structures and explaining the scenario

    There's a Client table that has a unique incrementing clientID

    There's a SITE table that has a unique incrementing SiteId

    sites can only belong to one client so it's all one to many

    Client Num is basically just what number of site has been inserted grouped by clientid

    We're using an ORM so I'll have to see if there's a way to inject Max(ClientNum) + 1 into the generated SQL but I'm thinking there isn't

    I'll probably just use an after insert trigger to update the Num

    that seems like the easiest

    I tried to use persisted but there's no way to use MAX or Count or any of those in a column you are indicating as computed and persisted

  • shane94 (5/7/2010)


    so I'll have to see if there's a way to inject Max(ClientNum) + 1 into the generated SQL but I'm thinking there isn't

    I'll probably just use an after insert trigger to update the Num

    that seems like the easiest

    Why not just use an IDENTITY in the client table? I think you're making it much harder than it is.

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • this is a logical mistake; you should never try to keep a counter in a table; the counter should be derived in a view, instead; go with bteraberry's idea and pull everything out into two tables with identity fields, then you can use a view like this to get exactly what you are after, without triggers or wierd code to try and update the clientnum column:

    results:

    SiteStuff clientid SiteID clientstuff ClientNum

    ---------- --------- ------- ------------ --------------------

    one.com 1 1 Manny 1

    one.com 2 1 Moe 2

    one.com 3 1 Jack 3

    one.com 10 1 Larry 4

    one.com 11 1 Shemp 5

    one.com 12 1 Curley-Joe 6

    two.com 4 2 Larry 1

    two.com 5 2 Moe 2

    two.com 6 2 Curley 3

    three.com 7 3 Larry 1

    three.com 8 3 Moe 2

    three.com 9 3 Joe 3

    code:

    Create table Sites(

    SiteID int not null identity(1,1) primary key,

    SiteStuff Varchar(30) )

    create table client (

    clientid int not null identity(1,1) primary key,

    SiteID int references Sites(SiteID),

    clientstuff varchar(30));

    GO

    CREATE VIEW ClientSites

    AS

    Select

    Sites.SiteStuff,

    Client.*,

    Row_Number() OVER (PARTITION BY Sites.SiteID ORDER BY Sites.SiteID,Client.clientid ) as ClientNum

    FROM Sites

    LEFT OUTER JOIN client ON Sites.SiteId = client.SiteId

    GO

    insert into sites (SiteStuff) select 'one.com' union all select 'two.com' union all select 'three.com'

    insert into client(SiteID,clientstuff)

    select 1,'Manny' union all select 1,'Moe' union all select 1,'Jack' union all

    select 2,'Larry' union all select 2,'Moe' union all select 2,'Curley' union all

    select 3,'Larry' union all select 3,'Moe' union all select 3,'Joe' union all

    select 1,'Larry' union all select 1,'Shemp' union all select 1,'Curley-Joe'

    select * from ClientSites

    Lowell


    --help us help you! If you post a question, make sure you include a CREATE TABLE... statement and INSERT INTO... statement into that table to give the volunteers here representative data. with your description of the problem, we can provide a tested, verifiable solution to your question! asking the question the right way gets you a tested answer the fastest way possible!

  • I don't think you're understanding the issue

    Lets say I have some blocks these blocks come in 3 different colors

    Each time I acquire a block that block has it's OWN unique ID but I also want to keep track of what number of block that color is that I received

    SO

    someone gives me a red block

    that block is now

    BLOCK 1 (DB ID only important to the DB)

    RED BLOCK 1 (concept important to the client)

    someone gives me a second red block

    BLOCK 2

    RED BLOCK 2

    someone gives me a blue block

    BLOCK 3

    BLUE BLOCK 1

    someone gives me a green block

    BLOCK 4

    GREEN BLOCK 1

    someone gives me a blue block

    BLOCK 5

    BLUE BLOCK 2

    etc...

    so now when the client is looking at all the blue blocks they will say HEY we want blue block 1

    in the DB this is BLOCK 3 but relative to the count of blue blocks this is blue block 1

    when they are looking at green blocks they will just see

    Green block 1

    Greek Block 2

    etc...

    they won't see that ID of the block in the DB

    but just what number green block has been inserted

    it's a purely for client/user friendly purposes for referring to things

    To do this with an identity column I'd have to create a new table every time I acquired a new color of block which isn't really a reasonable solution

  • Lowell (5/7/2010)


    this is a logical mistake; you should never try to keep a counter in a table; the counter should be derived in a view, isntead; go with bteraberry's idea and pull everything out into two tables with identity fields, then you can use a view like this to get exactly what you are after, without triggers or wierd code to try and update the clientnum column:

    results:

    SiteStuff clientid SiteID clientstuff ClientNum

    ---------- --------- ------- ------------ --------------------

    one.com 1 1 Manny 1

    one.com 2 1 Moe 2

    one.com 3 1 Jack 3

    one.com 10 1 Larry 4

    one.com 11 1 Shemp 5

    one.com 12 1 Curley-Joe 6

    two.com 4 2 Larry 1

    two.com 5 2 Moe 2

    two.com 6 2 Curley 3

    three.com 7 3 Larry 1

    three.com 8 3 Moe 2

    three.com 9 3 Joe 3

    code:

    Create table Sites(

    SiteID int not null identity(1,1) primary key,

    SiteStuff Varchar(30) )

    create table client (

    clientid int not null identity(1,1) primary key,

    SiteID int references Sites(SiteID),

    clientstuff varchar(30));

    GO

    CREATE VIEW ClientSites

    AS

    Select

    Sites.SiteStuff,

    Client.*,

    Row_Number() OVER (PARTITION BY Sites.SiteID ORDER BY Sites.SiteID) as ClientNum

    FROM Sites

    LEFT OUTER JOIN client ON Sites.SiteId = client.SiteId

    insert into sites (SiteStuff) select 'one.com' union all select 'two.com' union all select 'three.com'

    insert into client(SiteID,clientstuff)

    select 1,'Manny' union all select 1,'Moe' union all select 1,'Jack' union all

    select 2,'Larry' union all select 2,'Moe' union all select 2,'Curley' union all

    select 3,'Larry' union all select 3,'Moe' union all select 3,'Joe' union all

    select 1,'Larry' union all select 1,'Shemp' union all select 1,'Curley-Joe'

    select * from ClientSites

    But the solidarity of this seems to be lacking..

    If anything gets deleted than the row numbers all shift also with a trigger I store the number 4 in the DB and it's always the number 4 it's there it's solid it's quick to access there's no joins or counting or views or row_numbers it's just straight up

    select ClientNum FROM Sites where SiteID = 5

    BAM

    And it's an insert trigger that runs once the after math of that is non existent the value just gets created and there's nothing more to worry about ever

  • Again, you're making this way harder than it needs to be. Using triggers to hard code the table when you can simply use the capabilities of T-SQL to query is a terrible idea. For example, here I show how you can get the data you want using your colored block example:

    declare @t_table table

    (BlockINT IDENTITY,

    BlockColorVARCHAR(10),

    BlockIDINT

    )

    insert into @t_table select 'RED', 1

    insert into @t_table select 'RED', 2

    insert into @t_table select 'BLUE', 1

    insert into @t_table select 'GREEN', 1

    insert into @t_table select 'BLUE', 2

    select Block,

    BlockColor,

    BlockID,

    TheValueYouAreTryingToGet = ROW_NUMBER() OVER(PARTITION BY BlockColor ORDER BY Block)

    from @t_table

    order by Block

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • bteraberry (5/7/2010)


    Again, you're making this way harder than it needs to be. Using triggers to hard code the table when you can simply use the capabilities of T-SQL to query is a terrible idea. For example, here I show how you can get the data you want using your colored block example:

    declare @t_table table

    (BlockINT IDENTITY,

    BlockColorVARCHAR(10),

    BlockIDINT

    )

    insert into @t_table select 'RED', 1

    insert into @t_table select 'RED', 2

    insert into @t_table select 'BLUE', 1

    insert into @t_table select 'GREEN', 1

    insert into @t_table select 'BLUE', 2

    select Block,

    BlockColor,

    BlockID,

    TheValueYouAreTryingToGet = ROW_NUMBER() OVER(PARTITION BY BlockColor ORDER BY Block)

    from @t_table

    order by Block

    Yea but for

    insert into @t_table select 'RED', 2

    you're operating off the premise that you know this is red block number 2 each time a block is inserted it doesn't know what number of that color it is unless it checks for the max block number

    I realize that you're using

    TheValueYouAreTryingToGet = ROW_NUMBER() OVER(PARTITION BY BlockColor ORDER BY Block)

    to get the block number but that just seems excessive

    I mean each block always maintains the exact same colored block number it never changes ever so deriving that value every single time I query that table seems like unnecessary cycles on the DB

    red block 2 is always red block 2 so why not just store that in a column as a discrete value than call it good

    I mean if I get to the point where the table has 5 million rows with 1.3 million blocks than in order to get color number i'll have to row number over that whole table instead of just a straight query against a discrete value in a column

    Maybe I'm overstating the performance implications but it just seems like if my options are

    1) a trigger that runs once and inserts a value and now I can forever just query straight against the column

    2) use a view that calculates this exact same unchanging value every time it queries

    I'd rather go with number 1 since it's a done once work forever

    and lets say for SOME reason one of the middle blocks gets deleted that means every single block number after that will get decremented by one

    Blocks shouldn't be deleted but still basing it off a row_number calculations binds me to never being able to delete any rows ever cause it'll screw everything up

  • It's just bad design. (I would suggest doing some reading on normalization.) Basically, unless there is a compelling performance reason to do so, never store data in the table that can be figured out through a query. There are situations where it is necessary for performance reasons, but judging by your questions I am highly doubtful that you are working on such a large and/or complex system to require this. More likely you'll save yourself about 0.005 seconds on your query, but the question is at what cost?

    I work in an extremely large environment and we don't use triggers unless absolutely necessary. Now this is a matter of preference ... other shops use them regularly. One of the issues is that they tend to be harder for other people to support and they can be easily forgotten. Keeping logic in a stored procedure makes it easy for anyone with experience to look and see and figure things out. Triggers, especially when created by people with less experience, can be difficult to troubleshoot and they may not fire exactly when you think they will.

    So when I look at what you're asking, I see a very easy straightforward solution that adheres to solid design principals on one hand and a sort of concocted solution that is dicey at best on the other hand. My impression is that you've presumed the trigger approach from the beginning without really listening to what has been told to you by people with years of experience. You're certainly free to pursue whatever avenue you wish, but do realize that some of us went through trial and error learning on issues such as these in previous decades. We're just trying to help save you some frustration later.

    Good luck to you whatever path you choose.

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • shane94 (5/7/2010)


    bteraberry (5/7/2010)


    I'd rather go with number 1 since it's a done once work forever

    and lets say for SOME reason one of the middle blocks gets deleted that means every single block number after that will get decremented by one

    Blocks shouldn't be deleted but still basing it off a row_number calculations binds me to never being able to delete any rows ever cause it'll screw everything up

    You would be relating entries based on the block ID and not the calculated ROW_NUMBER() column so this would be irrelevant. This would be the case whether you were deleting records (which I don't really like) or using an IsActive bit field. Your block ID would be an IDENTITY and would refer to that record for all time and would never change. It's also the most likely candidate to establish the clustered index, so for all reasons that is the column you would want to join on.

    └> bt



    Forum Etiquette: How to post data/code on a forum to get the best help[/url]

  • I have been listening this is all in the pursuit of knowledge

    And no a colleague was suggesting the triggers and I was trying to find a solution that didn't involve triggers of any sort...

    I mean typically I try to stay away from triggers and even sprocs as much as I possibly can because they create a bad mixing of layers... Which is a completely different discussion 🙂

    Which is where I was trying to get computed columns to work for me...

    I guess I'm just still not totally sold on the idea of

    "Basically, unless there is a compelling performance reason to do so, never store data in the table that can be figured out through a query. "

    From what I've read giving SQL as little as possible to "figure out" is always the preferred approach...

    I mean yea this one tiny detail of figuring out might be asinine but layer after layer after layer of "figuring" out adds up

    It seems odd to make SQL figure out the exact same thing over and over again

    it seems like having a column that I want the number 6 in but instead of just storing a 6 I have SQL pull 2*3 every single time I query that table

    even though I could run a single task that says hey this will always be 6 forever and ever so you don't ever have to figure that out ever again it's just always a 6

Viewing 15 posts - 1 through 15 (of 21 total)

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