连接两个表的数据计算

Data Calculation for joining two tables

我正在研究 Foxpro 以创建一个简单的应用程序来处理来自两个 tables AB 的数据(大小为 tableB >> tableA 的大小)。来自 Excel spreadsheet 的数据被导入到这两个 table 中。

tableA
id            balance    load    state     
1             10         null    l
2             22         null    l 
3             31         null    l

tableB  
Load id     id      ord        fact   type   1st value  rounded value   state
    1        1        1        0.09      1      null        null         l
    2        1        2        0.02      0      null        null         l
    3        1        3        0.13      1      null        null         l
    4        1        4       -0.05      0      null        null         l
    5        2        1        0.01      1      null        null         l
    6        2        2        0.092     1      null        null         l
    7        2        3        0.03      0      null        null         l
    8        3        1        0.14      1      null        null         l
    9        3        2        0.12      0      null        null         l
   10        3        3       -0.02      0      null        null         l

我的朋友希望我编写一个 Foxpro 代码来执行以下操作:首先,创建空的 tableA 和 tableB,其中包含上面显示的列。每列每天都会加载 excel 电子表格中的(数十万)数据。其次,对于每个唯一 ID,代码使用给定的公式更新 3 列 1st valuerounded valueload

1st value[i] = If(Type[i]=0, load[i-1]*fact[i], load[i-1]*fact[i]/(1-fact[i]))

1st value[1] = If(Type[1]=0, balance[1]*fact[1], balance[1]*fact[1]/(1-fact[1]))

rounded value[i] = If(1st value[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)

load[i+1] = load[i] + rounded value[i+1] (i >= 1)

load[1] = balance[1] + rounded value[1]

我想我必须像下面这样创建一个 table 来存储上面这一步的计算:

Calculation Table
  balance     id      ord    1st value  rounded value    load    
  10          1        1      0.989         0.90        10.9 (= 10 + 0.9)
  10.9        1        2      0.218         0.20        11.1 (= 10.9 + 0.2)
  11.1        1        3      1.658         1.60        12.7 (= 11.1 + 1.6)
  11.06       1        4     -0.635        -0.64        11.06 (=12.7 + (-0.64))

期望输出

使用 Calculation Table 中的结果,我们更新原始 tableAtableB 如下:

tableB    
Load id     id      ord       1st value  rounded value   state
    1        1        1        0.989          0.90       calculated        
    2        1        2        0.218          0.20       calculated     
    3        1        3        1.658          1.60       calculated 
    4        1        4       -0.635         -0.64      calculated 
    5        2        1        ...            ....      calculated 
    6        2        2        ...            ....      calculated 

tableA (Note: for each value in `load id`, the `load` column only stores the **last** value in the `calculation` table which corresponds to maximum `ord`)
id            balance    load    state     
1             10         9.5     calculated
2             22         ...     calculated 
3             31         ...     calculated

任何人都可以帮助我创建 tableB 的语法,计算列 1st valuerounded valueload 的结果并将其存储到 [=33= 中] table 在 tableA 和 tableB 之间的 id 列上使用 Inner Join 函数,并更新 tableB?

我的尝试:

第一步(使用上面显示的列字段创建两个 tables A 和 B)

CREATE TABLE tableA; 
(     id    int,    ;
      balance   double, ;
      load   C(240), ;
      state  C(240), ;)

CREATE TABLE tableB; 
(     Load id   int, ;
      id        int, ;
      ord       int, ;
      fact      double,  ;
      type      binary (not sure....)  ;
     1st value  C(240),;
      rounded value  C(240), ;
      state     C(240), ;)

好吧,你的问题很长,里面包含多个问题。我会尝试分段回复(在中间编辑我的答案),因为这将是一个很长的答案(甚至可以分成多个答案)。

首先,您的 create table 语法很接近但不正确。 VFP(顺便说一句,它不是 VFB 而是 V FP)不支持字段名称中的空格(除非它是一个长字段名称)。使用带空格的字段名只会自找麻烦。所以最好不要使用它们。它看起来像:

CREATE TABLE tableA; 
(     id    int,    ;
      balance   double, ;
      load   C(240), ;
      state  C(240))

CREATE TABLE tableB; 
(     Load id   int, ;
      id        int, ;
      ord       int, ;
      fact      double,  ;
      type      int  ;
      firstValue  C(240),;
      roundedVal  C(240), ;
      state     C(240))

请注意,在 final 字段后没有逗号和 ;在 VFP 中意味着在下一行继续命令(因此在最后的字段定义行中删除)。我还更改了 2 个字段名称以与自由 table 的字段命名兼容(长度最多为 10,并且必须以字母开头,没有空格)。使用 tables 这个 way.or 游标更容易,前提是您一次性完成并且以后不要尝试更改结构。

如果您想使用长字段名,那么您可以像使用免费 table 那样使用它,但是 table 需要成为数据库的一部分。它也适用于游标,前提是您一次性执行此操作并且之后不要尝试更改结构。

虽然我在那里添加了代码来创建 TableA、TableB,但您是说那些 table 的数据将来自 Excel。您并没有真正提供有关 Excel 部分的详细信息(数据是如何表示的——是数据范围吗?)。很有可能你直接用ODBC/OLEDB从Excel中选择数据就创建了这两个table。

为了从 Excel 获取数据,我在 Foxite, you can check the post in this link 上发布了一些详细信息。我没有在这里提供任何示例代码,因为我还不知道 Excel 部分。

假设我们从 Excel 得到数据让我们检查其他部分(顺便说一下 table B id 称为外键,而不是主键。它 link 表 B 中的行顶部 TableA).

1st value[i] = If(Type[i]=0, balance[i]*fact[i], balance[i]*fact[i]/(1-fact[i]))

我们可以使用 REPLACE 命令(xBase 命令)或 SQL 更新命令来完成此操作。让我们不要考虑这里的差异(真的不值得)并选择 SQL 更新来完成工作(语法也可以在其他数据库中重用 - 比如 MS SQL 服务器,postgreSQL , mySQL ...).

Update tableB ;
  set firstValue = iif( type = 0, ;
          tableA.balance * fact, ;
          tableA.balance * fact/(1-fact)) ;
from tableA ;
where tableA.Id = tableB.Id

或稍微简化:

Update tableB ;
  set firstValue = tableA.balance * fact / ;
          iif( type = 0, 1, (1-fact)) ;
from tableA ;
where tableA.Id = tableB.Id

请注意,VFP 会每行执行此表达式,因此我们不需要您在伪代码中的 [i](数组标识符)。

下一个:

rounded value[i] = If(Type[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)

将以相同的方式翻译:

Update tableB ;
  set roundVal = iif(type > 0, ;
        rounddown(firstValue,1), ;
        roundup(firstValue,2)) ;
from tableA ;
where tableA.Id = tableB.Id

但是VFP没有roundup和rounddown函数,我写这些只是作为概念翻译。您可以做的是创建两个执行 RoundUp 和 RoundDown 的自定义函数。有多种方法可以编写这些函数,恕我直言,最简单的方法是将它们编写为 2 个单独的 .prg 文件,当您执行上述 SQL 命令时,这些 prg 文件位于您的搜索路径中:

RoundUp.prg

Lparameters tnValue, tnPlaces
If Round(m.tnValue, m.tnPlaces) = m.tnValue
    Return m.tnValue
Else
    Return Round(m.tnValue+((10^-(m.tnPlaces+1))*5), m.tnPlaces)
Endif

RoundDown.prg

Lparameters tnValue, tnPlaces
If Round(m.tnValue, m.tnPlaces) = m.tnValue
    Return m.tnValue
Else
    Return Round(m.tnValue-((10^-(m.tnPlaces+1))*5), m.tnPlaces)
Endif

您提供的 link 中的函数似乎不适合我的工作(但不容易理解和测试,所以没有花时间仔细检查)。

我不确定一个包含两个 table 的 sheet 是否好。我不记得 Tables 集合是否是 WorkSheet 或 WorkBook 的成员。如果是 WorkSheet 就可以了。我可以稍后(可能是明天)检查并编写示例代码。

您可以使用数据类型 LOGICAL (l) 作为类型。在 MS SQL 服务器和其他后端它对应于位(1 或 0)。内部存储为布尔值,但在表达式中用作 .T./.F。 (true\false VFP 中的符号表示。在代码中,您可以简单地将其用作:

iif( type, ...

与 iif(type = .T., ...) 相同 - 在 Type > 0 中。并且:

iif( !type, ...

与 iif( type = .F., ...) 或 iif( type NOT equal to .T., ... - 如 Type = 0.

在这种情况下我没有使用 inner join,因为在这里使用来自 TableA 的 a 就足够了(在其他后端中也是如此,尽管一般倾向于使用 join 来编写)。

编辑:添加代码作为另一个答案。

根据您的问题: 不需要显式定义内部连接,那里有一个隐式连接。我没有编写 SQL 更新,而是更喜欢利用 VFP 的 xBase 功能并使用 scan...endscan 代替(可以使用 SQL 但会更复杂)。

是的,这意味着将这 2 个 RoundUp.prg 和 RoundDown.prg 文件放入与我们上面的主文件代码相同的目录路径中,但前提是主文件代码位于当前目录或搜索路径中。为了更清楚,请考虑:

c:\SomeFolder\RoundUp.prg c:\SomeFolder\RoundDown.prg c:\ANOTHERFolder\Main.prg

你在: c:\YetAnotherFolder

如果您这样调用 main.prg:

do ('c:\ANOTHERFolder\Main.prg')

它需要找到 RoundUp、RoundDown,如果 c:\Somefolder 包含在 SET('PATH') 中就可以 - 即:

Set path to c:\SomeFolder;c:\VFPHomeFolderMaybe

或者如果您不想考虑路径,您可以将那些 RoundUp\Down 代码作为过程包含在代码中(就像我在另一个答案的代码中所做的那样 - 请注意,在 VFP 中没有PROCEDURE 和 FUNCTION 之间的区别。您可以自由选择其中任何一个。一些开发人员更喜欢将 FUNCTION 用于那些 return 值 - 但实际上任何 PROCEDURE\FUNCTION returns 值所以假设那些用于 return 值。)

I don't think logical type mean "1" or "0" automatically, correct? If that's the case, I would have to leave it as int type, because the input is always defined as 1 or 0 for type column.

嗯,这很难正式回答。在 VFP 布尔数据中 类型由文字 .F 定义。和T。您可以强制转换(aBoolean 到 int)并分别得到 0 和 1。或者你可以投(1 as logical)来得到.T。 IOW 1[=96=] 和.T..F。在某种意义上是可以互换的。这完全取决于您想在哪里使用它。如果数据来自外部来源,它将以 1[=96=] 的形式出现。只需将其转换或放入逻辑数据类型列(隐式转换),它就会被视为 .T..F。或者您正在将数据从逻辑源发送到外部源(比如 XML、MS SQL 服务器、postgreSql、其他 OLEDB\ODBC 数据源)然后.T..F。被转换为 1[=96=].

添加为另一个答案以防止混乱。如果需要,我可以做进一步的解释。在这里,我使用了与示例数据匹配的 Excel 范围。您可以将范围替换为实际范围(以及 excel 文件名):

GetDataFromExcel("c:\myFolder\myExcel.xlsx", "B9:E12", "G9:N19")
DoCalculation()
Select crsA
Browse
Select crsB
Browse

Procedure DoCalculation
    *1st value[1] = If(Type[1]=0, balance[1]*fact[1], balance[1]*fact[1]/(1-fact[1]))

    *rounded value[i] = If(1st value[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)
    *rounded value[1] = If(1st value[1]>0, rounddown(1st value[1], 1), roundup(1st value[1],2)

    *load[1] = balance[1] + rounded value[1]

    * i > 1 - ord > 1
    *1st value[i] = If(Type[i]=0, load[i-1]*fact[i], load[i-1]*fact[i]/(1-fact[i]))

    *rounded value[i] = If(1st value[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)

    *load[i+1] = load[i] + rounded value[i+1] (i >= 1)

    Local lnBalance
    Select CrsB
    Index On Padl(Id,10,'0')+Padl(ord,10,'0') Tag ALinkB
    Select crsA
    Set Relation To Padl(Id,10,'0') Into CrsB
    Scan
        lnBalance = crsA.Balance
        Select CrsB
        Scan While Id = crsA.Id
            Replace ;
                firstValue With m.lnBalance*fact / Iif(!Type, 1, 1-fact),  ;
                roundVal With Iif(firstValue > 0, ;
                roundDown(firstValue,1), ;
                roundUp(firstValue, 2))
            lnBalance = m.lnBalance + CrsB.roundVal
        Endscan
        Select crsA
        Replace Load With m.lnBalance
    Endscan
Endproc

Procedure GetDataFromExcel(tcExcelFileName, tcTableARange, tcTableBRange)
    Local lcConStr
    lcConStr = ;
        'Provider=Microsoft.ACE.OLEDB.12.0;'+;
        'Data Source='+Fullpath(m.tcExcelFileName)+';'+;
        'Extended Properties="Excel 12.0;HDR=Yes"'
    Local lcSQLA, lcSQLB

    TEXT to lcSQLA textmerge noshow
Select [id], [balance], [load], [state]
from [Sheet1$<< m.tcTableARange >>]
    ENDTEXT

    TEXT to m.lcSQLB textmerge noshow
select
   [Load Id] as LoadId,
   [Id], [Ord], [Fact], [Type],
   [1st value] as firstValue,
   [Rounded value] as roundVal,
   [State]
from [Sheet1$<< m.tcTableBRange >>]
    ENDTEXT

    ADOQuery(m.lcConStr, m.lcSQLA, "crsTableA")
    ADOQuery(m.lcConStr, m.lcSQLB, "crsTableB")

    Select Cast(Id As Int) As Id, Cast(Balance As Double) As Balance, ;
        Cast(Load As Double) As Load, Cast(State As c(1)) As State ;
        from crsTableA ;
        into Cursor crsA ;
        readwrite
    Select Cast(LoadId As Int) As LoadId, ;
        Cast(Id As Int) As Id, Cast(ord As Int) As ord, ;
        Cast(fact As Double) As fact, Cast(Type As logical) As Type, ;
        Cast(firstValue As Double) As firstValue, ;
        Cast(roundVal As Double) As roundVal, ;
        Cast(State As c(1)) As State From crsTableB ;
        into Cursor CrsB ;
        readwrite
    Use In (Select('crsTableA'))
    Use In (Select('crsTableB'))
Endproc

Procedure roundUp(tnValue, tnPlaces)
    If Round(m.tnValue, m.tnPlaces) = m.tnValue
        Return m.tnValue
    Else
        Return Round(m.tnValue+((10^-(m.tnPlaces+1))*5), m.tnPlaces)
    Endif
Endproc

Procedure roundDown(tnValue, tnPlaces)
    If Round(m.tnValue, m.tnPlaces) = m.tnValue
        Return m.tnValue
    Else
        Return Round(m.tnValue-((10^-(m.tnPlaces+1))*5), m.tnPlaces)
    Endif
Endproc

Procedure ADOQuery(tcConStr,tcQuery,tcCursorName)
    Local oConn As 'ADODB.Connection'
    Local oRS As ADODB.RecordSet
    oConn = Createobject('ADODB.Connection')
    oConn.Mode= 1  && adModeRead
    oConn.Open( m.tcConStr )
    oRS = oConn.Execute(m.tcQuery)
    RS2Cursor(oRS,m.tcCursorName)
    oRS.Close
    oConn.Close
Endproc


Procedure RS2Cursor(toRS, tcCursorName) && simple single cursor - not intended for complex ones
    tcCursorName = Iif(Empty(m.tcCursorName),'ADORs',m.tcCursorName)
    Local xDOM As 'MSXML.DOMDocument'
    xDOM = Createobject('MSXML.DOMDocument')
    toRS.Save(xDOM, 1)
    Xmltocursor(xDOM.XML, m.tcCursorName)
Endproc

编辑:我针对下面的评论编辑了另一个答案。现在回答您的问题:

  1. Shouldn't GetDataFromExcel("c:\myFolder\myExcel.xlsx", "B9:E12", "G9:N19") get called after the Procedure Procedure GetDataFromExcel(tcExcelFileName, tcTableARange, tcTableBRange)??

没有。在 prg 文件中,过程总是放在正常执行代码之后。 IOW 如果你的 PRG 有:

Do Something
* ...

Procedure SomeProcedure
* ...
endproc

Procedure Something
endproc

代码从调用 Something 开始并执行之后的行,直到它看到第一个过程调用(或 FUNCTION,DEFINE CLASS)。某些东西可能是一个过程(如示例中所示)或一个单独的 prg。

  1. Shouldn't Procedure roundUp and Procedure roundDown get called before roundDown(firstValue,1), ; roundUp(firstValue, 2))??

不是,同上。你说的比较像核心C的规则

  1. Does the left ID on this line Scan While Id = crsA.Id come from CrsB?? Also, why is there the change from crsA to CrsA? Is this a typo? – user177196 5 mins ago

是的。它来自crsB。但从某种意义上说,你是对的,我应该明确并在其中包含别名:

Scan while crsB.Id = crsA.Id

在 VFP 中,如果您不包含别名,则假定为当前别名。

我们正在外循环中扫描crsA。然后我们切换到 crsB 并在那里扫描,在我们完成切换回 crsA 之后(实际上 scan 命令会记住它关联的别名,并在它隐式命中 endscan 时执行此切换,但我更喜欢显式)。

编辑:

    Select CrsB
    Index On Padl(Id,10,'0')+Padl(ord,10,'0') Tag ALinkB
    Select crsA
    Set Relation To Padl(Id,10,'0') Into CrsB

在前两行,我们 selecting crsB 游标并在其上创建索引。索引表达式包含 Id 和 Old 字段。 VFP 不支持在一个索引键中使用多个列名,但它支持表达式。用 10 个零填充两个字段,我们创建的键如下:

Id, Ord: 2,3 例如有一个key 00000000020000000003

我们可以把它变小,但无论如何,因为不知道 Id 有多大,Ord 可以使它的长度为 10 以适应任何 32 位整数值。

然后在第 3、4 行,我们 select 游标 crsA,然后通过表达式 Padl(Id,10,'0') - Id 填充 10 个零来设置从 crsA 到 crsB 的关系。从 crsA Id:1 有一个关系键 0000000001 然后(匹配所有以 0000000001 开头的索引键,无论 Ord 部分是什么 - 顺便说一句,索引中也有 Ord 确保它们按 Ord 排序)。

实际上,当记录指针指向 crsA 中的 Id:1 时,在 crsB 中自动匹配具有 Id:1 的那些(最好通过浏览观察 - 浏览 crsB 然后 select crsA并浏览。当您在 crsA 中导航时,您会看到 crsB 的浏览 window 将仅显示具有匹配 ID 的行)。从概念上讲,它看起来像这样控制两个游标中的记录指针:

crsA (id)      crsB (Id, Ord)
1 ----+------- 1,1
      +------- 1,2 
      +------- 1,3 
      +------- 1,4 

2 ----+------- 2,1
      +------- 2,2 
      +------- 2,3 

我使用它是因为它是 VFP 的一个强大功能,可以更轻松地表达您想要的内容。同样可以通过使用 SQL Update 来实现,但是,VFP 的 SQL 没有那么强大并且编写起来会复杂得多(对于 [1] 简单但是对于 > 1 情况它变得复杂- 在遥远的过去,在其他后端也不是那么容易,但随着时间的推移,postgreSQL、MS SQL 服务器等后端已经获得了对此类查询的更多支持)。

(添加为另一个答案只是因为其他人阅读时间太长)

can you try your code with this dataset (drive.google.com/open?id=1uCWwt5ubd2_F8w2gsh3v4VDpibWz7PAz) to see if you will get the two output tables from your code, each similar to the one shown in the previous Excel worksheet I uploaded for you?

我下载了那个电子表格,下面是我需要更改的内容: 对于 tableA 和 B,您的范围是 C8:F35 和 H8:O62。此外,您的 "balance" 被命名为 "base"。编辑新代码(下载到 d:\temp\workbook2.xlsx)以匹配范围和 "balance" 到 "base":

* Get the data from given excel filename and ranges
* first range is tableA, second one is tableB
GetDataFromExcel("d:\temp\WorkBook2.xlsx", "Sheet1$C8:F35", "Sheet1$H8:O62")

* Now data is in cursors csrA and crsB do the calculation in these
DoCalculation()

* Done. Show the results selecting and browsing the crsA and B
Select crsA
Browse
Select crsB
Browse

* Get specific fields only from crsB
Select loadId, id, ord, firstVal, roundedVal, state ;
from crsB ;
into cursor crsBCustom ;
nofilter
browse

* Check data from both cursors (join)
* I chose the fields as I see fit
* ta and tb are local aliases for crsA and crsB
* helping to write shorter SQL in this case

Select tb.LoadId, tb.Id, ta.base, ta.load, ; 
       tb.firstValue, tb.roundVal, ;
       ta.State as StateA, tb.State as StateB ;    
from crsA ta ;
inner join crsB tb on ta.Id = tb.Id ;
order by tb.Id, tb.Ord ;
into cursor crsBoth ;
NoFilter
browse


* Does the specific calculations on specific data
Procedure DoCalculation
    *1st value[1] = If(Type[1]=0, Base[1]*fact[1], Base[1]*fact[1]/(1-fact[1]))

    *rounded value[i] = If(1st value[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)
    *rounded value[1] = If(1st value[1]>0, rounddown(1st value[1], 1), roundup(1st value[1],2)

    *load[1] = Base[1] + rounded value[1]

    * i > 1 - ord > 1
    *1st value[i] = If(Type[i]=0, load[i-1]*fact[i], load[i-1]*fact[i]/(1-fact[i]))

    *rounded value[i] = If(1st value[i]>0, rounddown(1st value[i], 1), roundup(1st value[i],2)

    *load[i+1] = load[i] + rounded value[i+1] (i >= 1)

    *declare local variable
    Local lnBase

    * select crsB and create an index there
    Select CrsB
    Index On Padl(Id,10,'0')+Padl(ord,10,'0') Tag ALinkB

    * select crsA as parent and link to crsB 
    * using the "id" part of index
    Select crsA
    Set Relation To Padl(Id,10,'0') Into CrsB

    * start looping the rows
    Scan
        * working with a new Id (1, 2, ...)
        * save base value to m.lnBase
        lnBase = crsA.Base

        * select crsB and start looping the rows there
        * because of the index in effect and the relation created
        * pointer would be on the first crsB row with a matching Id 
        * and since Ord is also part of the index the first row of 
        * given Id
        * Limit the looping in crsB (child table) to Id in crsA
        * using WHILE clause 
        Select CrsB
        Scan While Id = crsA.Id
            * do replacing starting on first row of this Id (Ord=1)
            * we don't have any scope clauses in replace, thus 
            * we are doing "single row" updates

            Replace ;
                firstValue With m.lnBase*fact / Iif(!Type, 1, 1-fact),  ;
                roundVal With Iif(firstValue > 0, ;
                roundDown(firstValue,1), ;
                roundUp(firstValue, 2))
            * after each replace update m.lnBase value 
            * to use in next row   
            lnBase = m.lnBase + CrsB.roundVal
        Endscan
        * completed updating crsB
        * select crsA and also update crsA.base with final 'load' value
        Select crsA
        Replace Load With m.lnBase
    Endscan
    * Update state to 'Calculated'
    Update crsA set state = 'Calculated'
    Update crsB set state = 'Calculated'
Endproc

* Get data from excel with given filename and ranges
* This code is not generic and expects the 
* data to be in a specific format.
* Does not do any error check 
Procedure GetDataFromExcel(tcExcelFileName, tcTableARange, tcTableBRange)
    * declare and define the connection string to excel
    Local lcConStr
    lcConStr = ;
        'Provider=Microsoft.ACE.OLEDB.12.0;'+;
        'Data Source='+Fullpath(m.tcExcelFileName)+';'+;
        'Extended Properties="Excel 12.0;HDR=Yes"'

    * Declare and define the 2 SQL needed to get data for A and B
    * rename the fields in SQL for easier handling
    Local lcSQLA, lcSQLB
    TEXT to lcSQLA textmerge noshow
Select [id], [base], [load], [state]
from [<< m.tcTableARange >>]
    ENDTEXT

    TEXT to m.lcSQLB textmerge noshow
select
   [Load Id] as LoadId,
   [Id], [Ord], [Fact], [Type],
   [1st value] as firstValue,
   [Rounded value] as roundVal,
   [State]
from [<< m.tcTableBRange >>]
    ENDTEXT

    * Execute the queries and place results in given cursors 
    ADOQuery(m.lcConStr, m.lcSQLA, "crsTableA")
    ADOQuery(m.lcConStr, m.lcSQLB, "crsTableB")

    * Sanitize the cursors a bit
    * (OledB query would assign rather generic datatypes)
    Select Cast(Id As Int) As Id, Cast(Base As Double) As Base, ;
        Cast(Load As Double) As Load, Cast(State As c(50)) As State ;
        from crsTableA ;
        into Cursor crsA ;
        readwrite
    Select Cast(LoadId As Int) As LoadId, ;
        Cast(Id As Int) As Id, Cast(ord As Int) As ord, ;
        Cast(fact As Double) As fact, Cast(Type As logical) As Type, ;
        Cast(firstValue As Double) As firstValue, ;
        Cast(roundVal As Double) As roundVal, ;
        Cast(State As c(50)) As State From crsTableB ;
        into Cursor CrsB ;
        readwrite
    Use In (Select('crsTableA'))
    Use In (Select('crsTableB'))
Endproc

* roundUp and down custom functions

* RoundUp and Down excel style
* Not correct math wise IMHO
Procedure roundUp(tnValue, tnPlaces)
    Local lnResult, lnValue
    lnValue = Abs(m.tnValue)
    If Round(m.lnValue, m.tnPlaces) != m.lnValue
        lnValue = Round(m.lnValue+((10^-(m.tnPlaces+1))*5), m.tnPlaces)
    Endif
    Return Sign(m.tnValue) * m.lnValue
Endproc

Procedure roundDown(tnValue, tnPlaces)
    Local lnResult, lnValue
    lnValue = Abs(m.tnValue)
    If Round(m.lnValue, m.tnPlaces) != m.lnValue
        lnValue = Round(m.lnValue-((10^-(m.tnPlaces+1))*5), m.tnPlaces)
    Endif
    Return Sign(m.tnValue) * m.lnValue
Endproc


* Generic function to query a given data source
* and place results in a cursor  
Procedure ADOQuery(tcConStr,tcQuery,tcCursorName)
    Local oConn As 'ADODB.Connection'
    Local oRS As ADODB.RecordSet
    oConn = Createobject('ADODB.Connection')
    oConn.Mode= 1  && adModeRead
    oConn.Open( m.tcConStr )
    oRS = oConn.Execute(m.tcQuery)
    RS2Cursor(oRS,m.tcCursorName)
    oRS.Close
    oConn.Close
Endproc

* Helper function to ADOQuery to convert
* an ADODB.Recordset to a VFP cursor
Procedure RS2Cursor(toRS, tcCursorName) && simple single cursor - not intended for complex ones
    tcCursorName = Iif(Empty(m.tcCursorName),'ADORs',m.tcCursorName)
    Local xDOM As 'MSXML.DOMDocument'
    xDOM = Createobject('MSXML.DOMDocument')
    toRS.Save(xDOM, 1)
    Xmltocursor(xDOM.XML, m.tcCursorName)
Endproc

这是完整的代码。只需将文件路径和名称更改为您的 select 所有代码,右键单击并执行 selection 即可查看结果。或者将它保存为 prg,比如 ImportMyExcel.prg 和 运行 它:

ImportMyExcel()

你可以看到我的结果,所以我没有上传任何结果。

Also, is Procedure RS2Cursor(toRS, tcCursorName) intended to generate the 2 output tables? Why do we need this procedure though: Procedure ADOQuery(tcConStr,tcQuery,tcCursorName)?

嗯,这些程序对新手来说有点棘手(也许不是)。我认为您应该了解 VFP、游标、游标适配器、将 ADO 记录集转换为游标等的历史(可能是高级水平)。我不知道,这些是我提出并发布在我给你的 foxite link 上的程序。只是认为它们是黑盒(就像一个内置的)功能,它们正在工作。 ADOQuery 的工作是简单地查询 OLEDB 源并将结果 return 作为游标。使用 cursorAdapter,您可能不需要这样的过程,但该过程是在 CursorAdapter 存在之前设计的。

Two more questions please: 1) where does the m come from in m.lnBalance?

米。显式通知编译器它是一个 内存变量 。它被称为 MDOT。有些开发人员声称不需要它,并且通常会导致长时间的 运行ning 讨论(您可能会在这些讨论中找到我的名字)。直到今天,还没有人能向我展示 and\or 为什么我们不应该或不需要使用它。如果您相信我,这不是偏好,而是您应该使用的东西。

2) Don't we need to define crsTableA? Or you meant we can use the CREATE Table tableA in your previous code to make crsTableA valid?

没有。该代码中没有 table 。我们将 excel 中的数据读入游标(最初是 crsTableA 和 crsTableB),然后清理成 2 个游标 crsA 和 crsB。它们都是游标。游标类似于 tables 但不会持久保存在磁盘上。它们甚至可能会在记忆中度过一生,当你关闭它们时它们就消失了。在这里我更喜欢游标,因为在不损害任何真实数据的情况下,您可以 运行 N 次并检查您的结果。当您对持久化感到满意时,数据就像 "Select ... into" 或 "insert into ..."(还有更多方法)和 table 一样简单。即使在 table 的情况下,您也不需要使用 "Create Table ..."。 "select Into ..." 命令可以 select 来自源的数据并通过创建它来将其保存到 table(就像组合 'create table ...' 然后 'insert into ...')。

Also, I saw that B9:E12 does not match the range of tableA or tableB in the Excel spreadsheet I uploaded for you before. Am I missing something here?

如果您认为数据分别从 B9 和 G9 开始,它与您的原始样本匹配。

I have another question: can you please clarify on what these lines do: Select CrsB Index On Padl(Id,10,'0')+Padl(ord,10,'0') Tag ALinkB Select crsA Set Relation To Padl(Id,10,'0') Into CrsB.

我想我在上一个问题中解释了这部分。我将很快对代码本身进行评论。