Auto generate id number in vb.net - vb.net

I am creating a library management system. I want to generate different book code for different book genre. For eg:- There is a combo box with book three book genres(novel,literature,poem) and there is another text box with book code. I want if someone chooses novel in comboBox, book code starts form N001 and if someone chooses literature in comboBox, book code starts from L001 and if someone chooses poem, book code starts from P001.
(My vb application is connected with ms access database.)

I see several problems with your fundamental numbering strategy.
Most library numbering systems (Dewey Decimal/Library of Congress) factor sort ordering abilities to their numbering system and allow for future additions to be able to sort higher without needing to shift the number of all other items. With your incremental strategy, how would you accommodate inserting a book later in the process?
If you set up with three digits of numbering, what would you do once you get 1000 books in a category?
While you could use a magic number table that includes the last id allocated per category and increment it as you add a new book. If you have more than one user, there is a potential for a race condition where two users get the same last number and both try to increment it at the same time landing on the same new Id.
I typically recommend keeping a generic ID that has no meaning in the system. This number would be set as an Auto Number in Access and the database would generate new Ids when records are added and ensure that you don't have the collision issue above. If you absolutely need a publically viewable Id, you can use the magic number strategy for a separate BookNumber column that isn't used for record identity. That way if you have a collision in the future, data won't get corrupted.

there are several schools of thought on how one can approach this.
One way? Well, you say have a book type or some such, and then you have that book code. It would be a simple number column without the prefix (P, B etc.).
So, to get the next number, you would then go:
myBookNumber = DMAX("BookNumber","tblBooks","BookType = 'P')
MyBookNumber = MyBookNumber + 1
So, you simply store the book number separate from the prefix. In forms, reports etc., you can then always display the book number like rst!BookType & rst!BookNumber
Another way, which I prefer is to create a table that allows you to get the next book number This also makes it easy to add more types, and better is such a design lets you set the starting number. This approach is also often used for invoice numbers.
So, you create a table called tlbBookIncrements. It will look like this:
ID: (pk - autonumber)
BookType: text (the one char book type)
BookNumber: Number the next book number to assign.
You then build a function like this:
Public Function GetNextBookN(strBookType As String) As Long
Dim strSQL As String
Dim rst As DAO.Recordset
strSQL = "SELECT * FROM tblBookIncrement where BookType = '" & strBookType & "'"
Set rst = CurrentDb.OpenRecordset(strSQL)
If rst.RecordCount = 0 Then
' this book type does not exist - lets add it and start at 1
rst.AddNew
rst!BookType = strBookType
rst!booknumber = 1
End If
GetNextBookN = rst!booknumber
rst!booknumber = rst!booknumber + 1
rst.Update
rst.Close
End Function
Update:
The poster was using vb.net, not VBA, my bad.
Here is the above as vb.net, it really much the same.
Public Function GetNextBookN(strBookType As String) As Integer
Using mycon As New OleDbConnection(My.Settings.ConnectionString1)
Dim strSQL As String
strSQL = "SELECT * FROM tblBookIncrement where BookType = '" & strBookType & "'"
Using da As New OleDbDataAdapter(strSQL, mycon)
Dim cmdBuilder = New OleDbCommandBuilder(da)
Dim rst As New DataTable
da.Fill(rst)
da.AcceptChangesDuringUpdate = True
With rst
If .Rows.Count = 0 Then
With .Rows.Add
.Item("BookType") = strBookType
.Item("booknumber") = 1
End With
GetNextBookN = 1
Else
GetNextBookN = .Rows(0).Item("booknumber")
.Rows(0).Item("booknumber") += 1
End If
End With
da.Update(rst)
End Using
End Using
End Function
So, now anytime you need a book number, you can go like this:
Say, in a form, you need to assign the book number, you could go like this:
You could say use the on-insert event of the form, and go like this:
me!BookNumber = me!bookType & GetNextBookN(me!bookType)
So, the above would say if the bookType is "P", result in P101 if the numbering table has a book type of P and the booknumber set to 101. And of course once you pull that number, then the handy dandy function will increment the number for you.
And I also added the feature that if the type requested does not exist, then we add the type and start at 1. This way, the code works as you over time add new types of books, and then the book number table will always work, and have available a next book number easily available in code.

Related

update main table from two others with modification of data before updating

I have two tables that I need to update. I have to go through every record on one table then do some modifications to the data then upload the modified
data to another table!
The two tables have 3,000 records and 11,000 records. Plus I also have to
check some info from a third table with about 50 records!
Dim id
Dim fly_SQL
id="user1"
Dim rsc1_conn As ADODB.Connection
Set rsc1_conn = CreateObject("ADODB.Connection")
rsc1_conn.Provider = "SQLOLEDB"
rsc1_conn.ConnectionString = "SERVER=companyserver;UID=" & id &
";Trusted_Connection=Yes;DATABASE=DATAbank" '
rsc1_conn.Open
Set rsc1 = CurrentDb.OpenRecordset("SELECT * FROM main_database",
dbOpenDynaset, dbSeeChanges)
rsc1.movefirst
do until rsc1.EOF
fly_SQL = "Select * from alt_db where alt_db.number = main.net_number"
Set rsc2 = CurrentDb.OpenRecordset(fly_SQL)
do stuff
code = dlookup( "type_def", "third_rec" , alt_db.activity = activity)
The two tables both use net_number as a reference which on the main is primary key unique, but the alt_db has multiple entries.
So basically I have to loop through each net_number on the main, look at the matching net_number on the alt_db then compare an activity field
with a third table to see which field I update on the main! If it's a Project management expense I put it in the main.PM_cost. The net_number in alt_db might repeat for 10 other expenses that need to be funneled into their proper expense categories in the main DB! As an example:
Main table looks like
net_number
first record shows
main.netnumber = 123456
main.cont_cost
main.PM_cost
main.mgmt_cost
alt_db table looks like
alt_db.net_number
alt_db.activity
alt_db.PM_cost
alt_db.const_cost
alt_db.mgmt_cost
third_rec looks like
third_rec.code
third_type
where data can be something like con1 , sabb ,
code type
sauf construction
con1 management
I130 project management
And needed rules:
check alt_db.activity with third_rec.act and return activity type
If activity type is construction then I put the alt_db.cost into main.const_cost
If activity type is project_mgmt then I put the alt_db.cost into main.PM_cost. The alt_db.activity could be con1 or SAF4 and the type is determined by the third_rec table.
Trying to figure out the best (most efficient way) to go about this.
Any suggestions?
The above code will surely be missing proper variable definitions and such but it's just for explaining my dilemma!
I could probably do it with DLookup but I don't think that would be very efficient!
Pete
Best way was to build a query that produces a file filtered data from the Main and alt_db to group the activities by net_number . Then use a case to determine which fields from alt_db to update using the third file and update the result in the proper fields on the main db.

Access SQL Randomizer Not working as intended

I'm using the below mentioned code to select a record ID from an Access Database that wasn't already selected in the last day and add it to an array.
The general goal is that a record that matches the initial "Difficulty" criteria will be retrieved so long as either the record was never selected before OR the record wasn't chosen in the last 2 days. After the loop is done, I should have x amount of unique record ID's and add those onto an array for processing elsewhere.
Private Function RetrieveQuestionID(questionCount As Integer)
' We're using this retrieve the question id's from the database that fit our arrangements.
Dim intQuestArray(0 To questionCount) As Integer
Dim QuestionConnection As New OleDb.OleDbConnection("PROVIDER=Microsoft.ACE.OLEDB.12.0;Data Source = |DataDirectory|\Database\MillionaireDB.accdb;")
QuestionConnection.Open()
For i As Integer = 1 To intNoOfQuestions
'TODO: If there are no valid questions, pull up any of them that meets the difficulty requirement....
Dim QuestionConnectionQuery As New OleDb.OleDbCommand("SELECT Questions.QuestionID FROM Questions WHERE (((Questions.QuestionDifficulty)=[?])) AND (((Questions.LastDateRevealed) Is Null)) OR (Questions.LastDateRevealed >= DateAdd('d',-2,Date())) ORDER BY Rnd((Questions.QuestionID) * Time());", QuestionConnection)
QuestionConnectionQuery.Parameters.AddWithValue("?", intQuestionDifficulty(i - 1).ToString)
Dim QuestionDataAdapter As New OleDb.OleDbDataAdapter(QuestionConnectionQuery)
Dim QuestionDataSet As New DataSet
QuestionDataAdapter.Fill(QuestionDataSet, "Questions")
intQuestArray(i - 1) = QuestionDataSet.Tables("Questions").Rows(0).Item(0)
Dim QuestionConnectionUpdateQuery As New OleDb.OleDbCommand("UPDATE Questions SET Questions.LastDateRevealed = NOW() WHERE Questions.QuestionID = [?]", QuestionConnection)
QuestionConnectionUpdateQuery.Parameters.AddWithValue("?", intQuestArray(i - 1).ToString)
QuestionConnectionUpdateQuery.ExecuteNonQuery()
Next
QuestionConnection.Close()
Return intQuestArray
End Function
However, looping through the array will show that there are records are somehow being repeated even though the record updates during the loop.
Is there another way to loop through the database and pull up these records? I even attempted to move the .Open() and .Close() statements to within the For...Next loop and I'm given worse results than before.
As Steve wrote, the >= should be a < .
In addition, your WHERE clause is missing parentheses around the OR part.
It should be (without all unnecessary parentheses):
SELECT Questions.QuestionID
FROM Questions
WHERE Questions.QuestionDifficulty=[?]
AND ( Questions.LastDateRevealed Is Null
OR Questions.LastDateRevealed < DateAdd('d',-2,Date()) )
ORDER BY Rnd(Questions.QuestionID * Time());
Also have a look at How to get random record from MS Access database - it is suggested to use a negative value as parameter for Rnd().

Unable to read new records from a table

I have a data adapter with 4 tables in a dataset. When I update a new record to the table it appears in the SQL database and the associated datagridivew has been reloaded with the dat in the table, but when I try and read the new record using the following code it can't find the record.
Dim row As DataRow = dsSrvAV.Tables("ServiceAvailability").Select("ID = " & intRecordID).FirstOrDefault()
The same code is used to read other records that were in the database when the application opened, it's just new records that it can't read.
This is the code that writes the new records
Dim newAvailability As DataRow = dsSrvAV.Tables("ServiceAvailability").NewRow()
'Add some data to it
newAvailability("Service_ID") = cboServices.SelectedValue
newAvailability("Date") = Format(dtpDate.Value.ToString, "Short Date")
newAvailability("Downtime") = nudDowntime.Value
newAvailability("Notes") = txtNotes.Text
newAvailability("MajorIncident") = txtMajorIncident.Text
newAvailability("ActionsTaken") = txtActionsTaken.Text
newAvailability("Type") = cboType.SelectedValue
newAvailability("Root_Cause") = txtRootCause.Text
'Add it to the table
dsSrvAV.Tables("ServiceAvailability").Rows.Add(newAvailability)
'Update the adapter
daSrvAv.Update(dsSrvAV, "ServiceAvailability")
dsSrvAV.Tables("ServiceAvailability").AcceptChanges()
Can anyone offer any thoughts as to why this won't allow new records to be read back.
Thanks
Rich
Per comments - this solved the issue.
Close your dsSrvAv dataset, and then re-open it, and then do the select.
Regardng performance: are you adding 1 record per second of 1,000,000. If its 1,000,000 then yes there's an overhead. If its 1 per second there isn't any noticable overhead.

Is it ever okay to violate first normal form?

I have an Access database that will be required to present data with several one-to-many relationships on one row (e.g., it would list items as "a, b, e, f", and I would have multiple columns like that). I know it's a bad idea to store data that way as well, but considering that I'm allowing the user to filter on several of these columns, I can't think of a better way of dealing with the data than violating first normal form.
As an example: say that I have several journal articles, each of which may report on multiple animals and multiple vegetables. The user can filter on the source name, or they can filter on one or more animals and one or more vegetables. The output should look like
Source name....animals...............vegetables
Source 1.......dogs, cats, birds.....carrots, tomatoes
Source 2.......leopards, birds.......tomatoes, zucchini, apples
Source 3.......cats, goldfish........carrots, cucumbers
Typically you would have a separate table with Source name + animal:
Source name......animal
Source 1.........dog
Source 1.........cats
Source 1.........birds
Source 2.........leopards
etc
and a similar table for vegetables. But considering how the data needs to be presented to the user (a comma-separated list), and how the user filters the data (he may filter to only see sources that include dogs and cats, and sources with carrots and tomatoes), I think it makes sense to store the data as comma separated lists for animals and vegetables. With a comma-separated list, when the user selects multiple vegetables and multiple animals, I can say
WHERE (Vegetables like "*carrots*" and Vegetables like "*tomatoes*") AND (Animals like *dogs*" and Animals like "*cats*")
I can't think of an efficient way to do this same kind of query in Access without using a lot of VBA and multiple queries.
You can always construct a scenario in which violating any rule makes sense, so the answer to the question in your title is Yes.
This is not one of those scenarios, however. The searching and presentation issues you raise are common to most one-to-many relationships (or, at least, to many one-to-many relationships) and if this were a reason to violate first normal form then you wouldn't see a lot of normalized databases.
Construct the database correctly and you won't have to worry about commas, search terms embedded in each other, and slow searches due to the lack of indexes. Write a reusable piece of code to perform the comma-separate roll-ups for you so you don't keep reinventing the wheel.
i would still normalize this properly - and then worry about the presentation.
in Oracle - this would be done with a user defined aggregate function.
Why not contruct a Join Table so you can sustain the 1:1 in relationship to field referential integrity. Otherwise you will have to parse out the 1:many field value to then get a referential association so everything works magically (hehehe ;))
When i find myself in need to violate the 1st normal form, the answer 9:10 is to create a Join Table and construct some methodology to produce the desired effect.
Edited: 2012-10-09 9:06AM
This design was in response to an unknown amount of information to be displayed in an unknown amount of column/fields. Although mine is oriented towards numerical values, you could simply develop a vba method to concatenate the information fields to produce a singular field of data.
Table1
gid (Number) <- Table2.id
cid (Number) <- Table3.id
price (Number)
gid MANY <- ONE Table2.id
cid MANY <- ONE Table3.id
crt_CategoryPG
TRANSFORM Sum(Table1.price) AS SumOfprice
SELECT View1.tid, Table1.cid, View1.category
FROM Table2 INNER JOIN (View1 INNER JOIN Table1 ON View1.cid = Table1.cid) ON Table2.gid = Table1.gid
WHERE (((Table2.active)=True) AND ((View1.active)=True))
GROUP BY View1.tid, Table1.cid, View1.category, Table2.active, View1.active
ORDER BY View1.tid, Table1.cid
PIVOT [Table2].[gid] & " - " & [Table2].[nm];
Refresh the CT Table
Public Function RefreshCategoryPricing(Optional sql As String = "")
If HasValue(lstType) Then
Application.Echo False 'Turn off Screen Updating
Dim cttbl As String: cttbl = CreateCTTable("crt_CategoryPG") 'Create Table to store the Cross-Tab information
If IsNullOrEmpty(sql) Then
sql = SQLSelect(cttbl)
End If
Dim flds As DAO.Recordset: Set flds = CurrentDb.OpenRecordset(sql)
Dim fldwd As String, fldhd As String 'Store the Field Width pattern and Field Header Row
fldhd = "-1;-1;Category"
fldwd = "0"";0"";2.5""" 'Handles `tid`, `cid`, and `category` columns in the ListBox
'Assign the number of columns based on the number of fields in CTtable
lstCategoryPG.ColumnCount = flds.Fields.Count
Dim fld As Long
For fld = 3 To (flds.Fields.Count - 1)
fldwd = fldwd & ";.75"""
fldhd = fldhd & ";" & flds.Fields(fld).Name
Next
GC flds
lstCategoryPG.ColumnHeads = True
lstCategoryPG.ColumnWidths = fldwd
sql = SQLSelect(cttbl, , ("tid = " & lstType.Value))
lstCategoryPG.Enabled = True
RefreshControl CurrentDb, lstCategoryPG, sql, , False
lstCategoryPG.AddItem fldhd, 0
Application.Echo True 'Turn on Screen Updating
End If
End Function
Create Cross-Tab Table
'#ct - String value, Source Cross-Tab to base Table design off of
Public Function CreateCTTable(ct As String) As String
Dim tbl As String: tbl = "tbl_" & ct
Dim sql As String
If TableExists(tbl) Then
'Table exists and needs to be dropped
sql = SQLDrop(tbl)
CurrentDb.Execute sql
End If
'Create Table
sql = SQLSelect(ct, "* INTO " & tbl)
CurrentDb.Execute sql
CreateCTTable = tbl
End Function

INSERT INTO increment control field

I'm using MS-ACCESS database.
From the prject I use and made some other questions the table NOEUDS and INFRA (that should be updated):
Table INFRA:
RECNO - NOEUD - SECURISE
00000008 C002 F
00000005 C009 F
00000001 C035 F
00000002 C001 F
00000003 C036 F
00000006 C012 F
00000007 C013 F
TABLE NOEUDS:
NOEUD TYPE_MAT N_AMONT
C021 COF 100
C022 COF 229
C023 COF 130
C002 COF 111
I want to create a query that checks on NOEUDS the nodes C* that are missing inside INFRA table, if not should be inserted a new one.
The problem is the RECNO field that works as a control and can not be duplicated (not primary key because all the DB is only a repositoty for the program that controls it).
All the fields are text so RECNO is a consecutive counting using HEX numbers as shown.
I used the query to select:
SELECT (SELECT MAX(CINT(INFRA.RECNO))+1 AS N FROM INFRA),
NOEUDS.NOEUD, "F" AS Expr2
FROM NOEUDS
WHERE (((NOEUDS.NOEUD) Like "C*"
And (NOEUDS.NOEUD) Not In (SELECT NOEUD FROM INFRA)));
The result was:
9 C021 F
9 C022 F
9 C023 F
SHOULD BE:
9 C021 F
A C022 F
B C023 F
I need some help on this one so I can insert the correct RECNO in hexadecimal counting after 00000019 passes to 0000001A and so on.
thanks in advance
UPDATE 1:
The program we use uses a Access database as storage. When I add a noeud using the program I have to insert some more info using the menus needed for the maps and as built information. The problem is that a lot info is redundant and the program can not handle it automatically. I am trying to work lees and insert the possible information using querys.
Every time I insert a noeud in noeuds table, is needed to insert a line in INFRA table only with RECNO (sequential counting from the last one), the NOEUD and some other info (to complete the autocad table tag). Since I have hundreds of Cxxx, Bxxx, Pxxx, Gxxx equipments I sabe for each project some hour of boring work.
I need help on counting a sequential way of adding RECNO for each NOEUD found in NOEUDS table that will be inserted in INFRA table.
UPDATE 2:
I'm inserting each noeud by hand. Is it possible to join in a way that it takes the list from the noeuds that I want to insert and insead of doing 1 by 1 it takes the list and does in a sequence?
the 2 queries are these:
Equipes I want to add at table INFRA:
SELECT NOEUDS.NOEUD
FROM NOEUDS
WHERE (((NOEUDS.NOEUD) Like "C*" And (NOEUDS.NOEUD) Not In (SELECT NOEUD FROM INFRA)));
Insertion by hand:
INSERT INTO INFRA ( recno, NOEUD, SECURISE )
SELECT (SELECT Right(String(8, "0") & Hex(Max(Val("&H" & RECNO)) + 1), 8) AS N FROM INFRA), NOEUDS.NOEUD, "F" AS Expr2
FROM NOEUDS
WHERE (NOEUDS.NOEUD=[INSERT CHAMBRE?]);
I think a VBA solution should be better than trying to do what you want with only SQL. If you don't have much VBA experience, it could still be achievable because the required VBA should be fairly basic. See if this code outline is enough to get you started.
Public Sub AddToInfra()
Const cstrQuery As String = "qryUnmatchedNoeuds" ' Note 1 '
Dim db As DAO.Database ' Note 2 '
Dim fld As DAO.Field
Dim rsFrom As DAO.Recordset
Dim rsTo As DAO.Recordset
Set db = CurrentDb
Set rsFrom = db.OpenRecordset(cstrQuery, dbOpenSnapshot)
Set rsTo = db.OpenRecordset("infra", dbOpenTable, dbAppendOnly)
Do While Not rsFrom.EOF
rsTo.AddNew
For Each fld In rsFrom.Fields ' Note 3 '
If Not fld.Name = "RECNO" Then
rsTo.Fields(fld.Name).Value = fld.Value
End If
Next fld
rsTo!RECNO = Next_InfraRecno ' Note 4 '
rsTo!SECURISE = "F" ' Note 5 '
rsTo.Update
rsFrom.MoveNext
Loop
rsTo.Close
rsFrom.Close
Set fld = Nothing
Set rsFrom = Nothing
Set rsTo = Nothing
Set db = Nothing
End Sub
Notes:
I used a saved query based on my best guess as to what you want. See the SQL below.
DAO.Database requires a reference to Microsoft DAO Object Library. If your Access version is 2000 or maybe Access XP, you may need to set that reference (from VBE main menu, Tools->References).
I decided the destination table would include fields which match the name and data type of the fields in the source recordset. If that doesn't work for you, substitute something like this for each of the common fields: rsTo!YourFieldNameHere.Value = rsTo!YourFieldNameHere.Value (And drop .Value if you prefer.)
Create a Next_InfraRecno() function to return the next RECNO value. Translate the approach we used earlier into a function. Post a new question if you run into trouble ... show us your code, error message and line which triggers the error (if any), and anything else we need to know. :-)
I got the impression you want SECURISE = "F" for each of the inserted rows.
In a comment you mentioned "Use field ANCIEN for storage of counting". I don't know what's involved for that and hope, whatever it is, you can integrate it into this code outline. If not, sorry. :-(
Here is the SQL for my qryUnmatchedNoeuds query:
SELECT n.DELETED, n.NOEUD
FROM
noeuds AS n
LEFT JOIN infra AS i
ON n.NOEUD = i.NOEUD
WHERE
(((n.NOEUD) Like "c*")
AND ((i.NOEUD) Is Null));
Although I don't understand your question very well, I hope this answer provides something you can use.
My INFRA table has a text column named RECNO. The table contains one row.
RECNO
00000019
This query give me "1A" as N.
SELECT Hex(Max(Val("&H" & RECNO)) + 1) AS N
FROM INFRA;
To pad N with zeros to a width of 8, I can use this query which gives me "0000001A" as N.
SELECT Right(String(8, "0") & Hex(Max(Val("&H" & RECNO)) + 1), 8) AS N
FROM INFRA;
Regarding the rest of your question, my instinct would be to open a recordset containing the unmatched NOEUDS.NOEUD values, then move through the recordset rows and insert each NOEUD value, your custom RECNO sequence number, and the "other info" into the INFRA table.