VBA Access SQL Server table insert behaves differently - sql-server

I have a problem when changing access code to SQL Server. I am trying to save the data in SQL Server, not in the Access database.
Access code
SELECT MAX("SPR-" & VAL(Replace([RequestID],"SPR-",""))+1) AS AutoID
FROM Tb_Request;
SQL Server:
SELECT FORMAT(MAX(1 + REPLACE(RequestID, 'SPR-', '')),'SPR-#') AS RequestNo
FROM dbo.PilotRequest
I think I changed correctly because I checked in SQL Server, the result is correct.
When I apply this code in Access, the result looks like this:
(There is data in the dbo.PilotRequest table, which is '5555'.)
Is there anyone who knows the reason for this?

You need to do the concatenation outside of the Max, else your Max will compare strings and fail (because there's a string inside it).
SELECT "SPR-" & MAX(VAL(Replace([RequestID],"SPR-",""))+1) AS AutoID
FROM Tb_Request;

Related

Dynamic SQL without having to use fully qualified table names in SQL (Openrowset?)

I have a large set of pre-existing sql select statements.
From a stored procedure on [Server_A], I would like to execute each of these statements on multiple different SQL Servers & Databases (the list is stored in a local table on [Server_A] , and return the results into a table on [Server_A].
However, I do not want to have to use fully qualified table names in my sql statements. I want to execute "select * from users", not "select * from ServerName.DatabaseName.SchemaName.Users"
I've investigated using Openrowset, but I am unable to find any examples where both the Server name and DatabaseName can be specified as an attribute of the connection, rather than physically embedded within the actual SQL statement.
Is Openrowset capable of this? Is there an alternate way of doing this (from within a stored procedure, as opposed to resorting to Powershell or some other very different approach?)
The inevitable "Why do I want to do this?"
You can do it (specify the server and database in the connection
attributes and then use entirely generic sql across all databases) in
virtually every other language that accesses SQL Server.
Changing all my pre-existing complex SQL to be fully qualified is a
huge PITA (besides, you simply shouldn't have to do this)
This can be done quite easily via SQLCLR. If the result set is to be dynamic then it needs to be a Stored Procedure instead of a TVF.
Assuming you are doing a Stored Procedure, you would just:
Pass in #ServerName, #DatabaseName, #SQL
Create a SqlConnection with a Connection String of: String.Concat("Server=", ServerName.Value, "; Database=", DatabaseName.Value, "; Trusted_Connection=yes; Enlist=false;") or use ConnectionStringBuilder
Create a SqlCommand for that SqlConnection and using SQL.Value.
Enable Impersonation via SqlContext.WindowsIdentity.Impersonate();
_Connection.Open();
undo Impersonation -- was only needed to establish the connection
_Reader = Command.ExecuteReader();
SqlContext.Pipe.Send(_Reader);
Dispose of Reader, Command, Connection, and ImpersonationContext in finally clause
This approach is less of a security issue than enabling Ad Hoc Distributed Query access as it is more insulated and controllable. It also does not allow for a SQL Server login to get elevated permissions since a SQL Server login will get an error when the code executes the Impersonate() method.
Also, this approach allows for multiple result sets to be returned, something that OPENROWSET doesn't allow for:
Although the query might return multiple result sets, OPENROWSET returns only the first one.
UPDATE
Modified pseudo-code based on comments on this answer:
Pass in #QueryID
Create a SqlConnection (_MetaDataConnection) with a Connection String of: Context Connection = true;
Query _MetaDataConnection to get ServerName, DatabaseName, and Query based on QueryID.Value via SqlDataReader
Create another SqlConnection (_QueryConnection) with a Connection String of: String.Concat("Server=", _Reader["ServerName"].Value, "; Database=", _Reader["DatabaseName"].Value, "; Trusted_Connection=yes; Enlist=false;") or use ConnectionStringBuilder
Create a SqlCommand (_QueryCommand) for _QueryConnection using _Reader["SQL"].Value.
Using _MetaDataConnection, query to get parameter names and values based on QueryID.Value
Cycle through SqlDataReader to create SqlParameters and add to _QueryCommand
_MetaDataConnection.Close();
Enable Impersonation via SqlContext.WindowsIdentity.Impersonate();
_QueryConnection.Open();
undo Impersonation -- was only needed to establish the connection
_Reader = _QueryCommand.ExecuteReader();
SqlContext.Pipe.Send(_Reader);
Dispose of Readers, Commands, Connections, and ImpersonationContext in finally clause
If you want to execute a sql statement on every database in a instance you can use (the unsupported, unofficial, but widely used) exec sp_MSforeachdb like this:
EXEC sp_Msforeachdb 'use [?]; select * from users'
This will be the equivalent of going through every database through a
use db...
go
select * from users
This is an interesting problem because I googled for many, many hours, and found several people trying to do exactly the same thing as asked in the question.
Most common responses:
Why would you want to do that?
You can not do that, you must fully qualify your objects names
Luckily, I stumbled upon the answer, and it is brutally simple. I think part of the problem is, there are so many variations of it with different providers & connection strings, and there are so many things that could go wrong, and when one does, the error message is often not terribly enlightening.
Regardless, here's how you do it:
If you are using static SQL:
select * from OPENROWSET('SQLNCLI','Server=ServerName[\InstanceName];Database=AdventureWorks2012;Trusted_Connection=yes','select top 10 * from HumanResources.Department')
If you are using Dynamic SQL - since OPENROWSET does not accept variables as arguments, you can use an approach like this (just as a contrived example):
declare #sql nvarchar(4000) = N'select * from OPENROWSET(''SQLNCLI'',''Server=Server=ServerName[\InstanceName];Database=AdventureWorks2012;Trusted_Connection=yes'',''#zzz'')'
set #sql = replace(#sql,'#zzz','select top 10 * from HumanResources.Department')
EXEC sp_executesql #sql
Noteworthy: In case you think it would be nice to wrap this syntax up in a nice Table Valued function that accepts #ServerName, #DatabaseName, #SQL - you cannot, as TVF's resultset columns must be determinate at compile time.
Relevant reading:
http://blogs.technet.com/b/wardpond/archive/2005/08/01/the-openrowset-trick-accessing-stored-procedure-output-in-a-select-statement.aspx
http://blogs.technet.com/b/wardpond/archive/2009/03/20/database-programming-the-openrowset-trick-revisited.aspx
Conclusion:
OPENROWSET is the only way that you can 100% avoid at least some full-qualification of object names; even with EXEC AT you still have to prefix objects with the database name.
Extra tip: The prevalent opinion seems to be that OPENROWSET shouldn't be used "because it is a security risk" (without any details on the risk). My understanding is that the risk is only if you are using SQL Server Authentication, further details here:
https://technet.microsoft.com/en-us/library/ms187873%28v=sql.90%29.aspx?f=255&MSPPError=-2147217396
When connecting to another data source, SQL Server impersonates the login appropriately for Windows authenticated logins; however, SQL Server cannot impersonate SQL Server authenticated logins. Therefore, for SQL Server authenticated logins, SQL Server can access another data source, such as files, nonrelational data sources like Active Directory, by using the security context of the Windows account under which the SQL Server service is running. Doing this can potentially give such logins access to another data source for which they do not have permissions, but the account under which the SQL Server service is running does have permissions. This possibility should be considered when you are using SQL Server authenticated logins.

Problems executing this INSERT in MS Access

So I seem to be forced to use MS Access as a SQL Server client.
For whatever reason, this just won't execute:
INSERT INTO l9990064_INF_PATH (DATA_PATH)
VALUES ("/OlifeRequest/RequestType")
SELECT DATA_PATH, "/OlifeRequest/RequestType"
FROM l9990064_INF_PATH
WHERE DATA_PATH NOT IN
(SELECT DISTINCT DATA_PATH
FROM l9990064_INF_PATH
WHERE DATA_PATH="/OlifeRequest/RequestType");
Basically the query attempts to insert a field in table if it doesn't already exist in that table.
The error I get is:
Missing semicolon (;) at end of SQL statement.
Clearly this is not the case, there is in fact a semicolon at the very end.
It appears to be a valid query so I'm wondering what I have to do here? Thanks!
INSERT INTO l9990064_INF_PATH (DATA_PATH)
VALUES ('/OlifeRequest/RequestType'); --<-- Single Quotes
SELECT DATA_PATH, '/OlifeRequest/RequestType' --<-- Single Quotes
FROM l9990064_INF_PATH
WHERE DATA_PATH <> '/OlifeRequest/RequestType'; --<-- Single Quotes
Also there is no need to use NOT IN operator since it is checking the value in the same table it is selecting from.
Or if you were trying to insert data from a SELECT statement , it would be something like ....
INSERT INTO l9990064_INF_PATH (DATA_PATH, Other_Column_Name)
SELECT DATA_PATH, '/OlifeRequest/RequestType'
FROM l9990064_INF_PATH
WHERE DATA_PATH <> '/OlifeRequest/RequestType';
I didn't know this at first, but it appears the queries I was using would work in SQL server but Access was only allowing Access queries or some shit. So even though Access was acting as a SQL server client, it wasn't really allowing me to use SQL server queries.
I went through the pain of getting SQL Server Management Studio as the client through the painful procurement at my company and I couldn't think of a better solution, do not use Access as a SQL Server client kids...

SQL Server Linked Server Example Query

While in Management Studio, I am trying to run a query/do a join between two linked servers.
Is this a correct syntax using linked db servers:
select foo.id
from databaseserver1.db1.table1 foo,
databaseserver2.db1.table1 bar
where foo.name=bar.name
Basically, do you just preface the db server name to the db.table ?
The format should probably be:
<server>.<database>.<schema>.<table>
For example:
DatabaseServer1.db1.dbo.table1
Update: I know this is an old question and the answer I have is correct; however, I think any one else stumbling upon this should know a few things.
Namely, when querying against a linked server in a join situation the ENTIRE table from the linked server will likely be downloaded to the server the query is executing from in order to do the join operation. In the OP's case, both table1 from DB1 and table1 from DB2 will be transferred in their entirety to the server executing the query, presumably named DB3.
If you have large tables, this may result in an operation that takes a long time to execute. After all it is now constrained by network traffic speeds which is orders of magnitude slower than memory or even disk transfer speeds.
If possible, perform a single query against the remote server, without joining to a local table, to pull the data you need into a temp table. Then query off of that.
If that's not possible then you need to look at the various things that would cause SQL server to have to load the entire table locally. For example using GETDATE() or even certain joins. Others performance killers include not giving appropriate rights.
See http://thomaslarock.com/2013/05/top-3-performance-killers-for-linked-server-queries/ for some more info.
SELECT * FROM OPENQUERY([SERVER_NAME], 'SELECT * FROM DATABASE_NAME..TABLENAME')
This may help you.
For those having trouble with these other answers , try OPENQUERY
Example:
SELECT * FROM OPENQUERY([LinkedServer], 'select * from [DBName].[schema].[tablename]')
If you still find issue with <server>.<database>.<schema>.<table>
Enclose server name in []
You need to specify the schema/owner (dbo by default) as part of the reference. Also, it would be preferable to use the newer (ANSI-92) join style.
select foo.id
from databaseserver1.db1.dbo.table1 foo
inner join databaseserver2.db1.dbo.table1 bar
on foo.name = bar.name
select * from [Server].[database].[schema].[tablename]
This is the correct way to call.
Be sure to verify that the servers are linked before executing the query!
To check for linked servers call:
EXEC sys.sp_linkedservers
right click on a table and click script table as select
select name from drsql01.test.dbo.employee
drslq01 is servernmae --linked serer
test is database name
dbo is schema -default schema
employee is table name
I hope it helps to understand, how to execute query for linked server
Usually direct queries should not be used in case of linked server because it heavily use temp database of SQL server. At first step data is retrieved into temp DB then filtering occur. There are many threads about this. It is better to use open OPENQUERY because it passes SQL to the source linked server and then it return filtered results e.g.
SELECT *
FROM OPENQUERY(Linked_Server_Name , 'select * from TableName where ID = 500')
For what it's worth, I found the following syntax to work the best:
SELECT * FROM [LINKED_SERVER]...[TABLE]
I couldn't get the recommendations of others to work, using the database name. Additionally, this data source has no schema.
In sql-server(local) there are two ways to query data from a linked server(remote).
Distributed query (four part notation):
Might not work with all remote servers. If your remote server is MySQL then distributed query will not work.
Filters and joins might not work efficiently. If you have a simple query with WHERE clause, sql-server(local) might first fetch entire table from the remote server and then apply the WHERE clause locally. In case of large tables this is very inefficient since a lot of data will be moved from remote to local. However this is not always the case. If the local server has access to remote server's table statistics then it might be as efficient as using openquery More details
On the positive side T-SQL syntax will work.
SELECT * FROM [SERVER_NAME].[DATABASE_NAME].[SCHEMA_NAME].[TABLE_NAME]
OPENQUERY
This is basically a pass-through. The query is fully processed on the remote server thus will make use of index or any optimization on the remote server. Effectively reducing the amount of data transferred from the remote to local sql-server.
Minor drawback of this approach is that T-SQL syntax will not work if the remote server is anything other than sql-server.
SELECT * FROM OPENQUERY([SERVER_NAME], 'SELECT * FROM DATABASE_NAME.SCHEMA_NAME.TABLENAME')
Overall OPENQUERY seems like a much better option to use in majority of the cases.
I have done to find out the data type in the table at link_server using openquery and the results were successful.
SELECT * FROM OPENQUERY (LINKSERVERNAME, '
SELECT DATA_TYPE, COLUMN_NAME
FROM [DATABASENAME].INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME =''TABLENAME''
')
Its work for me
Following Query is work best.
Try this Query:
SELECT * FROM OPENQUERY([LINKED_SERVER_NAME], 'SELECT * FROM [DATABASE_NAME].[SCHEMA].[TABLE_NAME]')
It Very helps to link MySQL to MS SQL
PostgreSQL:
You must provide a database name in the Data Source DSN.
Run Management Studio as Administrator
You must omit the DBName from the query:
SELECT * FROM OPENQUERY([LinkedServer], 'select * from schema."tablename"')
For MariaDB (and so probably MySQL), attempting to specify the schema using the three-dot syntax did not work, resulting in the error "invalid use of schema or catalog". The following solution worked:
In SSMS, go to Server Objects > Linked Servers > Providers > MSDASQL
Ensure that "Dynamic parameter", "Level zero only", and "Allow inprocess" are all checked
You can then query any schema and table using the following syntax:
SELECT TOP 10 *
FROM LinkedServerName...[SchemaName.TableName]
Source: SELECT * FROM MySQL Linked Server using SQL Server without OpenQuery
Have you tried adding " around the first name?
like:
select foo.id
from "databaseserver1".db1.table1 foo,
"databaseserver2".db1.table1 bar
where foo.name=bar.name

Inserting NULL in an nvarchar fails in MSAccess

I'm experiencing something a bit strange.
I have a table on SQL Server 2008, say StockEvent that contains a Description field defined as nVarchar(MAX).
The field is set to be Nullable, has no default value and no index on it.
That table is linked into an Access 2007 application, but if I explicitly insert a NULL into the field, I'm systematically getting:
Run-time Error '3155' ODBC--insert on a linked table 'StockEvent' failed.
So the following bits of code in Access both reproduce the error:
Public Sub testinsertDAO()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("StockEvent", _
dbOpenDynaset, _
dbSeeChanges + dbFailOnError)
rs.AddNew
rs!Description = Null
rs.Update
rs.Close
Set rs = Nothing
Set db = Nothing
End Sub
Public Sub testinsertSQL()
Dim db As DAO.Database
Set db = CurrentDb
db.Execute "INSERT INTO StockEvent (Description) VALUES (NULL);", _
dbSeeChanges
Set db = Nothing
End Sub
However, if I do the same thing from the SQL Server Management Studio, I get no error and the record is correctly inserted:
INSERT INTO StockEvent (Description) VALUES (NULL);
It doesn't appear to be machine-specific: I tried on 3 different SQL Server installations and 2 different PCs and the results are consistent.
I initially though that the problem may be in my Access application somewhere, but I isolated the code above into its own Access database, with that unique table linked to it and the results are consistent.
So, is there some known issue with Access, or ODBC and inserting NULL values to nvarchar fields?
Update.
Thanks for the answers so far.
Still no luck understanding why though ;-(
I tried with an even smaller set of assumptions: I created a new database in SQL Server with a single table StockEvent defined as such:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[StockEvent](
[ID] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](max) NULL
) ON [PRIMARY]
GO
Then linked that table though ODBC into the test Access 2007 application.
That application contains no forms, nothing except the exact 2 subroutines above.
If I click on the linked table, I can edit data and add new records in datasheet mode.
Works fine.
If I try any of the 2 subs to insert a record, they fail with the 3155 error message.
(The table is closed and not referenced anywhere else and the edit datasheet is closed.)
If I try the SQL insert query in SQL Server Management Studio, it works fine.
Now for the interesting bit:
It seems that anything as big or bigger than nvarchar(256), including nvarchar(MAX) will fail.
Anything with on or below nvarchar(255) works.
It's like Access was considering nvarchar as a simple string and not a memo if its size is larger than 255.
Even stranger, is that varchar(MAX) (wihout the n) actually works!
What I find annoying is that Microsoft's own converter from Access to SQL Server 2008 converts Memo fields into nvarchar(MAX), so I would expect this to work.
The problem now is that I need nvarchar as I'm dealing with Unicode...
OK, I may have found a related answer: Ms Access linking table with nvarchar(max).
I tried using the standard SQL Server driver instead of the SQL Server Native Client driver and nvarchar(MAX) works as expected with that older driver.
It really annoys me that this seems to be a long-standing, unfixed, bug.
There is no valid reason why nvarchar should be erroneously interpreted as a string by one driver and as a memo when using another.
In both cases, they appear as memo when looking a the datatype under the table design view in Access.
If someone has any more information, please leave it on this page. I'm sure others will be glad to find it.
That should be legal syntax. Is it possible that the field you are try to give a null value is linked to other fields that don't allow null values?
Potential concurrency problem... Is the record open by another instance of Access on the same or a different machine, or does a form bound to the table have the record open in the same instance of Access on the same machine?
Renaud, try putting something in one of the other fields when you do the insert.
Also, try inserting an empty string ("") instead of a null.
Renaud,
Did you try running a SQL Profiler trace? If you look at the Errors and Warnings category it should kick out an error if your insert failed as a result of a SQL Server constraint.
If you don't see any errors, you can safely assume that the problem is in your application.
Also, are you sure you're actually connected to SQL Server? Is CurrentDB not the same variable you're using in your Access test loop?
i got annother issue (here my post: link text
In some very rare cases an error arises when saving a row with a changed memo field - same construct explained in my former post but driving sql2000-servers and it's appropriate odbc-driver (SQL SERVER).
The only weired fix is: to expand the table structure on sql-server with a column of datatype [timestamp] and refresh the odbc-links. That works and releases the show-stopper in this column on this one row ...
Maybe this info can help someone - for me it's history in going further to odbc with sql2008 in changing the datatypes [text] to [varchar(max)].

Using Parameters in MS Reporting Services (SQL Server 2008) against an ODBC data source

I writing a report in Visual Studio that takes a user input parameter and runs against an ODBC datasource. I would like to write the query manually and have reporting services replace part of the where clause with the parameter value before sending it to the database. What seems to be happening is that the #parmName I am assuming will be replaced is actually being sent as part of the SQL statement. Am I missing a configuration setting somewhere or is this simply not possible?
I am not using the filter option in the tool because this appears to bring back the full dataset from the database and do the filtering on the SQL Server.
It sounds like you'll need to treat the SQL Statement as an expression. For example:
="Select col1, col2 from table 1 Where col3 = " & Parameters!Param1.Value
If the where clause is a string you would need to do the following:
="Select col1, col2 from table 1 Where col3 = '" & Parameters!Param1.Value & "'"
Important: Do not use line breaks in your SQL expression. If you do you will get an error.
Holla back if you need any more assistance.
Doesn't ODBC use the old "?" syntax for parameters? Try this:
select col1, col2 from table1 where col3 = ?
The order of your parameters becomes important then, but it's less vulnerable to SQL injection than simply appending the parameter value.
Encountered same problem trying to query an access database via ODBC.
My original query: SELECT A.1 FROM A WHERE A.1 = #parameter resulted in error. Altered to: SELECT A.1 FROM A WHERE A.1 = ?.
You then have to map the query parameter with your report parameter.
I am a bit confused about this question, if you are looking for simple parameter usage then the notation is :*paramName* , however if you want to structurally change the WHERE clause (as you might in sql+ using ?) then you should really be using custom code within the report to define a function that returns the required sql for the query.
Unfortunately, when using custom code, parameters cannot be referenced directly in the generated query but have to have there values concatenated into the resultant String, thus introducing the potential for SQL injection.

Resources