|
|
| Introduction to Stored Procedures and User Defined Functions in SQL Server |
Contact Us
|
The Atlanta FoxPro Users Group
February 19, 2004
Using SQL Server 2000, we will discuss and demonstrate several examples and types of stored procedures
and user-defined functions. Then, we'll demonstrate setting up a connection to the database from
FoxPro, executing stored procedures from FoxPro and utilizing the stored procedure result set in FoxPro.
|
|
"Where is all the knowledge we lost with information?" -- T.S. Elliot
|
|
"Those parts of the system that you can hit with a hammer are called hardware; those program instructions that you can only curse at are called software." -- Anonymous
|
Why is it important to FoxPro developers?
- Bypasses a major FoxPro weakness.
- Client/server capabilities.
- Career-enhancing: being a developer with DBA skills is very sought-after; the trend will get stronger with Yukon. It's easier for a developer to become
a DBA than for a DBA to become developer.
Resources:
What we're not going to cover:
- Versions other than SQL Server 2000
- Security and roles
- SQL Injection
- Index tuning
- Other performance optimizations
SQL Server and MSDE
MSDE 2000 is a redistributable, scaled-down version of SQL Server 2000 and was introduced to provide application developers a database engine that is more powerful than the Jet engine and at the same time expandable to SQL Server. It is ideal for client applications requiring an embedded database and websites serving up to 25 concurrent users.
MSDE 2000 provides a cost-effective option for developers who need a database server that can be easily distributed and installed. Because it is fully compatible with SQL Server, developers can easily target both SQL Server and MSDE 2000 with the same application code base. This provides a seamless upgrade path from MSDE 2000 to SQL Server if an application grows beyond the storage and scalability limits of MSDE 2000.
Unlike SQL Server, MSDE does not have its own GUI interface (Enterprise Manager) for database design. MSDE ships with only osql utility and it's very difficult to create and manage databases from the command prompt. You can only create and manage MSDE databases via third-party tools and applications
MSDE is intended for single user or small workgroup environments. The following are some of the MSDE limitations in comparison with SQL Server:
- No Enterprise Manager, Query Analyzer, Profiler or Index Tuning Wizard
- Only supoprts 2GB RAM, 2 CPUs, 2GB database size limit
- Only five concurrent batch workloads or 25 concurrent connections for websites
- No Database Server Failover Support
- No Full-text search, English Query or OLAP support
- No Import and Export Wizards
- No Full or Bulk-Logged recovery model support (only Simple)
- No SQL Books Online
Alternatives to Enterprise Manager
Get SQL Server with an MSDN subscription or with Small Business Server
|
The Enterprise Manager is a graphical interface tool used to administer your SQL Servers. You can use the Enterprise Manager to configure SQL Server options, create/edit/view databases, perform maintenance and backups, and do quite a few more tasks that we will look at over the next few weeks.
A good introduction is here.
- Organization
- Tables and Indexes
- Stored Procedures, Views and User-Defined Functions
- Other Areas
|
Stored procedures are collections of SQL statements and control-of-flow language. Stored procedures differ from ordinary SQL statements and from batches of SQL statements in that they are pre-compiled. The first time you run a procedure, SQL Server's query processor analyzes it and prepares an execution plan that is ultimately stored in a system table. The subsequent execution of the procedure is according to the stored plan. Since most of the query processing work has already been performed, stored procedures execute almost instantaneously.
Stored procedures are extremely similar to the constructs seen in other programming languages. They accept data in the form of input parameters that are specified at execution time. These input parameters (if implemented) are utilized in the execution of a series of statements that produce some result. This result is returned to the calling environment through the use of a recordset, output parameters and/or a return code.
Benefits:
- Precompiled:
SQL Server compiles each stored procedure once and then reutilizes the execution plan. This results in tremendous performance boosts when stored procedures are called repeatedly.
- Reduced client/server traffic:
If network bandwidth is a concern in your environment, you'll be happy to learn that stored procedures can reduce long SQL queries to a single line that is transmitted over the wire.
- Efficient reuse of code:
Multiple users and client programs can use stored procedures. If you utilize them in a planned manner, you'll find the development cycle takes less time.
- Enhanced security controls:
You can grant users permission to execute a stored procedure independently of underlying table permissions.
- They allow faster execution:
If the operation requires a large amount of Transact-SQL code or is performed repetitively, stored procedures can be faster than batches of Transact-SQL code. They are parsed and optimized when they are created, and an in-memory version of the procedure can be used after the procedure is executed the first time. Transact-SQL statements repeatedly sent from the client each time they run are compiled and optimized every time SQL Server executes them.
- Another benefit is that you can execute a stored procedure on either a local or remote SQL Server. This enables you to run processes on other machines and work with information across servers, not just local databases.
- An application program written in Visual FoxPro can also execute stored procedures, providing an optimum solution between the client-side software and SQL Server.
Creating and Running a Stored Procedure
create procedure all_employees
as select * from employees
go
exec all_employees
name department badge
-------------------- -------------------- -----------
Brat Smith Sales 1234
Karen Jones Sales 5514
( 2 row(s) affected)
When you submit a stored procedure to the system, SQL Server compiles and verifies the routines within it. If any problems are found, the procedure is rejected and you'll need to determine what the problem is prior to resubmitting the routine. If your stored procedure references another, as yet unimplemented stored procedure, you'll receive a warning message, but the routine will still be installed.
Other Important Items:
- Query Analyzer is indispenable for testing stored procedure logic before implementing it.
- SET NOCOUNT ON:
Each time a statement in a stored procedure is executed, a message is sent to the client, called DONE_IN_PROC. This message also indicates the number
of records that have been affected by the executed statement. If you have one or two statements on a stored procedure it probably doesn't matter, but
if you have a complex collection of statements in your stored procedure, you should really consider adding SET NOCOUNT ON at the beginning of the stored
procedure, eliminating all this "chatter" about "records being affected" between the client and the server. This can provide a significant performance
boost because network traffic is greatly reduced.
- @@RECORDCOUNT
- @@IDENTITY and NEWID()
|
A UDF is a module, which is attached to a database in SQL Server. UDF's can accept parameters and can return a value. If you're familiar with stored procedures, then you'll be glad to know that UDF’s can contain code in a similar way to stored procedures. UDF's, however are limited to modifying only local variables (those whose lifetime is within the scope of the UDF), meaning that you can’t use SELECT, INSERT, DELETE, etc statements to modify any tables outside of the scope of the UDF. They are also limited to the types of variables you may return. For example - text, ntext, and image datatypes can not be returned.
What good are UDF's if you can't modify relational data then? Well, they're great for creating functions that need to work with data from databases, but don't actually need to modify the database. You can still retrieve and manipulate tables but can't physically commit changes to them.
OrderStatus is an example of implementing a business rule with a UDF.
CREATE FUNCTION OrderStatus
(@Shipped int, @ShipDate datetime)
RETURNS char
AS
BEGIN
DECLARE @RET char(1)
SET @RET = 'Z'
IF @Shipped = 1 OR @ShipDate <> '1900-01-01'
BEGIN
SET @RET = 'B'
END
ELSE
BEGIN
IF @Shipped = 1 AND @ShipDate = '1900-01-01'
BEGIN
SET @RET = 'S'
END
ELSE
BEGIN
IF @Shipped = 0
BEGIN
SET @RET = 'L'
END
END
END
RETURN @RET
END
GetActBags is an example of UDF that utilizes a query (that itself calls another UDF) to return a single value.
CREATE FUNCTION GetActBags
(@ProductCode varchar(20), @Grade varchar(20))
RETURNS float
AS
BEGIN
DECLARE @RET real
SELECT @RET = SUM(CASE ISNULL(PODetail.POUnits, 0)
WHEN 0 THEN 0
ELSE PODetail.POBOLWeight * dbo.WeightMult(GTable.TableValue, 'Bags (55.115)')
END)
FROM PODetail
LEFT OUTER JOIN GTable ON GTable.TableName = 'Units' AND GTable.TableKey = PODetail.POUnits
WHERE PODetail.POProduct = @ProductCode AND PODetail.POGrade = @Grade
RETURN @RET
END
Num2Words is a UDF version of a function that is common to almost every programming language.
CREATE FUNCTION dbo.Num2Words
(@Number Numeric (38, 0))
RETURNS VARCHAR(8000)
/*
* Converts a integer number as large as 34 digits into the equivalent words.
* The first letter is capitalized.
*
* Example:
SELECT dbo.Num2Words (1234567890) + CHAR(10)
+ dbo.Num2Words (0) + CHAR(10)
+ dbo.Num2Words (123) + CHAR(10)
SELECT dbo.Num2Words(76543210987654321098765432109876543210)
DECLARE @i numeric (38,0)
SET @i = 0
WHILE @I <= 1000
BEGIN
PRINT convert (char(5), @i)
+ convert(varchar(255), dbo.Num2Words(@i))
SET @I = @i + 1
END
*/
AS
BEGIN
DECLARE @inputNumber VARCHAR(38)
DECLARE @NumbersTable TABLE (number CHAR(2), word VARCHAR(10))
DECLARE @outputString VARCHAR(8000)
DECLARE @length INT
DECLARE @counter INT
DECLARE @loops INT
DECLARE @position INT
DECLARE @chunk CHAR(3) -- for chunks of 3 numbers
DECLARE @tensones CHAR(2)
DECLARE @hundreds CHAR(1)
DECLARE @tens CHAR(1)
DECLARE @ones CHAR(1)
IF @Number = 0 RETURN 'Zero'
-- initialize the variables
SELECT @inputNumber = CONVERT(varchar(38), @Number),
@outputString = '',
@counter = 1
SELECT @length = LEN(@inputNumber),
@position = LEN(@inputNumber) - 2,
@loops = LEN(@inputNumber)/3
-- make sure there is an extra loop added for the remaining numbers
IF LEN(@inputNumber) % 3 <> 0 SET @loops = @loops + 1
-- insert data for the numbers and words
INSERT INTO @NumbersTable
SELECT '00', ''
UNION ALL SELECT '01', 'one' UNION ALL SELECT '02', 'two'
UNION ALL SELECT '03', 'three' UNION ALL SELECT '04', 'four'
UNION ALL SELECT '05', 'five' UNION ALL SELECT '06', 'six'
UNION ALL SELECT '07', 'seven' UNION ALL SELECT '08', 'eight'
UNION ALL SELECT '09', 'nine' UNION ALL SELECT '10', 'ten'
UNION ALL SELECT '11', 'eleven' UNION ALL SELECT '12', 'twelve'
UNION ALL SELECT '13', 'thirteen' UNION ALL SELECT '14', 'fourteen'
UNION ALL SELECT '15', 'fifteen' UNION ALL SELECT '16', 'sixteen'
UNION ALL SELECT '17', 'seventeen' UNION ALL SELECT '18', 'eighteen'
UNION ALL SELECT '19', 'nineteen' UNION ALL SELECT '20', 'twenty'
UNION ALL SELECT '30', 'thirty' UNION ALL SELECT '40', 'forty'
UNION ALL SELECT '50', 'fifty' UNION ALL SELECT '60', 'sixty'
UNION ALL SELECT '70', 'seventy' UNION ALL SELECT '80', 'eighty'
UNION ALL SELECT '90', 'ninety'
WHILE @counter <= @loops
BEGIN
-- get chunks of 3 numbers at a time, padded with leading zeros
SET @chunk = RIGHT('000' + SUBSTRING(@inputNumber, @position, 3), 3)
IF @chunk <> '000'
BEGIN
SELECT @tensones = SUBSTRING(@chunk, 2, 2)
, @hundreds = SUBSTRING(@chunk, 1, 1)
, @tens = SUBSTRING(@chunk, 2, 1)
, @ones = SUBSTRING(@chunk, 3, 1)
-- If twenty or less, use the word directly from @NumbersTable
IF CONVERT(INT, @tensones) <= 20 OR @Ones='0'
BEGIN
SET @outputString = (SELECT word
FROM @NumbersTable
WHERE @tensones = number)
+ CASE @counter
WHEN 1 THEN '' -- No name
WHEN 2 THEN ' thousand ' WHEN 3 THEN ' million '
WHEN 4 THEN ' billion ' WHEN 5 THEN ' trillion '
WHEN 6 THEN ' quadrillion ' WHEN 7 THEN ' quintillion '
WHEN 8 THEN ' sextillion ' WHEN 9 THEN ' septillion '
WHEN 10 THEN ' octillion ' WHEN 11 THEN ' nonillion '
WHEN 12 THEN ' decillion ' WHEN 13 THEN ' undecillion '
ELSE '' END
+ @outputString
END
ELSE
BEGIN -- break down the ones and the tens separately
SET @outputString = ' '
+ (SELECT word
FROM @NumbersTable
WHERE @tens + '0' = number)
+ '-'
+ (SELECT word
FROM @NumbersTable
WHERE '0'+ @ones = number)
+ CASE @counter
WHEN 1 THEN '' -- No name
WHEN 2 THEN ' thousand ' WHEN 3 THEN ' million '
WHEN 4 THEN ' billion ' WHEN 5 THEN ' trillion '
WHEN 6 THEN ' quadrillion ' WHEN 7 THEN ' quintillion '
WHEN 8 THEN ' sextillion ' WHEN 9 THEN ' septillion '
WHEN 10 THEN ' octillion ' WHEN 11 THEN ' nonillion '
WHEN 12 THEN ' decillion ' WHEN 13 THEN ' undecillion '
ELSE ''
END
+ @outputString
END
-- now get the hundreds
IF @hundreds <> '0'
BEGIN
SET @outputString = (SELECT word
FROM @NumbersTable
WHERE '0' + @hundreds = number)
+ ' hundred '
+ @outputString
END
END
SELECT @counter = @counter + 1, @position = @position - 3
END
-- Remove any double spaces
SET @outputString = LTRIM(RTRIM(REPLACE(@outputString, ' ', ' ')))
SET @outputstring = UPPER(LEFT(@outputstring, 1)) + SUBSTRING(@outputstring, 2, 8000)
RETURN @outputString
END
|
RP_spGetRelatedOrders is a simple example of a parameterized user query.
CREATE PROCEDURE RP_spGetRelatedOrders
@OrderNumber varchar(10)
AS
SET NOCOUNT ON
SELECT PODetail.POProduct, PODetail.POGrade, PODetail.POBOLWeight,PODetail.POUnits, PODetail.POPrice,
OrderDetail.MasterID, OrderDetail.ProductQty, OrderDetail.ProductUnits, OrderDetail.ProductUnitPrice,
OrderDetail.ProductDiscPct, OrderMaster.MasterCustID, OrderMaster.MasterCustPO, OrderMaster.MasterETS,
Customers.CustName, OrderMaster.MasterReleaseDate, OrderMaster.MasterShipFrom, GTable.TableValue AS Units,
OrderDetail.ProductQty * dbo.WeightMult(GTable.TableValue, 'Bags (55.115)') AS Bags
FROM PODetail
INNER JOIN OrderDetail ON OrderDetail.ProductCode = PODetail.POProduct
INNER JOIN OrderMaster ON OrderMaster.MasterID = OrderDetail.MasterID
INNER JOIN Customers ON Customers.CustID = OrderMaster.MasterCustID
INNER JOIN GTable ON GTable.TableName = 'Units' AND
GTable.TableKey = OrderDetail.ProductUnits
INNER JOIN GTable AS GTable2 ON GTable2.TableName = 'Units' AND
GTable2.TableKey = OrderDetail.ProductPriceUnits
WHERE PONumber = @OrderNumber
ORDER BY Customers.CustName, PONumber
RP_spQtyOnHandReport is a complex example of a report query.
CREATE PROCEDURE RP_spQtyOnHandReport
AS
SET NOCOUNT ON
SELECT PONumber, Vendor, Material, POProduct, POPrice,
OrdBags, ROUND(OrdBags * 55.115, 0) AS OrdLbs,
ActBags, ROUND(ActBags * 55.115, 0) AS ActLbs,
ActBags - OrdBags AS RemBags, ROUND((ActBags - OrdBags) * 55.115, 0) AS RemLbs,
PORemarks, WhseName
FROM (SELECT PODetail.PONumber, GTable4.TableValue AS Material, GTable2.TableValue AS Vendor,
PODetail.POProduct, POPrice, PODetail.PORemarks, Warehouse.WhseName,
SUM(CASE ISNULL(OrderDetail.ProductUnits, 0)
WHEN 0 THEN 0
ELSE OrderDetail.ProductQty * dbo.WeightMult(GTable.TableValue, 'Bags (55.115)')
END) AS OrdBags,
(SELECT SUM(CASE POActBags
WHEN 0 THEN
CASE ISNULL(PODet.POUnits, 0)
WHEN 0 THEN 0
ELSE PODet.POBOLWeight * dbo.WeightMult(GTable3.TableValue, 'Bags (55.115)')
END
ELSE POActBags
END) AS POActBags
FROM PODetail AS PODet
INNER JOIN GTable AS GTable3 ON GTable3.TableName = 'Units' AND
GTable3.TableKey = PODet.POUnits
WHERE PODetail.PONumber = PODet.PONumber AND
PODetail.POProduct = PODet.POProduct
GROUP BY PODet.PONumber, PODet.POProduct) AS ActBags
FROM PODetail
LEFT OUTER JOIN OrderDetail ON OrderDetail.ProductCode = PODetail.POProduct
LEFT OUTER JOIN GTable ON GTable.TableName = 'Units' AND
GTable.TableKey = OrderDetail.ProductUnits
INNER JOIN POMaster ON POMaster.PONumber = PODetail.PONumber
LEFT OUTER JOIN GTable AS GTable2 ON GTable2.tablename = 'Vendors' AND
CAST(GTable2.TableKey AS int) = POMaster.POVendorID
INNER JOIN GTable AS GTable4 ON GTable4.TableName = 'Materials' AND
GTable4.TableKey = PODetail.POMaterials
INNER JOIN Warehouse ON Warehouse.WhseID = PODetail.POWarehouse
WHERE POMaster.POClosed = 0
GROUP BY PODetail.PONumber, GTable4.TableValue, GTable2.TableValue, PODetail.POProduct,
PODetail.POPrice, GTable.TableValue, PODetail.PORemarks, Warehouse.WhseName) AS temp
GROUP BY PONumber, Vendor, Material, POProduct, POPrice, OrdBags, ActBags, PORemarks, WhseName
HAVING ActBags > OrdBags OR OrdBags > ActBags * 1.1
ORDER BY PONumber, Vendor, Material, POProduct
spFW_GetNameList is a simple example of a parameterized user query.
CREATE PROCEDURE spFW_GetNameList
@tStrTaxID varchar(20), @tStrState varchar(3), @tStrZip varchar(10), @tStrLastName varchar(30),
@tStrProviderType varchar(2), @IntRNCNameLengthToUseMin int, @RoutingTree varchar(500)
AS
SET NOCOUNT ON
DECLARE @lStrWhereStatement varchar(1000)
DECLARE @StrCommand varchar(1100)
DECLARE @lStrNameField varchar(30)
DECLARE @lStrAdditionalOrder varchar(100)
SET @lStrWhereStatement = ' MasterNames.NetCode IN (' + @RoutingTree + ') '
SET @lStrAdditionalOrder = ''
IF @tStrTaxID <> ''
BEGIN
SET @lStrWhereStatement = @lStrWhereStatement + ' AND MasterNames.TaxID = ''' + @tStrTaxID + ' '''
END
IF @tStrZip <> ''
BEGIN
SET @tStrZip = LEFT(@tStrZip, 5)
SET @lStrWhereStatement = @lStrWhereStatement + ' AND MasterNames.BZip LIKE ''' + @tStrZip + '%'' '
END
IF @tStrLastName <> ''
BEGIN
IF @tStrProviderType = 'H'
BEGIN
SET @lStrNameField = 'MasterNames.FAname'
SET @lStrAdditionalOrder = @lStrAdditionalOrder + ' MasterNames.FaName,'
END
ELSE
BEGIN
SET @lStrNameField = 'MasterNames.LName'
SET @lStrAdditionalOrder = @lStrAdditionalOrder + ' MasterNames.LName,'
END
IF LEN(@tStrLastName) <= @IntRNCNameLengthToUseMin
BEGIN
SET @lStrWhereStatement = @lStrWhereStatement + ' AND ' +
@lStrNameField + ' = ''' + @tStrLastName + ''' '
END
ELSE
BEGIN
SET @lStrWhereStatement = @lStrWhereStatement + ' AND ' +
@lStrNameField + ' LIKE ''' + @tStrLastName + '%'' '
END
END
IF @tStrProviderType <> ''
BEGIN
IF @tStrProviderType='H'
BEGIN
SET @lStrWhereStatement = @lStrWhereStatement + ' AND MasterNames.LName = '''' '
END
ELSE
BEGIN
SET @lStrWhereStatement = @lStrWhereStatement + ' AND MasterNames.LName <> '''' '
END
END
SET @lStrWhereStatement = @lStrWhereStatement + ' AND MasterNames.BState = ''' + @tStrState + ''' '
SET @StrCommand = 'SELECT TOP 100 MasterNames.*, ' +
'dbo.ItemIndex(''' + @RoutingTree + ''', MasterNames.NetCode) as Seq ' +
'FROM MasterNames ' +
'WHERE ' + @lStrWhereStatement +
'ORDER BY Seq, ' + @lStrAdditionalOrder
EXEC(@StrCommand)
|
SQL pass-through (SPT) technology enables you to send SQL statements directly to a server. SPT statements, because they execute on the back-end server,
are powerful ways to enhance the performance of your client/server applications.
The method illustrated in this section contrast with the CursorAdapter method, introduced in VFP 8.0. Some code and examples are here.
SPT technology includes the following characteristics:
- You can use server-specific functionality, such as stored procedures and server-based intrinsic functions.
- You can use extensions to SQL supported by the server, as well as data-definition, server administration, and security commands.
- You have more control over SQL pass-through Update, Delete, and Insert statements.
- You have more control over remote transactions.
- Visual FoxPro can handle SQL pass-through queries that return more than a single result set.
- By default, a SQL pass-through query always returns a non-updatable snapshot of remote data, which is stored in an active view cursor. You can make the cursor updatable by setting properties with the CURSORSETPROP( ) function.
The following code assumes a trusted connection to the SQL Server. If that is not the case, fill in the UID and PWD parameters
with the user name and password, respectively.
lnConn = SQLStringConnect("DRIVER={SQL Server};SERVER=ServerName;UID=;PWD=;DATABASE=SQLDatabaseName")
IF lnConn <= 0
=AERROR(laError)
=MESSAGEBOX(laError(1, 2), 32, "Could not connect to SQL Server")
RETURN "Could not connect to SQL Server"
ENDIF
lcSQL = "EXEC spExampleProcedure " + ALLTRIM(STR(lnID))
=SQLSETPROP(lnConn, "asynchronous", .F.)
DO WHILE .T.
lnReturn = SQLExec(lnConn, lcSQL)
IF lnReturn <= 1
EXIT
ENDIF
ENDDO
IF lnReturn <= 0
=AERROR(laError)
=MESSAGEBOX(laError(1, 2), 32, "EXEC spExampleProcedure Failed")
RETURN "EXEC spExampleProcedure failed: " + lcSQL
ENDIF
** Result set now resides in local cursor SQLResult
=SQLDisconnect(lnConn)
It is very important that a connection be released when it is no longer needed by the application because connections consume valuable resources on the server, and the number of connections may be limited by licensing constraints.
A class method that interacts with a SQL Server stored procedure:
*-----------------------------------------------------------------------------*
* Return a single SQL table record into an object collection
* Assumes public connection handle stored in .lnConn and a place to store the
* resulting record count (.lnDBFRecCount).
*-----------------------------------------------------------------------------*
PROCEDURE GetSQLRecordObject(lcSQL AS STRING) AS OBJECT
LOCAL lnSavSel, loReturnObject, lcErrMsg, lnTally
lnSavSel = SELECT()
WITH THIS
lnTally = SQLExec(.lnConn, lcSQL)
SELECT SQLResult
IF RECCOUNT() > 0
SELECT SQLResult
SCATTER MEMO NAME loReturnObject
.lnDBFRecCount = RECCOUNT()
ELSE
SCATTER MEMO NAME loReturnObject BLANK
.lnDBFRecCount = 0
ENDIF
USE IN SQLResult
IF VARTYPE(loReturnObject) <> "O"
lcErrMsg = .ERRORMESSAGE("Could not create loReturnObject for " +;
lcSQL + "'.")
RETURN lcErrMsg
ENDIF
ENDWITH
SELECT (lnSavSel)
RETURN loReturnObject
ENDPROC
Retrieving the SQL Server identity value for a newly-inserted record:
lcSQL = "INSERT Prospects " +;
"(Name, Addr1, Addr2, City, State, Postal, Fips, Phone, Fax) " +;
"VALUES " +;
"('" + STRTRAN(lcName, "'", "''") + "','" + STRTRAN(lcAddr1, "'", "''") + "','" +;
STRTRAN(lcAddr2, "'", "''") + "','" + STRTRAN(lcCity, "'", "''") + "','" +;
lcState + "','" + lcPostal + "','" + lcFips + "','" +;
lcPhone + "','" + lcFax + "') " +;
"SELECT TOP 1 @@IDENTITY AS ProspectID FROM Prospects"
lnTally = SQLExec(.lnConn, lcSQL)
Linking to a FoxPro table from withing a SQL Server stored procedure:
DECLARE @ParmCity varchar(100)
DECLARE @SQLStr varchar(300)
SET @ParmCity = UPPER('Pensacola')
SET @SQLStr = 'SELECT Temp.*
FROM OPENROWSET(''VFPOLEDB.1'',
''C:\HOME\ATLANTA FOXPRO USERS GROUP\'';'''';'''',
''SELECT *
FROM Zip
WHERE City=''''' + @ParmCity + ''''''') AS Temp'
EXECUTE(@SQLStr)
|
|