Inserting into SQL Server table with GUID pk from MS Access - sql-server

I'm in the process of moving our DB from an Access backend to a SQL Server backend while keeping the Access front end. One of the tables was set up with a replication ID for its PK. As an access DB, we could insert values into this table without referencing the PK field, as Access automatically generates a new GUID.
We have a function that builds a record to insert into this table that no longer works with the linked SQL Server version of the table and I'm pretty sure it must has something to do with this replcationID field. In SQL Server, it imported as data type 'uniqueidentifier'. When the function runs, it return no errors, but also inserts nothing.
I'd like to know if there is a way to get this working without changing it to a passthrough. How do I get MS Access to tell SQL Server to generate a new GUID when inserting a new record.
As a side note, the PK field is called PurchaseID and is not referenced currently in the vba (since it used to auto generate for this field)
Public Sub BuildReorderRecord(Market As String, MPID As String, FloorsetID As String)
Dim values As String
Dim sql As String
Dim P3ID As String
Dim Username As String
Dim dt As String
Dim db As Database
Dim rs As Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT ID FROM tblAttributes WHERE MPID = """ & MPID & """")
'Get P3ID and auditing values
dt = Now()
Username = Environ("USERNAME")
P3ID = rs![ID]
'build record values string
values = """" & P3ID & """, """ & Market & """, """ & MPID & "', """ & FloorsetID & """"
values = values & ", #" & dt & "#, """ & Username & """, #" & dt & "#, """ & Username & """"
'build sql string
sql = "INSERT INTO tblReorders (P3ID, country, mpid, floorsetID"
sql = sql & ", CreateDate, CreatorUsername, ChangeRecord_Timestamp, ChangeRecord_Username)" & vbNewLine
sql = sql & "VALUES(" & values & ")"
'insert the new record
db.Execute sql
Set db = Nothing
Set rs = Nothing
End Sub

You could add a default constraint on the table for that column in sql server like so:
alter table t
add constraint [df_t_guid]
default newid() for [guid];
Or you could use a custom function in access to generate a guid like stguidgen()
insert into ... (guid...
values (stguidgen(), ...)
With a pass-through query, you could use newid() which generates a new uniqueidentifier in sql server.
insert into ... (guid...
values (newid(), ...)

Related

How would I obtain the primary key of a newly inserted table row in a SQL Database from Access VBA?

I have a SQL Server database with a MS Access frontend.
I need to obtain the primary key of a newly inserted row in a SQL Server database table.
I have tried SCOPE_INDENTITY() and ##IDENTITY without success.
I have this Department table consisting of two columns:
ID - primary key identity column
DeptName of type NVARCHAR(255)
This is an example of the VBA code:
Dim idRecordset As Recordset
Dim lastId as Integer
DoCmd.RunSql "INSERT INTO Departments (DeptName) VALUES ('Department A')
Set idRecordset = CurrentDb.OpenRecordset("SELECT ##IDENTITY")
lastId = idRecordSet.Fields(0).Value
idRecordset.Close
Set idRecordset = Nothing
The INSERT statement succeeds. A new row for 'Department A' is added to the Departments table
SELECT SCOPE_IDENTITY() throws a no such column error.
SELECT ##IDENTITY always returns 0.
Questions and notes:
Can SELECT ##IDENTITY be used from Access VBA?
I will try creating a SQL Server stored procedure and see if it can be called from Access VBA
You can achieve this by using ADO and directly querying SQL server.
I prefer using the OUTPUT clause over scope_identity() since it handles nonidentity calculated defaults as well, but both should work fine.
Dim rs As ADODB.Recordset
Dim conn As New ADODB.Connection
conn.Open "Some connection string to SQL server"
Set rs = conn.Execute("DECLARE #NewID table(NewId Integer);" & _
"INSERT INTO dbo.Departments (DeptName) " & _
"OUTPUT Inserted.Id INTO #NewID VALUES ('Department A'); " & _
"SELECT * FROM #NewID")
LastId = rs.Fields(0).Value
You may prefer to separately execute the query and then opening a recordset using SELECT scope_identity(), which saves you from that long SQL clause, but adds an ever-so-slight delay since it adds a trip to the server.

Access Upsizing - Property 'Attributes' already exists for 'table'

I'm trying to use Access's upsizing wizard to move the data from Access to SQL Server. I'm currently on this error but can't figure out where the property is coming from;
Property 'Attributes' already exists for 'table'.
The SQL its trying to run is;
EXEC sp_addextendedproperty N'Attributes', N'2', N'user', N'dbo', N'table', N'table', N'column', N'ID'
But the table in Access doesn't include an ID column and I can't see anything in the properties on the table to indicate why it's trying to add the property for SQL Server.
In the wizard guide I chose not to import any extras like indexes, triggers etc.
Any ideas why the wizard is doing this and how to stop it trying to create the properties?
Alternatively, are there any other tools which would moved the data from Access to MSSQL while keeping Access front-end objects in place and working?
The upsizing wizard had its deficiencies from the start, and has been removed from recent versions of Access. I recommend not using it.
Personally, I have a form that handles upsizing for me. It's a form with on it two text boxes named ConStr and adoString (the first containing the connection string for Access to use including the ODBC; prefix, the second containing either an ODBC or OLEDB string for ADO to use), a button named ToSQL, and a listbox named lstTables. It contains the following code:
To populate the local tables on load:
Private Sub Form_Load()
lstTables.RowSourceType = "Value List"
Dim iterator As Variant
For Each iterator In CurrentDb.TableDefs
If Not iterator.NAME Like "MSys*" And Not iterator.NAME Like "~*" Then
lstTables.AddItem iterator.NAME
End If
Next iterator
End Sub
To move the tables over to SQL server:
Private Sub ToSQL_Click()
Dim i1 As Variant
Dim td As DAO.TableDef
Dim NewTd As DAO.TableDef
Dim db As DAO.Database
Set db = CurrentDb
'Iterate through all selected tables
With lstTables
For Each i1 In .ItemsSelected
Set td = db.TableDefs(.ItemData(i1))
'Add a primary key if none exist
'AddPK td 'Not providing this one as it's not part of normal upscaling
'Move the table to SQL server
DoCmd.TransferDatabase acExport, "ODBC Database", _
conStr _
, acTable, .ItemData(i1), .ItemData(i1)
'Rename the local table to name_local
td.NAME = .ItemData(i1) & "_local"
'Change the remote table to the schema specified
'ADOChangeSchema GetDefaultSchema(), "mySchema", .ItemData(i1) 'Not providing this one as it's not part of normal upscaling
'Set the primary key in SQL server
ADOAddPrimaryKey GetDefaultSchema(), .ItemData(i1), GetPKName(td)
'Create a new linked table, linking to the remote table
Set NewTd = db.CreateTableDef(.ItemData(i1), 0, GetDefaultSchema() & .ItemData(i1), conStr)
db.TableDefs.Append NewTd
Next i1
End With
End Sub
And some helper functions:
Public Sub ADOAddPrimaryKey(SchemaName As String, tableName As String, FieldName As String)
On Error GoTo SetNotNull
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
conn.Open adoString
cmd.ActiveConnection = conn
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Exit Sub
SetNotNull:
If Err.Number = -2147217900 Then
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ALTER COLUMN [" & FieldName & "] INTEGER NOT NULL"
cmd.Execute
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Else
Err.Raise Err.Number
End If
End Sub
Public Function GetDefaultSchema() As String
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim rs As Object
conn.Open adoString
Set rs = conn.Execute("SELECT SCHEMA_NAME()")
GetDefaultSchema = rs.Fields(0)
End Function
Public Function GetPKName(td As DAO.TableDef) As String
'Returns the name of the first field included in the primary key (WARNING! Doesn't return all fields for composite primary keys!)
Dim idx As DAO.Index
For Each idx In td.Indexes
If idx.Primary Then
GetPKName = idx.Fields(0).NAME
Exit Function
End If
Next idx
End Function
This form only preserves data and the primary key, and makes several assumptions (too lazy to avoid them), such as: table names don't contain square brackets, there are no composite primary keys, the table schema is safe for use in SQL statements, there are no attachment fields or multivalued fields, and there are no relationships (had a version that preserved relationships, but... I honestly don't know where it is now).
It also leaves a renamed copy of the local table. The original version then tests 1K random rows to check if the content is identical, but I've omitted that for brevity.
You can use this as a starting point, since it might need tuning to suit your specific needs.

SQL query that uses a range in excel as the criteria

I use external data from an SQL query in excel to pull data onto a spreadsheet then the spreadsheet can be sent to one of my colleges in say the sales department and they can view the queried information.
I want to be able to have a cell that they can enter data into and press a refresh button.
So I need to do some thing like this:
SELECT Customer.CustomerCode, Customer.Name,
OrderHeader.OrderType, OrderHeader.OrderNumber
FROM Customer INNER JOIN
OrderHeader ON Customer.CustomerID = OrderHeader.CustomerID
WHERE (OrderHeader.OrderType = 2) AND (OrderHeader.OrderNumber = Range("A1").Value)
Not sure how or if this is possible and the reason I need to do this is because i'm going through lines from all the Quotes and if there is over 65536 then i'll have a problem.
This is what I have at the moment the SQL is different but that doesn't matter
The easiest way is to pull the values for the parameters into VBA, and then create the SQL statement as a string with the parameter values, and then populate the commandtext of the query.
Below is a basic example of the parameter in cell A1.
Sub RefreshQuery()
Dim OrderNo As String
Dim Sql As String
' Gets value from A1
OrderNo = Sheets("Sheet1").Range("A1").Value
'Creates SQL Statement
Sql = "SELECT Customer.CustomerCode, Customer.Name, " & _
"OrderHeader.OrderType , OrderHeader.OrderNumber " & _
"FROM Customer " & _
"INNER Join OrderHeader ON Customer.CustomerID = OrderHeader.CustomerID " & _
"WHERE (OrderHeader.OrderType = 2) And (OrderHeader.OrderNumber = " & OrderNo & ") "
With ActiveWorkbook.Connections(1).ODBCConnection
.CommandText = Sql
.Refresh
End With
End Sub
This assumes you have the query in Excel to begin with, and that it's the only query. Otherwise you'll have to define the name of the query as below:
With ActiveWorkbook.Connections("SalesQuery").ODBCConnection
I hope that helps :)
Further to #OWSam's example using ODBC, we can also use ADO to pull back query results from SQL Server. Using ADO, you cannot use windows verification and you will need the user to input their password somehow, to be able to run the query.
Personally I create a userform which has UserName and Password. Username pre-populated using Environ, and then they can type their password into the relevant TextBox.
Sub sqlQuery()
Dim rsConn As ADODB.Connection, rsData As ADODB.Recordset
Dim strSQL As String, winUserName As String, pwd As String
winUserName = UCase(Environ("username"))
pwd = "mypassword" 'password needed here.
strSQL = "SELECT * FROM mydatabase" 'query
Set rsConn = New ADODB.Connection
With rsConn
.ConnectionString = "Provider = sqloledb;" & _
"Data Source = [server name here];" & _
"Initial Catalog = [initial database];" & _
"Integrated Security=SSPI;" & _
"User ID = " & winUserName & ";" & _
"Password = " & pwd & ";"
.Open
End With
Set rsData = rsConn.Execute(strSQL)
Range("A1").CopyFromRecordset rsData
End Sub
Edit: You must go into references and turn on the Microsoft ActiveX Data Objects Recordset 6.0 library (or equivalent within your VBE).

Create database in SQL Server (only once) using VBA Excel

I would like to create a database in SQL server using VBA (Excel) just the first time that I will run the code. So the second time I run the code, the database will exist, and it will not create another one.
#abarisone
`Public Sub CreateDBTable()
Dim dbConnectStr As String
Dim Catalog As Object
Dim cnt As ADODB.Connection
Dim dbName As String
Dim tblName As String, ServerName As String, UserID As String, pw As String
tblName = shControl.Range("B5") 'Table Name
ServerName = "SERVICESWS15" 'Enter Server Name or IP
dbName = shControl.Range("B4") 'Enter Database Name
UserID = "" 'Leave blank for Windows Authentification
pw = "" 'Leave blank for Windows Authentification
dbConnectStr = "Provider=SQLOLEDB;Data Source=" & ServerName & ";Initial Catalog=" & dbName & ";User Id=" & UserID & ";Password=" & pw & ";"
Set Catalog = CreateObject("ADOX.Catalog")
Catalog.Create dbConnectStr
Set Catalog = Nothing
Set cnt = New ADODB.Connection
With cnt
.Open dbConnectStr
.Execute "CREATE TABLE " & tblName & " (KEY nvarchar(100) NOT NULL, " & _
"Date nvarchar(100) NOT NULL, " & _
"PRIMARY KEY (KEY));"
End With
Set cnt = Nothing
End Sub
`
There is an error in this line:
Catalog.Create dbConnectStr
Error: No such interface supported
It's not very complicated. First make sure that you are referring to the appropriate ADO library like here on the screenshot.
Then your have certain building blocks you will have to use: first make a Connection object (with a connection string), then make a Command object, and last but not least use the ExecuteNonQuery method of Command on your connection object. It does what the name says: executes an SQL command without having a RecordSet as a return object. See examples in the documentation starting from here.
I have not tried it before, but for this to happen without error, you will probably have to set your connection string to the system database called "Master" if working on MS SQL Server.
Of course, the SQL commands you will have to execute are (1) check if the database exists, (2) create db and tables if not.
Then you can create your "normal" Connection object to your database.
WARNING: to be able to create a database, your technical user defined in the VBA script must have high (system admin) privileges which is definitely a HUGE RISK even if you protect your excel. If it's not a sandbox environment, DO NOT DO IT!

how to insert update and delete records in tables

I have a large windows form with 50 different fields (text boxes,combo boxes,listview,check box) i have 10 tables in sql server database each table have different column I want to insert my windows forms 50 fields in these 10 tables. Here is my code to insert record in a table
Dim cmd As New SqlCommand
cmd.Connection = conn
cmd.CommandText = "Insert Into ChartOfAccount (MainCode,MainDescription,AccountCode,AccountDescription,OpeningBalance) values ('" & MainCode & "','" & MainDescription & "','" & AccountCode & "','" & AccountDescription & "','" & OpeningBalance & "')"
cmd.ExecuteNonQuery()
So question is if I insert data into 10 different tables then I need to write 10 insert statments.
If I update or delete record I need to write update and delete command 10 tables seprately
this will take long time please guide me if any short method exists.
Your query is susceptible with SQL injection so better to use parameters:
Dim connStr as String = "connection string values here";
using con as new SqlConnection(connStr)
Dim commandText as String =
#"Insert Into ChartOfAccount (MainCode
,MainDescription,AccountCode
,AccountDescription,OpeningBalance)
VALUES
(#MainCode, #MainDesc,#AccountCode
,#AccountDesc,#OpeningBalance)"
Dim cmd as New SqlCommand(commandText,con)
cmd.Parameters.AddWithValue("#MainCode",MainCode)
cmd.Parameters.AddWithValue("#MainDesc",MainDescription)
cmd.Parameters.AddWithValue("#AccountCode",AccountCode)
cmd.Parameters.AddWithValue("#AccountDesc",AccountDescription)
cmd.Parameters.AddWithValue("#OpeningBalance",OpeningBalance)
Try
con.Open()
cmd.ExecuteNonQuery()
Catch ex as Exception
MessageBox.Show(ex.Message)
End Try
End Using
Now, this is only for Inserting records example.
You can create a stored procedure for that. In order to create a stored procedure, you can use the following SQL query:
Create procedure [dbo].[NewUser]
#Yourparameter1 int ,
#Yourparamtere2
As
Insert into dbo.Users
(
// Db columns
Column1 ,
Column2
)
values
(
#Yourparameter1
#Yourparameter2
)
And in your C# code, create a SQLParameter collection and pass it to the procedure. It will work fine and smoothly, and this way your code will be simpler.

Resources