I'm trying to avoid database access in loops within a project I am working on. Not being too good with SQL, I'm not sure of the best way to approach this.
I'm updating a stock level database in a sale procedure with multiple stock locations/pick locations.
Therefore, this is what I am doing.
Looping through Product IDs, then looping through the pick locations for each product and updating quantities as it goes, like:
For Each wProductId In calculatedProds.Keys '' loop through products requested passing values of pick locations
For i = 0 To locationCount ' split the location value from the string as per above
Dim thisLocation As Integer = locationID
Dim thisQty As Integer = qtyPicked
Dim sql As String = "UPDATE `stockLevels` SET `stockLevel`=`stockLevel` - '" & thisQty & "' WHERE `stockLocation`='" & thisLocation & "' AND `id`='" & wProductId & "'"
' DO DATA ACCESS WITH SQL ABOVE
Next
Next
Of course this works, but it is opening a new Database Connection for every stock location, for every item.
So how would I work this into a single Update Statement?
http://www.karlrixon.co.uk/writing/update-multiple-rows-with-different-values-and-a-single-sql-query/
That link gets me very close to what I am after, I think, but I am not 100% sure how to dynamically build that SQL statement and how to add two conditions to the CASE.
I need to build a SQL Statement something like:
UPDATE stockLevels
SET stockLevel= CASE id
WHEN '"& wProductId &"' AND stockLocation='"& thisLocation &"' THEN `stockLevel` - '" & thisQty & "'
WHEN '"& NEXTwProductId &"' AND stockLocation='"& NEXTthisLocation &"' THEN `stockLevel` - '" & NEXTthisQty & "'
END
But that's not correct where I am adding the second parameter to the CASE!
I am using MySQL and VB.NET, as usual, any help much appreciated.
Using a case statement in the set clause in your example isn't ideal for a number of reasons.
There is no where clause to help the database execute the query efficiently
The size of the query for large numbers of updates becomes excessive (consider updating 1000 rows like this)
You are manually implementing a join - the database can almost certainly do this more efficiently than you.
Debugging such a query is also difficult.
Instead, you should first measure the performance of the update one at a time approach to see if you actually need to make improvements.
If performance improvements are required, then I would suggest an approach where the updates are first bulk inserted into a temporary table. A suitable table would have the following columns:
wProductID, stockLocation, newStockLevel
The updates can be bulk inserted using the following MySQL syntax:
INSERT INTO temp_stock_updates
(wProductID, stockLocation, newStockLevel)
VALUES
(?,?,?), (?,?,?), (?,?,?), ...
And then a single update is run to update the main table. This query would look something like this:
UPDATE stockLevels s
JOIN temp_stock_updates u USING (wProductID, stockLocation)
SET
s.stockLevel = u.newStockLevel
Your CASE expression is simply incorrect syntactically.
There are two kinds of CASE expressions, almost identical to each other and yet slightly different in syntax.
One has the form of
CASE expr
WHEN value1 THEN result1
WHEN value2 THEN result2
...
ELSE result_else
END
The other looks like this:
CASE
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
ELSE result_else
END
And you were essentially attempting to mix these two kinds of CASE.
You probably just need to use the second one (also called search CASE, if I am not much mistaken):
...
CASE
WHEN id = '"& wProductId &"' AND stockLocation='"& thisLocation &"' THEN ...
WHEN id = '"& NEXTwProductId &"' AND stockLocation='"& NEXTthisLocation &"' THEN ...
...
Note that if there's no match and the CASE has no ELSE part, the result will be NULL, so make sure you've covered all the cases, otherwise use an ELSE part like this:
ELSE `stocklevel`
I.e. the CASE will evaluate to the original value of the column being updated, rendering no update for it in the end.
Related
Any help that can be provided to a Access and VB noob would be greatly appreciated. What I'm trying to do is concatenate the values from one table and insert it as a comma delimited value into a field in another table. I'm trying to take all the server names that are say Linux boxes and concatenate them into a different field.
Table A looks like this
Machine Name | Zone | Operating System
----------------------------------------
Server01 Zone A Linux
Server02 Zone B Linux
Server03 Zone A Windows
Server04 Zone C Windows
Server05 Zone B Solaris
Table B has the field I want to insert into: Affected_Machine_Names.
Now, I've tried looking through the Concatenate/Coalesce posts, but the SQL view in Access doesn't like the Declare statements. My VB skills suck badly and I can't seem to get the code to work in VB for Applications. Unfortunately, I can't get this database converted into our SQL farm cause I don't have a server available at the moment to host it.
Can anyone point me in the right direction?
You can use Concatenate values from related records by Allen Browne for this. Copy the function code from that web page and paste it into a new standard module. Save the module and give the module a name different from the function name; modConcatRelated would work.
Then I think you should be able to use the function in a query even though you're not proficient with VBA.
First notice I changed the field names in TableA to replace spaces with underscores. With that change, this query ...
SELECT
sub.Operating_System,
ConcatRelated("Machine_Name", "TableA",
"Operating_System = '" & sub.Operating_System & "'") AS Machines
FROM [SELECT DISTINCT Operating_System FROM TableA]. AS sub;
... produces this result set:
Operating_System Machines
Linux Server01, Server02
Solaris Server05
Windows Server03, Server04
If you can't rename the fields as I did, use a separate query to select the distinct operating systems.
SELECT DISTINCT TableA.[Operating System]
FROM TableA;
Save that as qryDistinctOperatingSystems, then use it in this version of the main query:
SELECT
sub.[Operating System],
ConcatRelated("[Machine Name]", "TableA",
"[Operating System] = '" & sub.[Operating System] & "'") AS Machines
FROM qryDistinctOperatingSystems AS sub;
This is a fairly basic VBA function that will loop through every row in a column, and concatenate it to a comma-delimited result string. i.e., for your example, it will return "Server01, Server02, Server03, Server04, Server05". (Don't forget to replace the column and table names)
Function ConcatColumn(OS As String) As String
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("Select * from TableA")
Dim result As String
'For every row after the first, add a comma and the field value:
While rst.EOF = False
If rst.Fields("Operating System") = OS Then _
result = result & ", " & rst.Fields("MyValue")
rst.MoveNext
Wend
'Clean it up a little and put out the result
If Left(result, 2) = ", " Then result = Right(result, Len(result) - 2)
Debug.Print result
ConcatColumn = result
End Function
To use this,
1. ConcatColumn("Windows") will return "Server04, Server03"
2. ConcatColumn("Linux") will return "Server01, Server02"
3. ConcatColumn("Solaris") will return "Server05"
4. ConcatColumn("") will return "".
I'm using an access function that loops through multiple record sets using case select statements and displays rows of strings in the VBA immediate window.
Can anyone suggest how I can learn about methods that might be used to update a table with the results that are currently displayed in the VBA immediate window?
So far, my searches have suggested that DoCmd.SQL might work.
Case Is = "1" ' 1 bottle
Debug.Print rsWCol!r4_wcol_outstring & rsBottl!bottleoutstring
displays the string below in the immediate window:
R41602T50 1 00 62 710 C 1120 9800 550 1 00S #135 0
I'd like to be able to use something like the following
Dim writeRecSQL As String ' used to append records to a temp table
writeRecSQL = "INSERT INTO tbl_R_export_TEMP ( R_str ) select rsWCol!r4_wcol_outstring & rsBottl!bottleoutstring;"
Case Is = "1" ' 1 bottle
' Debug.Print rsWCol!r4_wcol_outstring & rsBottl!bottleoutstring
DoCmd.RunSQL writeRecSQL
The select part of my SQL statement does not seem to be getting values.
I understand that, normally, it might be something like select fieldX from tablex
And that my statement is more like: select rs!foo
An 'Enter Parameter Value' message box is raised asking for the value of r4_wcol_outstring
(Following the SELECT in my SQL with a FROM raises VB "Run-time error '3134' syntax error in INSERT INTO statement.")
I could use some advice or an example of how write an SQL statement that will my record set parameter values.
Good place to start is Microsoft's site:
https://msdn.microsoft.com/en-us/library/bb208861(v=office.12).aspx
But if you just want to insert values from a record you just retrieved into another table
try wrapping fields in escaped double quotes and concetanting the values outside the hardcoded string.
As in:
writeRecSQL = "INSERT INTO tbl_R_export_TEMP ( [R_str] ) VALUES (""" & rsWCol!r4_wcol_outstring & rsBottl!bottleoutstring & """);"
Besides Docmd.RunSQL, you can also look at CurrentDB.Execute and predefined parameter queries using the Querydefs object
I am currently in the process of converting a large amount of ASP classic/VBscript pages from an old database (Unify Dataserver) to MySQL.
Say you have a query like this:
sql = "SELECT c.container_type, c_amount, c_sdate, c_edate, csrt " & _
"FROM containers c, container_chars cc"
objRS.Open sql, objConn, 3, 1
If I want to reference the column "c_edate", I can simply use this and it works fine:
x = objRS("c_edate")
However, when it comes to referencing a column like "c.container_type" (With a . used to differentiate it from another table, like so:
x = objRS("c.container_type")
It will say
ADODB.Recordset error '800a0cc1'
Item cannot be found in the collection corresponding to the requested name or ordinal.
I can fix it by using a number instead:
objRS(0)
This was never an issue until we switched to MySQL. In our old database, using the rs(table.column_name) format worked just fine. But in MySQL, once you add a (.) to the code, it can't find that item unless you switch it to a number.
As you can imagine, this is quite a pain as I go through the 700+ pages of this website manually counting the placement of each column in the corresponding select statement every time something from the query is referenced.
Does anyone know why this is happening or how to make the rs(table.column_name) format work with MySQL like it does with our old database?
In SQL Server, and apparently in MySQL too, the way to reference a field in the result set is to just use the name, without the prefix.
x = objRS("container_type")
The prefix is needed by the database to differentiate between identically-named columns, but once you send the results to a recordset, that recordset doesn't know or care where the columns came from.
The same goes for aliases:
SQL = "SELECT c.container_type AS ctype, [...]"
...
x = objRS("ctype")
Combining these two facts, if you do have identically-named columns in the result set, you must alias at least one of them. If you don't, it won't necessarily give an error, but you will not be able to reference the second column using the rs("name") syntax.
SQL = "SELECT c1.container_type, c2.container_type AS c_type2, ..."
...
x = objRS("container_type")
y = objRS("c_type2")
[Note that while you're at it, you probably should also modify your FROM clauses to use proper FROM table1 INNER JOIN table2 ON table1.fieldA = table2.fieldB type syntax. The FROM table1, table2 WHERE table1.fieldA = table2.fieldB syntax has been deprecated for many years now.]
I have a crosstab query which queries a bunch of locations and gets their measurement readings. I pivot on the measurement readings so I get a table which has all the measurements for a location/date combo on each line. This works fine for getting all the data. It also works fine for filtering on one value per field. i.e. WHERE LocationID = ? AND MeasureID = ? but what I really need is to have something like WHERE LocationID IN (?) AND MeasureID IN (?) where ? is an array (or whatever gets to job done. Is this possible?
On my forms I'm using a DAO.QueryDef object to build my recordsets. I'd like to avoid building the entire query string in VBA if possible, mostly because this particular query is pretty long and I'd rather it live in a view and not a code module. With that said I can build it all in VBA but it's just not the desired solution.
You can always use replace.
sSQL = "SELECT lots of sql WHERE LocationID IN (qqlocidqq)"
sSQLWithLoc = Replace (sSQL, "qqlocidqq", "1,2,3,4")
Dim qdf As QueryDef
'A query that exists
Set qdf= CurrentDB.QueryDefs("MyJunkQuery")
'Permanently change the sql of that query
qdf.SQL = sSQLWithLoc
Looking into this a little further, it may suit you to use Instr, like so:
SELECT Table1.LocationID
FROM Table1
WHERE InStr([#List],[LocationID])>0
Tested like so:
PARAMETERS Number_List Text(50);
TRANSFORM Count(Table1.AKey) AS CountOfAKey
SELECT Table1.AText
FROM Table1
WHERE InStr([Number_List],[ANumber])>0
GROUP BY Table1.AText
PIVOT Table1.ANumber;
Where Table1 consists of fields AKey, AText, and ANumber. Number_List is a comma separated list of numbers supplied by a parameter. Instr checks for the existence of ANumber from Table1 in the supplied parameter.
There is a problem with overlap 1,2,12, but a creative use of commas may suit:
WHERE InStr("," & [Number_List] & "," , "," & [ANumber] & ",")>0
Of course the delimiter does not have to be a comma, | is often useful.
Any help that can be provided to a Access and VB noob would be greatly appreciated. What I'm trying to do is concatenate the values from one table and insert it as a comma delimited value into a field in another table. I'm trying to take all the server names that are say Linux boxes and concatenate them into a different field.
Table A looks like this
Machine Name | Zone | Operating System
----------------------------------------
Server01 Zone A Linux
Server02 Zone B Linux
Server03 Zone A Windows
Server04 Zone C Windows
Server05 Zone B Solaris
Table B has the field I want to insert into: Affected_Machine_Names.
Now, I've tried looking through the Concatenate/Coalesce posts, but the SQL view in Access doesn't like the Declare statements. My VB skills suck badly and I can't seem to get the code to work in VB for Applications. Unfortunately, I can't get this database converted into our SQL farm cause I don't have a server available at the moment to host it.
Can anyone point me in the right direction?
You can use Concatenate values from related records by Allen Browne for this. Copy the function code from that web page and paste it into a new standard module. Save the module and give the module a name different from the function name; modConcatRelated would work.
Then I think you should be able to use the function in a query even though you're not proficient with VBA.
First notice I changed the field names in TableA to replace spaces with underscores. With that change, this query ...
SELECT
sub.Operating_System,
ConcatRelated("Machine_Name", "TableA",
"Operating_System = '" & sub.Operating_System & "'") AS Machines
FROM [SELECT DISTINCT Operating_System FROM TableA]. AS sub;
... produces this result set:
Operating_System Machines
Linux Server01, Server02
Solaris Server05
Windows Server03, Server04
If you can't rename the fields as I did, use a separate query to select the distinct operating systems.
SELECT DISTINCT TableA.[Operating System]
FROM TableA;
Save that as qryDistinctOperatingSystems, then use it in this version of the main query:
SELECT
sub.[Operating System],
ConcatRelated("[Machine Name]", "TableA",
"[Operating System] = '" & sub.[Operating System] & "'") AS Machines
FROM qryDistinctOperatingSystems AS sub;
This is a fairly basic VBA function that will loop through every row in a column, and concatenate it to a comma-delimited result string. i.e., for your example, it will return "Server01, Server02, Server03, Server04, Server05". (Don't forget to replace the column and table names)
Function ConcatColumn(OS As String) As String
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("Select * from TableA")
Dim result As String
'For every row after the first, add a comma and the field value:
While rst.EOF = False
If rst.Fields("Operating System") = OS Then _
result = result & ", " & rst.Fields("MyValue")
rst.MoveNext
Wend
'Clean it up a little and put out the result
If Left(result, 2) = ", " Then result = Right(result, Len(result) - 2)
Debug.Print result
ConcatColumn = result
End Function
To use this,
1. ConcatColumn("Windows") will return "Server04, Server03"
2. ConcatColumn("Linux") will return "Server01, Server02"
3. ConcatColumn("Solaris") will return "Server05"
4. ConcatColumn("") will return "".