2

How can I generate counter field like this 0001A, 0002A... becouse in standart it is 0,1,2,3,4.... how to change this?

DPS
  • 87
  • 3
  • 9
  • 4
    A counter field is a number and is stored as such. Can't you just add an 'A' in whatever place you display this field? – GolezTrol Oct 25 '11 at 19:27
  • 2
    Why would you do that ? If there's no real justification, just don't do it that way. And "we are used to do it like that" is NOT a justification :) – iDevlop Oct 25 '11 at 20:10

3 Answers3

2

The simplest solution would be to use a standard autonumber field (long integer). Let Access maintain those values for you. Then anytime you need those values in your "0001A" format, use the Format() function to add the leading zeros, and concatenate an "A".

This is trivially easy. If your autonumber field is named ID, you could do that transformation with this query:

SELECT Format(ID, "0000") & "A" AS formatted_ID
FROM YourTable;

Similarly you can apply the same expression to the control source property of a text box on a form or report.

HansUp
  • 95,961
  • 11
  • 77
  • 135
2

Adding to @HansUp's excellent answer, you could hide the IDENTITY column and at the same time expose the formatted column using a SQL VIEW: you could then revoke privileges on the table so that users work with the VIEW and do not 'see' the table e.g. demo:

copy+paste into any VBA module, no references nor Access UI/object model required, creates a new mdb in the temp folder e.g. use Excel:

Sub YourView2()

  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  On Error GoTo 0

  Dim cat
  Set cat = CreateObject("ADOX.Catalog")

  With cat
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMe.mdb"

    With .ActiveConnection

      Dim Sql As String

      Sql = _
      "CREATE TABLE YourTable ( " & _
      "ID INTEGER IDENTITY(1, 1) NOT NULL UNIQUE, " & _
      "data_col VARCHAR(20) NOT NULL);"
      .Execute Sql

      Sql = _
      "CREATE VIEW YourView AS " & _
      "SELECT FORMAT$(ID, '0000') & 'A' AS formatted_ID, " & vbCr & _
      "       data_col " & vbCr & _
      "  FROM YourTable;"
      .Execute Sql

      Sql = _
      "INSERT INTO YourView (data_col) VALUES ('one');"
      .Execute Sql      
      Sql = _
      "INSERT INTO YourView (data_col) VALUES ('day');"
      .Execute Sql      
      Sql = _
      "INSERT INTO YourView (data_col) VALUES ('when');"
      .Execute Sql      

      Sql = "SELECT * FROM YourView;"

      Dim rs
      Set rs = .Execute(Sql)
      MsgBox rs.GetString

    End With
    Set .ActiveConnection = Nothing
  End With
End Sub

I think this one would be even better if you include DDL GRANT/REVOKE samples to manage the privileges

Here's the updated code to do just that:

Sub YourView2()

  On Error Resume Next
  Kill Environ$("temp") & "\DropMe.mdb"
  Kill Environ$("temp") & "\DropMeToo.mdw"
  On Error GoTo 0

  ' Create workgroup and db
  Dim cat As ADOX.Catalog
  Set cat = CreateObject("ADOX.Catalog")
  With cat
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Jet OLEDB:Engine Type=4;" & _
        "Data Source=" & _
        Environ$("temp") & "\DropMeToo.mdw;" & _
        "Jet OLEDB:Create System Database=-1"
    .Create _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Jet OLEDB:Engine Type=4;" & _
      "Data Source=" & _
      Environ$("temp") & "\DropMe.mdb;" & _
      "Jet OLEDB:System Database=" & _
      Environ$("temp") & "\DropMeToo.mdw;"

    ' Add table with data and user with privileges
    With .ActiveConnection

      Dim Sql As String

      Sql = _
      "CREATE TABLE YourTable ( " & _
      "ID INTEGER IDENTITY(1, 1) NOT NULL UNIQUE, " & _
      "data_col VARCHAR(20) NOT NULL);"
      .Execute Sql

      Sql = _
      "CREATE VIEW YourView AS " & _
      "SELECT FORMAT$(ID, '0000') & 'A' AS formatted_ID, " & vbCr & _
      "       data_col " & vbCr & _
      "  FROM YourTable WITH OWNERACCESS OPTION;"
      .Execute Sql

      .Execute "CREATE USER onedaywhen pwd Chri5tma5;"
      .Execute "GRANT ALL PRIVILEGES ON YourView TO onedaywhen;"
      .Execute "REVOKE ALL PRIVILEGES ON YourTable FROM onedaywhen;"

    End With
  End With

  ' Test user can connect
  Dim con As ADODB.Connection
  Set con = New ADODB.Connection
  With con
    .ConnectionString = _
      "Provider=Microsoft.Jet.OLEDB.4.0;" & _
      "Jet OLEDB:Engine Type=4;" & _
      "Data Source=" & _
      Environ$("temp") & "\DropMe.mdb;" & _
      "Jet OLEDB:System Database=" & _
      Environ$("temp") & "\DropMeToo.mdw;" & _
      "User ID=onedaywhen;Password=pwd;"
    .Open

    On Error Resume Next

    ' Attempt to insert to table (no privileges)
    Sql = _
    "INSERT INTO YourTable (data_col) VALUES ('one');"
    .Execute Sql
    If Err.Number <> 0 Then
      MsgBox _
          Err.Number & ": " & _
          Err.Description & _
          " (" & Err.Source & ")"
    End If
    On Error GoTo 0

    Dim rs

    On Error Resume Next

    ' Attempt to read table (no privileges)
    Sql = _
    "SELECT * FROM YourTable;"
    Set rs = .Execute(Sql)
    If Err.Number <> 0 Then
      MsgBox _
          Err.Number & ": " & _
          Err.Description & _
          " (" & Err.Source & ")"
    End If
    On Error GoTo 0

    ' From here, work only with VIEW
    Sql = _
    "INSERT INTO YourView (data_col) VALUES ('one');"
    .Execute Sql

    Sql = _
    "INSERT INTO YourView (data_col) VALUES ('day');"
    .Execute Sql

    Sql = _
    "INSERT INTO YourView (data_col) VALUES ('when');"
    .Execute Sql

    Sql = "SELECT * FROM YourView;"

    Set rs = .Execute(Sql)
    MsgBox rs.GetString

    Set con = Nothing
  End With
End Sub
onedaywhen
  • 55,269
  • 12
  • 100
  • 138
  • +1 But why FORMAT$() instead of FORMAT() in the CREATE VIEW? Either way, it would return "A" as formatted_ID for Null ID. But the DDL includes NOT NULL constraint, so is there an issue here which makes FORMAT$() the superior choice? – HansUp Oct 26 '11 at 16:23
  • I appreciate your detailed examples. And I think this one would be even better if you include DDL GRANT/REVOKE samples to manage the privileges. http://msdn.microsoft.com/en-us/library/bb177904(office.12).aspx – HansUp Oct 26 '11 at 16:27
  • 1
    @HansUp "why FORMAT$() instead of FORMAT()...?" -- the difference _in VBA_ is that `FORMAT()` (no dollar sign) returns a `Variant`, which has no meaning in Access SQL e.g. there is no value of `x` that would make `SELECT TYPENAME(x) AS result;` return 'Variant'. Therefore, it makes more sense to me to use `FORMAT$()` (with dollar sign) that returns a `String` in VBA e.g. `SELECT 'onedaywhen' AS x, TYPENAME(x) AS result;` also returns 'String' in Access SQL. – onedaywhen Oct 27 '11 at 07:47
  • Thanks for your generosity, @onedaywhen – HansUp Oct 27 '11 at 17:15
1

One solution, that works for forms only!

  1. create a GetId() function that calculates your counter (using DMax generally)
  2. Use the Before insert event in your form to set the value of the field using GetId()

Drawback: in a multiuser environment, if another User2 starts addind a record after User1, but saves it before User1 saves his, there will be a duplicate problem. You will need to use the FormError event to regenerate the ID and resume the save process.

iDevlop
  • 24,841
  • 11
  • 90
  • 149
  • Yeah, I use this method. What I actually do is set the ID field to show "UNSET" until the BeforeUpdate event runs. Only then do I get the next available ID. What happens with the method you suggested is users will write down the ID Number they see on their paper records and not realize that it gets changed when the save finally occurs. – HK1 Oct 25 '11 at 20:39
  • I don't think you can't avoid running circles here. You can't "book" a number until it's saved. Or you could have a "booking" table where you insert your reserved number as soon as the user starts to type, and that you could clean up when a booking is older than 2 hrs or so ? – iDevlop Oct 25 '11 at 21:11
  • 1
    The Access Cookbook includes *Recipe 6.7 Create and Use Flexible AutoNumber Fields*, which is a somewhat related approach. It uses locking on a table to manage contention in a multi-user context. http://etutorials.org/Microsoft+Products/access/Chapter+6.+Data/Recipe+6.7+Create+and+Use+Flexible+AutoNumber+Fields/ – HansUp Oct 26 '11 at 16:36
  • 1
    @HansUp: +1, great link. I think I have this book somewhere, it's a goldmine ! – iDevlop Oct 26 '11 at 18:21
  • @HansUp: can be done without forms e.g. using ADO code: "Because the Microsoft Jet database engine has a read cache and lazy writes, you can get duplicate values in your custom counter field if two applications add records in less time than it takes for the cache to refresh and the lazy-write mechanism to flush to disk. [This article](http://support.microsoft.com/kb/240317/en-us) presents a method that takes these factors into account." – onedaywhen Oct 27 '11 at 21:13
  • @onedaywhen Thank you; that was interesting. Do you recommend using JRO? – HansUp Oct 28 '11 at 17:11
  • @HansUp: JRO is one of the libraries of ADO and IIRC I've only ever used it to call its `RefreshCache` method using an ADO Connection object. So my recommendation would be in line with [my general view on ADO vs DAO](http://stackoverflow.com/questions/1039224/is-it-better-to-use-ado-or-dao-in-access-2007/1041446#1041446). I guess DAO is sure to have an equivalent of ADO's 'RefreshCache` but does DAO have equivalents for `Transaction Commit Mode = Immediate` and `Lock Delay` properties? – onedaywhen Oct 31 '11 at 08:32
  • @onedaywhen I avoided JRO because I had the impression it was deprecated, but not sure where I got that impression. I don't know about DAO equivalents for those ADO features. If they exist, I haven't used them. – HansUp Oct 31 '11 at 14:27
  • @HansUp: replication in general was removed from the engine with effect from ACE 2007, may that was what you were recalling ;) – onedaywhen Oct 31 '11 at 16:28