SQL queries in batch don't execute - sql

My project is in Visual Foxpro and I use MS SQL server 2008. When I fire sql queries in batch, some of the queries don't execute. However, no error is thrown. I haven't used BEGIN TRAN and ROLLBACK yet. What should be done ??

that all depends... You don't have any sample of your queries posted to give us an indication of possible failure. However, one thing I've had good response with from VFP to SQL is to build into a string (I prefer using TEXT/ENDTEXT for readabilty), then send that entire value to SQL. If there are any "parameter" based values that are from VFP locally, you can use "?" to indicate it will come from a variable to SQL. Then you can batch all in a single vs multiple individual queries...
vfpField = 28
vfpString = 'Smith'
text to lcSqlCmd noshow
select
YT.blah,
YT.blah2
into
#tempSqlResult
from
yourTable YT
where
YT.SomeKey = ?vfpField
select
ost.Xblah,
t.blah,
t.blah2
from
OtherSQLTable ost
join #tempSqlResult t
on ost.Xblah = t.blahKey;
drop table #tempSqlResult;
endtext
nHandle = sqlconnect( "your connection string" )
nAns = sqlexec( nHandle, lcSqlCmd, "LocalVFPCursorName" )
No I don't have error trapping in here, just to show principle and readability. I know the sample query could have easily been done via a join, but if you are working with some pre-aggregations and want to put them into temp work areas like Localized VFP cursors from a query to be used as your next step, this would work via #tempSqlResult as "#" indicates temporary table on SQL for whatever the current connection handle is.
If you want to return MULTIPLE RESULT SETs from a single SQL call, you can do that too, just add another query that doesn't have an "into #tmpSQLblah" context. Then, all instances of those result cursors will be brought back down to VFP based on the "LocalVFPCursorName" prefix. If you are returning 3 result sets, then VFP will have 3 cursors open called
LocalVFPCursorName
LocalVFPCursorName1
LocalVFPCursorName2
and will be based on the sequence of the queries in the SqlExec() call. But if you can provide more on what you ARE trying to do and their samples, we can offer more specific help too.

Related

Dealing with filtered Pass Through Query in MS Access

I have a relatively complex SQL query (complex to run in Access) and want to run it in MS Access. It works with the pass-through query well but going forward I will face an issue that is related to a filter I apply in the query. I select the current report date within the where function. Below is a part of my query I try to handle ;
select LS.PID_FACILITY, LS.ASOF_DTE, LS.DATA_CYCLE_FLG, LS.CUST_ACC, LS.CUST_SMUN, LS.CUST_NME, LS.CUST_CTY,
WHERE LS.ASOF_DTE='19-SEP-22'
I do not want to change asof_dte filter manually everyday. If this was a normal access query I could join another table that includes only the current report date. But I cannot do it in a pass-through query. What is the alternative way to do it? I read something about creating variables or strings, but I could not relate them to my problem, since I am a beginner at creating such solutions.
Thank you all.
Well, two VERY intresting things here.
First, YES a great idea to include the date in the PT query. But, you don't want to change that date each time.
Soluton:
Add a paramter to the query, and then from Access code add that paramter. It is VERY easy to do this (one line of code!!! - don't adopt the zillion examples out there that has a boatload of ADO code - NOT required!.
However, BEFORE we start dealing with above?
A MUCH better and simple, less work way to approach this?
in place of stored procedure?
if possbile, create a view. and use that for the report.
Why?
Because you then get TWO VERY valuable bonus.
First, you can freely use the reports "where" clause, and it respects the where clause and STILL runs server side!!!
In other words, create a view for that existing query, but WITHOUT the date set in that view.
You then link to the view from access client side.
Now, to open (filter) the report, you can do this:
docmd.OpenReport "MyReport",acViewPreview,,"LS.ASOF_DTE='19-SEP-22'"
Now, of couse the above "where" clause can be a varible (string).
NOTE SUPER but SUPER careful here:
If you base the reprot on a pass-though query (that then uses the stored procedure), then the filter occures CLIENT SIDE!!!! (all rows will be returned and THEN filtered if you report is based on that stored procedure.
But, if you use a view?
The the filter makes it to the server side!!!!
While both the pass-through query or the "view" can be filtered with the above "open report" and the where clause we have above?
The view will still filter server side - the pass-though query will NOT!!!
Now, the 3rd way, is of course to build the stored procedure to accept a date parmater.
You then could do this:
with Currentdb.QueryDefs("MyPassThoughQueryGoesHere")
.SQL = "EXEC MyStoreProc " + "19-SEP-22"
END WITH
docmd.OpenReport "MyReport",acViewPreview
So, you CAN add and have a PT query and add a paramter as per above.
However, unless that stored procedure has some speical code, you are MUCH better off to create a view server side, base the reprot on that view, and simple pass + use the traditional "where" clause of the open report command. Even if that view has no filter, returns all rows in the table?
With the "where" clause of the open report command, ONLY those rows meeting that critera will be pulled down the network pipe.
So, say a invoice table with 1 million rows.
Create a view, link the view in access.
base report on that view.
Now, do this:
docmd.OpenReport "rptInvoice",,,"InvoiceNum = 134343"
The above will ONLY PULL down 1 row from the server. Even if the view has no filter and would return 1 million rows.
So, using a view is less work then creating the stored procedure.
But, you can modify the stored procedure to accept a paramter, and then as noted use the above example to modify the PT query you have, and THEN open the report.
I think overall, it is less work to use view. Furthermore, if you have a slow running report now?
Replace the query (move it) to sql server side. Get it working. Now link to that view (give it same name as what the client side query was in Access).
Now, EVEN if you had some fancy filter code in VBA, and used openReport with the "where" clause? It will now work, only pull the records down the network pipe, you get stored procedure performance without the hassles. and the date format and "where" clause for open report is access/VBA style - not sql server style SQL.
So, high recommend you try and dump the stored procedure and use a view (and EVEN better is any where clause works - not just one based on pre-defined parameters for the stored procedure - so you not limited to parameters)
. However, no big deal - the above "EXEC dbo.MyStoreProce " & strDate example would also work fine if you have a date parameter you wish to supply to the pass-though query.

UPDATE query is not "an updateable query" [duplicate]

On some Microsoft Access queries, I get the following message: Operation must use an updatable query. (Error 3073). I work around it by using temporary tables, but I'm wondering if there's a better way. All the tables involved have a primary key. Here's the code:
UPDATE CLOG SET CLOG.NEXTDUE = (
SELECT H1.paidthru
FROM CTRHIST as H1
WHERE H1.ACCT = clog.ACCT AND
H1.SEQNO = (
SELECT MAX(SEQNO)
FROM CTRHIST
WHERE CTRHIST.ACCT = Clog.ACCT AND
CTRHIST.AMTPAID > 0 AND
CTRHIST.DATEPAID < CLOG.UPDATED_ON
)
)
WHERE CLOG.NEXTDUE IS NULL;
Since Jet 4, all queries that have a join to a SQL statement that summarizes data will be non-updatable. You aren't using a JOIN, but the WHERE clause is exactly equivalent to a join, and thus, the Jet query optimizer treats it the same way it treats a join.
I'm afraid you're out of luck without a temp table, though maybe somebody with greater Jet SQL knowledge than I can come up with a workaround.
BTW, it might have been updatable in Jet 3.5 (Access 97), as a whole lot of queries were updatable then that became non-updatable when upgraded to Jet 4.
--
I had a similar problem where the following queries wouldn't work;
update tbl_Lot_Valuation_Details as LVD
set LVD.LGAName = (select LGA.LGA_NAME from tbl_Prop_LGA as LGA where LGA.LGA_CODE = LVD.LGCode)
where LVD.LGAName is null;
update tbl_LOT_VALUATION_DETAILS inner join tbl_prop_LGA on tbl_LOT_VALUATION_DETAILS.LGCode = tbl_prop_LGA.LGA_CODE
set tbl_LOT_VALUATION_DETAILS.LGAName = [tbl_Prop_LGA].[LGA_NAME]
where tbl_LOT_VALUATION_DETAILS.LGAName is null;
However using DLookup resolved the problem;
update tbl_Lot_Valuation_Details as LVD
set LVD.LGAName = dlookup("LGA_NAME", "tbl_Prop_LGA", "LGA_CODE="+LVD.LGCode)
where LVD.LGAName is null;
This solution was originally proposed at https://stackoverflow.com/questions/537161/sql-update-woes-in-ms-access-operation-must-use-an-updateable-query
The problem defintely relates to the use of (in this case) the max() function. Any aggregation function used during a join (e.g. to retrieve the max or min or avg value from a joined table) will cause the error. And the same applies to using subqueries instead of joins (as in the original code).
This is incredibly annoying (and unjustified!) as it is a reasonably common thing to want to do. I've also had to use temp tables to get around it (pull the aggregated value into a temp table with an insert statement, then join to this table with your update, then drop the temp table).
Glenn
There is no error in the code. But the error is Thrown because of the following reason.
- Please check weather you have given Read-write permission to MS-Access database file.
- The Database file where it is stored (say in Folder1) is read-only..?
suppose you are stored the database (MS-Access file) in read only folder, while running your application the connection is not force-fully opened. Hence change the file permission / its containing folder permission like in C:\Program files all most all c drive files been set read-only so changing this permission solves this Problem.
I know my answer is 7 years late, but here's my suggestion anyway:
When Access complains about an UPDATE query that includes a JOIN, just save the query, with RecordsetType property set to Dynaset (Inconsistent Updates).
This will sometimes allow the UPDATE to work.
Thirteen years later I face the same issue. DISTINCTROW did not solve my problem, but dlookup did.
I need to update a table from an aggregate query. As far as I understand, MS Access always assumes that de junction between the to-update table and the aggregate query is one-to-many., even though unique records are assured in the query.
The use of dlookup is:
DLOOKUP(Field, SetOfRecords, Criteria)
Field: a string that identifies the field from which the data is retrieved.
SetOfRecords: a string that identifies the set o record from which the field is retrieved, being a table name or a (saved) query name (that doesn’t require parameters).
Criteria: A string used to restrict the range of data on which the DLookup function is performed, equivalent to the WHERE clause in an SQL expression, without the word WHERE.
Remark
If more than one field meets criteria, the DLookup function returns the first occurrence. You should specify criteria that will ensure that the field value returned by the DLookup function is unique.
The query that worked for me is:
UPDATE tblTarifaDeCorretagem
SET tblTarifaDeCorretagem.ValorCorretagem =
[tblTarifaDeCorretagem].[TarifaParteFixa]+
DLookUp(
"[ParteVariável]",
"cstParteVariavelPorOrdem",
"[IdTarifaDeCorretagem] = " & [tblTarifaDeCorretagem].[IdTarifaDeCorretagem]
);
I would try building the UPDATE query in Access. I had an UPDATE query I wrote myself like
UPDATE TABLE1
SET Field1 =
(SELECT Table2.Field2
FROM Table2
WHERE Table2.UniqueIDColumn = Table1.UniqueIDColumn)
The query gave me that error you're seeing. This worked on my SQL Server though, but just like earlier answers noted, Access UPDATE syntax isn't standard syntax. However, when I rebuilt it using Access's query wizard (it used the JOIN syntax) it worked fine. Normally I'd just make the UPDATE query a passthrough to use the non-JET syntax, but one of the tables I was joining with was a local Access table.
This occurs when there is not a UNIQUE MS-ACCESS key for the table(s) being updated. (Regardless of the SQL schema).
When creating MS-Access Links to SQL tables, you are asked to specify the index (key) at link time. If this is done incorrectly, or not at all, the query against the linked table is not updatable
When linking SQL tables into Access MAKE SURE that when Access prompts you for the index (key) you use exactly what SQL uses to avoid problem(s), although specifying any unique key is all Access needs to update the table.
If you were not the person who originally linked the table, delete the linked table from MS-ACCESS (the link only gets deleted) and re-link it specifying the key properly and all will work correctly.
(A little late to the party...)
The three ways I've gotten around this problem in the past are:
Reference a text box on an open form
DSum
DLookup
I had the same issue.
My solution is to first create a table from the non updatable query and then do the update from table to table and it works.
Mine failed with a simple INSERT statement. Fixed by starting the application with 'Run as Administrator' access.
MS Access - joining tables in an update query... how to make it updatable
Open the query in design view
Click once on the link b/w tables/view
In the “properties” window, change the value for “unique records” to “yes”
Save the query as an update query and run it.
You can always write the code in VBA that updates similarly. I had this problem too, and my workaround was making a select query, with all the joins, that had all the data I was looking for to be able to update, making that a recordset and running the update query repeatedly as an update query of only the updating table, only searching the criteria you're looking for
Dim updatingItems As Recordset
Dim clientName As String
Dim tableID As String
Set updatingItems = CurrentDb.OpenRecordset("*insert SELECT SQL here*");", dbOpenDynaset)
Do Until updatingItems .EOF
clientName = updatingItems .Fields("strName")
tableID = updatingItems .Fields("ID")
DoCmd.RunSQL "UPDATE *ONLY TABLE TO UPDATE* SET *TABLE*.strClientName= '" & clientName & "' WHERE (((*TABLE*.ID)=" & tableID & "))"
updatingItems.MoveNext
Loop
I'm only doing this to about 60 records a day, doing it to a few thousand could take much longer, as the query is running from start to finish multiple times, instead of just selecting an overall group and making changes. You might need ' ' around the quotes for tableID, as it's a string, but I'm pretty sure this is what worked for me.
I kept getting the same error until I made the connecting field a unique index in both connecting tables. Only then did the query become updatable.
Philip Stilianos
In essence, while your SQL looks perfectly reasonable, Jet has never supported the SQL standard syntax for UPDATE. Instead, it uses its own proprietary syntax (different again from SQL Server's proprietary UPDATE syntax) which is very limited. Often, the only workarounds "Operation must use an updatable query" are very painful. Seriously consider switching to a more capable SQL product.
For some more details about your specific problems and some possible workarounds, see Update Query Based on Totals Query Fails.
I kept getting the same error, but all SQLs execute in Access very well.
and when I amended the permission of AccessFile.
the problem fixed!!
I give 'Network Service' account full control permission, this account if for IIS
When I got this error, it may have been because of my UPDATE syntax being wrong, but after I fixed the update query I got the same error again...so I went to the ODBC Data Source Administrator and found that my connection was read-only. After I made the connection read-write and re-connected it worked just fine.
Today in my MS-Access 2003 with an ODBC tabla pointing to a SQL Server 2000 with sa password gave me the same error.
I defined a Primary Key on the table in the SQL Server database, and the issue was gone.
There is another scenario here that would apply. A file that was checked out of Visual Source Safe, for anyone still using it, that was not given "Writeablity", either in the View option or Check Out, will also recieve this error message.
Solution is to re-acquire the file from Source Safe and apply the Writeability setting.
To further answer what DRUA referred to in his/her answer...
I develop my databases in Access 2007. My users are using access 2007 runtime. They have read permissions to a database_Front (front end) folder, and read/write permissions to the database_Back folder.
In rolling out a new database, the user did not follow the full instructions of copying the front end to their computer, and instead created a shortcut. Running the Front-end through the shortcut will create a condition where the query is not updateable because of the file write restrictions.
Copying the front end to their documents folder solves the problem.
Yes, it complicates things when the users have to get an updated version of the front-end, but at least the query works without having to resort to temp tables and such.
check your DB (Database permission) and give full permission
Go to DB folder-> right click properties->security->edit-> give full control
& Start menu ->run->type "uac" make it down (if it high)
The answer given above by iDevlop worked for me. Note that I wasn't able to find the RecordsetType property in my update query. However, I was able to find that property by changing my query to a select query, setting that property as iDevlop noted and then changing my query to an update query. This worked, no need for a temp table.
I'd have liked for this to just be a comment to what iDevlop posted so that it flowed from his solution, but I don't have a high enough score.
I solved this by adding "DISTINCTROW"
so here this would be
UPDATE DISTINCTROW CLOG SET CLOG.NEXTDUE

Moving from Access backend to SQL Server as be. Efficiency help needed

I am working on developing an application for my company. From the beginning we were planning on having a split DB with an access front end, and storing the back end data on our shared server. However, after doing some research we realized that storing the data in a back end access DB on a shared drive isn’t the best idea for many reasons (vpn is so slow to shared drive from remote offices, access might not be the best with millions of records, etc.). Anyways, we decided to still use the access front end, but host the data on our SQL server.
I have a couple questions about storing data on our SQL server. Right now when I insert a record I do it with something like this:
Private Sub addButton_Click()
Dim rsToRun As DAO.Recordset
Set rsToRun = CurrentDb.OpenRecordset("SELECT * FROM ToRun")
rsToRun.AddNew
rsToRun("MemNum").Value = memNumTextEntry.Value
rsToRun.Update
memNumTextEntry.Value = Null
End Sub
It seems like it is inefficient to have to use a sql statement like SELECT * FROM ToRun and then make a recordset, add to the recordset, and update it. If there are millions of records in ToRun will this take forever to run? Would it be more efficient just to use an insert statement? If so, how do you do it? Our program is still young in development so we can easily make pretty substantial changes. Nobody on my team is an access or SQL expert so any help is really appreciated.
If you're working with SQL Server, use ADO. It handles server access much better than DAO.
If you are inserting data into a SQL Server table, an INSERT statement can have (in SQL 2008) up to 1000 comma-separated VALUES groups. You therefore need only one INSERT for each 1000 records. You can just append additional inserts after the first, and do your entire data transfer through one string:
INSERT INTO ToRun (MemNum) VALUES ('abc'),('def'),...,('xyz');
INSERT INTO ToRun (MemNum) VALUES ('abcd'),('efgh'),...,('wxyz');
...
You can assemble this in a string, then use an ADO Connection.Execute to do the work. It is frequently faster than multiple DAO or ADO .AddNew/.Update pairs. You just need to remember to requery your recordset afterwards if you need it to be populated with your newly-inserted data.
There are actually two questions in your post:
Will OpenRecordset("SELECT * FROM ToRun") immediately load all recordsets?
No. By default, DAO's OpenRecordset opens a server-side cursor, so the data is not retrieved until you actually start to move around the recordset. Still, it's bad practice to select lots of rows if you don't need to. This leads to the next question:
How should I add records in an attached SQL Server database?
There are a few ways to do that (in order of preference):
Use an INSERT statment. That's the most elegant and direct solution: You want to insert something, so you execute INSERT, not SELECT and AddNew. As Monty Wild explained in his answer, ADO is prefered. In particular, ADO allows you to use parameterized commands, which means that you don't have to put-into-quotes-and-escape your strings and correctly format your dates, which is not so easy to do right.
(DAO also allows you to execute INSERT statements (via CurrentDb.Execute), but it does not allow you to use parameters.)
That said, ADO also supports the AddNew syntax familiar to you. This is a bit less elegant but requires less changes to your existing code.
And, finally, your old DAO code will still work. As always: If you think you have a performance problem, measure if you really have one. Clean code is great, but refactoring has a cost and it makes sense to optimize those places first where it really matters. Test, measure... then optimize.
It seems like it is inefficient to have to use a sql statement like SELECT * FROM ToRun and then make a recordset, add to the recordset, and update it. If there are millions of records in ToRun will this take forever to run?
Yes, you do need to load something from the table in order to get your Recordset, but you don't have to load any actual data.
Just add a WHERE clause to the query that doesn't return anything, like this:
Set rsToRun = CurrentDb.OpenRecordset("SELECT * FROM ToRun WHERE 1=0")
Both INSERT statements and Recordsets have their pros and cons.
With INSERTs, you can insert many records with relatively little code, as shown in Monty Wild's answer.
On the other hand, INSERTs in the basic form shown there are prone to SQL Injection and you need to take care of "illegal" characters like ' inside your values, ideally by using parameters.
With a Recordset, you obviously need to type more code to insert a record, as shown in your question.
But in exchange, a Recordset does some of the work for you:
For example, in the line rsToRun("MemNum").Value = memNumTextEntry.Value you don't have to care about:
characters like ' in the input, which would break an INSERT query unless you use parameters
SQL Injection
getting the date format right when inserting date/time values

SQL Parameters - where does expansion happens

I'm getting a little confused about using parameters with SQL queries, and seeing some things that I can't immediately explain, so I'm just after some background info at this point.
First, is there a standard format for parameter names in queries, or is this database/middleware dependent ? I've seen both this:-
DELETE * FROM #tablename
and...
DELETE * FROM :tablename
Second - where (typically) does the parameter replacement happen? Are parameters replaced/expanded before the query is sent to the database, or does the database receive params and query separately, and perform the expansion itself?
Just as background, I'm using the DevArt UniDAC toolkit from a C++Builder app to connect via ODBC to an Excel spreadsheet. I know this is almost pessimal in a few ways... (I'm trying to understand why a particular command works only when it doesn't use parameters)
With such data access libraries, like UniDAC or FireDAC, you can use macros. They allow you to use special markers (called macro) in the places of a SQL command, where parameter are disallowed. I dont know UniDAC API, but will provide a sample for FireDAC:
ADQuery1.SQL.Text := 'DELETE * FROM &tablename';
ADQuery1.MacroByName('tablename').AsRaw := 'MyTab';
ADQuery1.ExecSQL;
Second - where (typically) does the parameter replacement happen?
It doesn't. That's the whole point. Data elements in your query stay data items. Code elements stay code elements. The two never intersect, and thus there is never an opportunity for malicious data to be treated as code.
connect via ODBC to an Excel spreadsheet... I'm trying to understand why a particular command works only when it doesn't use parameters
Excel isn't really a database engine, but if it were, you still can't use a parameter for the name a table.
SQL parameters are sent to the database. The database performs the expansion itself. That allows the database to set up a query plan that will work for different values of the parameters.
Microsoft always uses #parname for parameters. Oracle uses :parname. Other databases are different.
No database I know of allows you to specify the table name as a parameter. You have to expand that client side, like:
command.CommandText = string.Format("DELETE FROM {0}", tableName);
P.S. A * is not allowed after a DELETE. After all, you can only delete whole rows, not a set of columns.

Running multiple SQL statements in the one operation

I'm trying to use ADO to create several tables at once, into MS Access. Is it possible to do multiple statements in the one operation? For instance:
...
// I have omitted the field details
CString sQuery = "CREATE TABLE [Table1] (..., PRIMARY KEY ([ID])); \nCREATE TABLE [Table2] (..., PRIMARY KEY ([ID]));";
oRecordset.Open(oDatabase.m_pConnection, sQuery)
This fails due to a "Syntax Error in CREATE TABLE statement", although each of the create statements work on their own perfectly. Is there a way of doing this sort of thing? There will also be statements to add constraints, add indexing, etc., and I'd really like to be able to do it so that I don't have to split up the string into separate parts.
ADO isn't the issue: the ACE/Jet engine simply does not support multiple SQL statements within a single operation. In other words, ACE/JET SQL lacks procedural syntax found in most 'industrial-strength' SQL products. See #David-W-Fenton's answer for more detail.
Bottom line: You will need to issue a Connection.Execute for each CREATE TABLE statement i.e. client side procedural code. But they can (perhaps should) all be run in the same transaction, of course.
ADO to MS Access does not support batch SQL statements. You need to run each statement as a separate execution.
People who think you can send multiple SQL statements to Jet in a batch just aren't thinking.
Jet is a file-server database engine -- there is no centralized server process controlling interaction between clients and the actual data store. Instead, clients are all running individual instances of Jet and cooperatively editing a file in a way that is controlled by the Jet locking file (LDB). Without a centralized process to serialize and prioritize the SQL statements, you wouldn't want Jet to be able to process multiple statements in a batch.
Those who are offering the suggestion of using ADO and separating the statements with a CrLf should code it up and give it a try and then get back to us about how useful their speculative advice actually is.
If you're sample set of commands is typical, just do something like this in VBA or the language of your choice:
public sub ExeuteBatch(BatchString as String)
var s as string
var abatch as array
sbatch = replace(sbatch, "\n", "")
abatch = split(BatchString, ";")
for each s in abatch
** adodb execute s here **
next s
end sub
That's off the top of my head, but you should be able to take it from there I hope.
Crude but it works - create the necessary number of queries with one SQL statement each, then use a Macro to run the queries successively. That's about as good as can be done with ADO/Jet.
I don't know if ADO is constructed over JET OleDB Engine, which I suppose, if it is this way, The Jet Engine doesn't support execution of multiple statements in one single batch, we tryed separating with ; and with the GO reserved word, but it does not work.
I think you can run multiple commands in one ADO Command.
You just need proper line feeds between then. i.e. \n doesn't work.
Try something like this:
(Using VB Syntaxish)
MyQuery = "Select * from Whatever " & vbLf <br>
MyQuery = MyString & "Select * from SomethingElse " & vbLF
oRecordset.Open(oDatabase.m_pConnection, MyQuery )