I have the Powershell following function to load data from a database via ODBC, given a connection string and a query.
function Run-OdbcSQL {
[OutputType([System.Data.DataTable])]
[cmdletbinding()]
param (
[Parameter(Mandatory)][string]$ConnString,
[Parameter(Mandatory)][string]$Query
)
$Conn = New-Object System.Data.Odbc.OdbcConnection
$Conn.ConnectionString = $ConnString
$Conn.Open()
try {
$Result =(New-Object Data.Odbc.OdbcCommand($Query,$Conn)).ExecuteReader()
$Table = New-Object "System.Data.DataTable"
$Table.Load($Result)
# Prevent PS from unravelling a table with a single row
Write-Output -NoEnumerate $Table
} finally {
$Conn.Close()
}
}
This works fine, except when one of the columns being returned, is defined as VARCHAR(MAX). In this case, the function returns the error:
Exception calling "Load" with "1" argument(s): "Failed to enable constraints.
One or more rows contain values violating non-null, unique, or foreign-key constraints."
If I modify the query to SELECT CAST(column_name AS VARCHAR(255)) FROM …, the data will be loaded without any issue.
Environment:
Database: SQL Server 2019
ODBC driver: SQL Server Native Client 11.0 / ODBC Driver 18 for SQL Server (reproducible with both)
The issue seems to be a combination of VARCHAR(MAX) and System.Data.DataTable, but I can't figure how to resolve it.
No definite solution, but I have managed to find some workarounds.
The CAST(<columnname> AS VARCHAR(<length>) works, but you're limited to 8000 characters because "SQL_CHAR and SQL_VARCHAR data types are limited to a maximum length of 8,000 characters" [1][2]
The text datatype on the other hand, can hold 2^30 - 1 bytes (identical to NVARCHAR(MAX)) and it seems to be passed through correctly (couldn't find any concrete info). Thus: `CAST( AS TEXT) avoids the issue.
Test case
Data: given a query column defined as VARCHAR(MAX), retrieving a single row where the contents of the column are 100.000 characters long.
Middleware: Tests were executed using both ODBC Driver 18 for SQL Server and SQL Server Native Client 11.0. Results were identical for either driver.
Database: Microsoft SQL Server 2019.
Query
Result
SELECT query
Exception calling "Load" with "1" argument(s): "Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints."
SELECT CAST(query as VARCHAR(8000))
OK, truncated at 8.000 characters
SELECT CAST(query as VARCHAR(10000))
The size (10000) given to the type 'varchar' exceeds the maximum allowed for any data type (8000).
SELECT CAST(query as TEXT)
OK, 10.000 characters
Remarks
For other databases (using different middleware), this approach might not work. E.g. for PostgreSQL, you need to use the MaxLongVarcharSize property, which is set to 8190 by default, and supports up to 65535 bytes.
Related
Background: We have Airflow 2.4.3 and the metadata database is SQL Server. Basically this one , our case uses sql server .
So, the connection for the internal db is through this connection string
[database]
sql_alchemy_conn = mssql+pyodbc://{{ database.user }}:{{ airflow_db_password }}#{{ database.server }}:{{ database.port }}/{{ database.name }}?driver=ODBC+Driver+17+for+SQL+Server
load_default_connections = False
Now, the problem is : all of the internal update queries (e.g. when a task is done) sends parameters as nvarchar.
In JDBC we can do something like this : setSendStringParametersAsUnicode.
How does one go about doing the same for pyodbc , connecting to sql sever? Is it possible? I could not find the same method for ODBC.
Problem is these metadata tables have the column data type as varchar, with unique indexes on them and since these internal airflow update queries pass on string parameters as nvarchar, it results in table scans and often deadlocks.
Example of an internal update query
declare #p1 int
set #p1=2603
exec sp_prepexec #p1 output,N'#P1 nvarchar(18),#P2 nvarchar(40),#P3 nvarchar(72),#P4 nvarchar(60),#P5 int',N'UPDATE task_instance SET state=#P1 WHERE task_instance.dag_id = #P2 AND task_instance.run_id = #P3 AND task_instance.task_id = #P4 AND task_instance.map_index = #P5',N'scheduled',N'lead_value_score_1_1',N'scheduled__2022-12-10T09:12:00+00:00',N'predict_funding_scores_instant',-1
Notice that all the columns in the where clause are passed on as nvarchar, whereas the table has them as varchar.
Any questions and I can try to answer them.
These update queries are airflow internal and thus we don't control them and thus trying to see if these can be set on the connection level.
I'm trying to do an append query from MS Access into SQL server. The SQL server column is varchar(max) which I thought meant it can accept more than 4000 characters
I get the following error when running this query from VBA in MS Access
Run Time Error 3155 ODBC - insert on a linked table
failed [Microsoft] [SQL Server Native Client 10.0] {SQL Server] The
size (7596) given to the parameter '#P6' exceeds the maximum allowed
(4000). (#2717)
adding my queries
this query is based on an linked outlook folder, Deleted Items
SELECT Trim(Mid([contents],InStr([contents],"Short Description: ")+19,(InStr([contents],"Requestor: ")-1)-(InStr([contents],"Short Description: ")+19)-3)) AS ShortDesc, Trim(Mid([contents],InStr([contents],"Requestor: ")+10,(InStr([contents],"Requestor EMail: ")-1)-(InStr([contents],"Requestor: ")+10)-3)) AS Requester, Trim(Mid(Mid([contents],InStr([contents],"Office Location: ")+3),InStr(Mid([contents],InStr([contents],"Office Location: ")),"Description:")+13,(InStr(Mid([contents],InStr([contents],"Office Location: ")),"Assigned Task: ")-1)-(InStr(Mid([contents],InStr([contents],"Office Location: ")),"Description:")+13)-3)) AS Description, Trim(Mid([contents],InStr([contents],"Request Item: ")+14,12)) AS TicketNoText, Val(Mid([contents],InStr([contents],"Request Item: ")+19,7)) AS TicketNo, Val(Mid([contents],InStr([contents],"Assigned Task: ")+19,7)) AS TaskNo, Mid([contents],InStr([contents],"Delivery Date: ")+15,10) AS DeliveryDate, Trim(Mid([contents],InStr([contents],"Requestor EMail: ")+17,(InStr([contents],"Office Location: ")-1)-(InStr([contents],"Requestor EMail: ")+17)-3)) AS RequesterEMail
FROM [Deleted Items]
WHERE ((([Deleted Items].From)="xxxxxxx#service-now.com") AND (([Deleted Items].Subject)="you just assigned a ticket to yourself"));
then, the append query is based on this one and a few other ones
INSERT INTO PROJECTS ( TaskNo, RequesterID, Description, TicketNo, ProjectFolderLink, SNLink, OpenedOn, DateDue )
SELECT QSNNew.taskno, cmbRequesters.RequesterID, "SHORT DESCRIPTION: " & [shortdesc] & Chr(13) & Chr(10) & Chr(13) & Chr(10) & "DESCRIPTION: " & [Description] AS Expr4, QSNNew.TicketNo, "#\\link to a network folder" & lpad([ticketno],"0",7) & "\#" AS Expr1, "#https://xxxx.service-now.com/nav_to.do?uri=sc_task.do?sysparm_query=number=TASK" & lpad([taskno],"0",7) & "#" AS Expr2, Now() AS Expr3, Mid([DeliveryDate],6,2) & "/" & Right([DeliveryDate],2) & "/" & Left([DeliveryDate],4) AS Expr5
FROM (QSNNew LEFT JOIN PROJECTS ON QSNNew.TicketNo = PROJECTS.TicketNo) LEFT JOIN cmbRequesters ON QSNNew.[Requester] = cmbRequesters.RequesterName
WHERE (((PROJECTS.TicketNo) Is Null));
in case anyone is wondering what I'm doing, I'm loading tickets from Service Now into an Access database and there's no other way of doing it, other than parsing notification emails i get from Service Now when a ticket is assigned to me.
So I'm parsing those emails and creating my own version with links to the ServiceNow page, network folders for the ticket, etc.
It's a matter of driver (SQL Native Client and ODBC Driver 17 are limited to 4000 chars). If you use SQL Server Driver (10.09.18362.01) limit is 64000 chars.
As yu2 suggested ADODB query would avoid that (ODBC Passthrough should do it too).
The parameter #P6 is produced by ODBC see Optimizing Microsoft Office Access Applications Linked to SQL Server Understanding Dynasets
This is answer for your question
3155 Insert into Linked Table error
On this link Microsoft recommends to use ADODB instead of ODBC if you can't decrease field size in application.
ODBC protocol is generally for big data -- sql server -- which uses "Fire Hose" bandwith. Access uses (my term) garden hose bandwith (with all due respect for mini RDBMSs) because Access is basically a mini RDBMS (relational database management system) which is also file based. Unless everything (front/back ends) is set up perfectly and conditions are ideal -- you will encounter the problem you are having. Microsoft came up with ADODB as a workaround for this problem. When I have to interface between sql server and Access -- I use ADODB. This has proven to be much more reliable and consistent between the small and large RDBMSs. Here is some sample ADODB code for reading from and writing to a Sql Server from Access
'--add a reference in Tools/References to Microsoft ActiveX Data Objects 2.x Library '--(2.5 or higher)
...
The Access "Long Text" column can contain a text string up to a gigabyte in size. The message says you are trying to fit 7596 characters into a 4000 character field.
If so, your SQL server database should be exposing an ODBC LongVarChar column instead of VarChar.
LongVarChar is an ODBC type. The mapping is done by the ODBC driver. If you use an ODBC driver that maps VarChar(MAX) to a VarChar ODBC column, you can either get a different driver, or, possibly, use a SQL SERVER 'TEXT' column instead. TEXT is the old SQL Server column type, from when VarChar could only go to 4000. Old ODBC drivers recognize that TEXT columns map to LongVarChar.
I think the error message is quite helpful.
You are trying to fit a string with length of 7596 while your maximum varchar length is 4000.
I guess you either truncate it or store it as a blob.
Oracle version 12.1.0.2
max_string_size=extended
I am using sql server ODBC to connect to sql server database via Oracle gateway to sql server, the connection is working fine and i am able to access sql server tables.
However, as per Oracle documentation starting 12c and with extended limit on varchar2 data type the conversion of sqlserver varchar(max) to oracle Long will only happen if the length of sql server data is more than 32k.
My sql server table has few columns defined as varchar(max) in and all of those i see getting converted to LONG when i try to describe the table over dblink.
I need to load the data from sql server to oracle and the above problem is making it very difficult as more than one long columns can not be copied over dblink.
Any help will be deeply appreciated.
I created a view on the SQL server side that uses substr(column,1,4000) to fit within the old Oracle max 4000 character length. This worked quite well with Oracle 11.
I am in the process of migrating to a new Oracle 18 instance that uses character set AL32UTF8 instead of WE8MSWIN1252. The exact same SQL is now getting:
ORA-28500: connection from ORACLE to a non-Oracle system returned this message:
[Microsoft][ODBC Driver Manager] Program type out of range {HY003}
ORA-02063: preceding 2 lines from CEAV195
Fortunately I don't have a tight deadline for working this out.
Comment: I am now getting
[Error] Execution (8: 17): ORA-00997: illegal use of LONG datatype
despite using the following in the view on the SQL Server side:
cast(substring(cr.response,1,2000) as varchar(2000)) response
As I said earlier, this worked perfectly fine with Oracle 11 and the WE8MSWIN1252 character set.
I hit the same issue and found this solution elsewhere
set serverout on
DECLARE
l_cursor BINARY_INTEGER;
l_id VARCHAR2(60);
l_temp VARCHAR2(250);
l_notes VARCHAR2(32767);
BEGIN
l_cursor := DBMS_HS_PASSTHROUGH.open_cursor#remotedb;
DBMS_HS_PASSTHROUGH.parse#remotedb(
l_cursor,
'select "RecId","Notes" from "MySqlServerTable"'
);
LOOP
DBMS_HS_PASSTHROUGH.get_value#remotedb(l_cursor, 1, l_id);
DBMS_HS_PASSTHROUGH.get_value#remotedb(l_cursor, 2, l_notes);
DBMS_OUTPUT.put_line(l_id || ' ' || l_notes);
END LOOP;
exception
when others then
DBMS_HS_PASSTHROUGH.close_cursor#remotedb(l_cursor);
raise;
END;
/
When obtaining an image grabde with sql server linked servers from PostgreSQL, I get the following error: OLE DB provider 'MSDASQL' for linked server 'bd_acceso_ruisegip' returned data that does not match expected data length for column '[MSDASQL] . fot_imagen '. The data length (maximum) expected is 255 and the data returned is 38471.
Don't know if you were dealing with a bytea column but I was having the same problem. Found the answer in the configuring of the postrgres ODBC system dsn. Under the Options/Datasource-page 2 there is a option for bytea as LO. Clicked that and now it works like a champ.
I found a similar issue when replicating some Forum data from PostgreSQL to MSSQL, using the PostgreSQL 64-bit driver and a Linked Server (.
When I coded like this:...
select * into Post from OpenQuery(PostgreSQL_Test1, 'select * From public.post')
... the MSSQL table defaulted to a column size of nvarchar(4000).
My fix: First, run it once with a small limit on the number of rows copied:
select * into Post from OpenQuery(PostgreSQL_Test1, 'select * From public.post limit 10')
Next, right-click on the local Post table. Choose "Script table as drop and create"
In the create script, replace the size of the offending column with VARCHAR(MAX)
Next, create the table.
Then use:
Insert Post select * from OpenQuery(PostgreSQL_Test1, 'select * From public.post')
Hope that helps.
Your mileage may vary.
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)].