Расчет данных для объединения двух таблиц - PullRequest
0 голосов
/ 22 сентября 2018

Я изучаю Foxpro, чтобы создать простое приложение для манипулирования данными из двух таблиц A и B (размер таблицы B >> размер таблицы A).Данные из Excel spreadsheet импортируются в эти две таблицы.

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 каждый день.Во-вторых, для каждого уникального идентификатора , код обновляет 3 столбца 1st value, rounded value и load с заданными формулами:

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]

Я думаю, мне нужно создать таблицу, подобную следующей, чтобы сохранить расчетвыше для этого шага:

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, мы обновляем исходные tableA и tableB следующим образом:

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 value, rounded value и load в таблицу calculation с функцией Inner Joinв столбце id между таблицей A и таблицей B и обновить tableB?

Моя попытка:

Первый шаг (Создание двух таблиц 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), ;)

Ответы [ 3 ]

0 голосов
/ 23 сентября 2018

Добавление еще одного ответа для предотвращения беспорядка.Я могу сделать дальнейшие объяснения, если вам нужно.Здесь я использовал диапазоны 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. Не должен вызываться GetDataFromExcel ("c: \ myFolder \ myExcel.xlsx", "B9: E12", "G9: N19") после процедурыПроцедура GetDataFromExcel (tcExcelFileName, tcTableARange, tcTableBRange) ??

Нет.Процедуры всегда помещаются после нормального кода выполнения в файл prg.IOW, если ваш PRG имеет:

Do Something
* ...

Procedure SomeProcedure
* ...
endproc

Procedure Something
endproc

Код начинается с вызова Something и выполняет после этого строки до тех пор, пока не увидит первый вызов процедуры (или FUNCTION, DEFINE CLASS).Что-то может быть процедурой (как в примере) или отдельным prg.

Не следует вызывать процедуру roundUp и процедуру roundDown перед вызовом roundDown (firstValue, 1),;roundUp (firstValue, 2)) ??

Нет, так же, как указано выше.То, что вы говорите, больше похоже на правила ядра C.

Сканирует ли левый идентификатор в этой строке пока Id = crsA.Id от CrsB ??Кроме того, почему происходит переход с CrsA на CrsA?Это опечатка?- user177196 5 минут назад

Да.это исходит от crsB.Но, в некотором смысле, вы правы, я должен быть явным и включить псевдоним там как:

Scan while crsB.Id = crsA.Id

В VFP, если вы не включите псевдоним, то предполагается, что текущий является *. 1032*

Мы сканируем crsA во внешнем цикле.Затем мы переключаемся на crsB и сканируем там, после того, как мы завершили переключение обратно на crsA (фактически команда сканирования запоминает псевдоним, с которым она связана, и делает это переключение, когда она неявно достигает конца, но я предпочитаю быть явным).

EDIT:

    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

В первых двух строках мы выбираем курсор crsB и создаем для него индекс.Индексное выражение содержит поля Id и Old.VFP не поддерживает несколько имен столбцов в ключе индекса, но поддерживает выражения.Заполняя оба поля 10 нулями, мы создаем ключи, такие как:

Id, Ord: 2,3 в качестве примера имеет ключ 00000000020000000003

Мы могли бы сделать его меньше, но в любом случае, так как не зная, сколькобольшой Id, Ord можно сделать равным 10 в длину, чтобы соответствовать любому 32-битному целочисленному значению.

Затем в 3-й, 4-й строках мы выбираем курсор crsA и затем устанавливаем отношение из crsA в crsB через выражение Padl (Id, 10, '0') - Id, дополненный 10 нулями.Из идентификатора crsA Id: 1 имеет ключ отношения 0000000001 (при сопоставлении всех ключей индекса, начинающихся с 0000000001, независимо от того, какая часть Ord - кстати, если в индексе есть Ord, это также гарантирует, что они упорядочены Ord).

В действительности, когда указатель записи указывает на Id: 1 в crsA, в crsB автоматически сопоставляются те, у которых Id: 1 (лучше всего наблюдать при просмотре - просмотрите crsB, затем выберите crsA и просмотрите.crsA, вы увидите, что окно просмотра для crsB будет показывать только строки с соответствующим 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, однако, SQL-код VFP не такой мощный и его будет гораздо сложнее писать (для [1] легко, но для> 1 случая это становится сложным - это было не так легко вдругие бэкэнды тоже в далеком прошлом, но со временем такие бэкэнды, как postgreSQL, сервер MS SQL и т. д., получили гораздо большую поддержку для таких запросов).

0 голосов
/ 25 сентября 2018

(добавление в качестве другого ответа только потому, что другим слишком долго читать)

можете ли вы попробовать свой код с этим набором данных (drive.google.com/open?id=1uCWwt5ubd2_F8w2gsh3v4VDpibWz7PAz), чтобы увидеть,Вы получите две таблицы вывода из своего кода, каждая из которых похожа на ту, что была показана в предыдущей электронной таблице Excel, которую я загрузил для вас?

Я загрузил эту электронную таблицу, и вот что мне нужно было изменить:диапазоны были C8: F35 и H8: O62 для таблиц A и B. Также ваш «баланс» был назван «base».Новый код (загруженный в d: \ temp \ workbook2.xlsx), отредактированный для соответствия диапазонам и от «баланса» до «базы»:

* 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

Это весь код.Просто измените путь к файлу и имя на свое, выберите весь код, щелкните правой кнопкой мыши и выполните выбор, чтобы увидеть результаты.Или сохраните его как prg, скажем ImportMyExcel.prg и запустите его:

ImportMyExcel()

Вы можете увидеть результаты, которые у меня есть, поэтому я не загружал никаких результатов.

ТакжеПроцедура RS2Cursor (toRS, tcCursorName) предназначена для генерации 2 выходных таблиц?Но зачем нам эта процедура: Процедура ADOQuery (tcConStr, tcQuery, tcCursorName)?

Что ж, эти процедуры немного сложны для новичка (возможно, нет).Я думаю, что вы должны знать историю VFP, курсоров, адаптеров курсора, преобразования набора записей ADO в курсор и т. Д. (Вероятно, продвинутый уровень).Я не знаю, это были процедуры, которые я придумал и опубликовал также по ссылке на фоксит, которую я вам дал.Просто подумайте, что они в черных ящиках (как встроенные) и выполняют свои функции.Работа ADOQuery состоит в том, чтобы просто запросить источник OLEDB и вернуть результат в виде курсора.С помощью курсора-адаптера вам может не понадобиться такая процедура, но эта процедура была разработана до появления CursorAdapter.

Пожалуйста, еще два вопроса: 1) откуда взялась буква m в m.lnBalance?

м.явно уведомляет компилятор, что это переменная памяти .Это упоминается как MDOT.Есть разработчики, которые утверждают, что это не нужно, и, как правило, это ведет к длительным дискуссиям (и, вероятно, вы найдете мое имя в этих дискуссиях).До сегодняшнего дня никто не мог показать и \ или продемонстрировать мне, почему мы не должны или нам не нужно это использовать.Если вы мне верите, это не предпочтение, а то, что вы должны использовать.

2) Разве нам не нужно определять crsTableA?Или вы имели в виду, что мы можем использовать таблицу CREATE TableA в вашем предыдущем коде, чтобы сделать crsTableA допустимым?

Нет.В этом коде нет таблицы.Мы читаем данные из Excel в курсор (изначально crsTableA и crsTableB), а затем очищаем их в 2 курсора crsA и crsB.Все они курсоры.Курсоры похожи на таблицы, но не сохраняются на диске.Они могут даже провести всю свою жизнь в памяти и исчезнут, когда вы закроете ихЗдесь я предпочел курсоры, потому что без ущерба для реальных данных вы можете запустить N раз и проверить свои результаты.Когда вы останетесь довольны сохранением данных, достаточно просто «выбрать ... в» или «вставить в ...» (есть и другие способы) таблицы.Даже в случае таблицы вам не нужно использовать «Создать таблицу ...».Команда «Выбрать в ...» может выбрать данные из источника и сохранить их в таблице, создав их (например, как «создать таблицу ...», а затем «вставить в ...»).

Кроме того, я увидел, что B9: E12 не соответствует диапазону таблицы A или таблицы B в электронной таблице Excel, которую я загружал для вас ранее.Я что-то здесь упускаю?

Это соответствует вашим исходным выборкам, если вы думаете, что данные начинаются с B9 и G9 соответственно.

У меня есть еще один вопрос: не могли бы вы уточнитьчто делают эти строки: Выберите CrsB Index On Padl (Id, 10, '0') + Padl (ord, 10, '0') Tag ALinkB Выберите crsA Установить отношение к Padl (Id, 10, '0') в CrsB.

Кажется, я объяснил эту часть в предыдущем вопросе.Я скоро прокомментирую сам код.

0 голосов
/ 22 сентября 2018

Ну, у вас есть длинный вопрос, содержащий несколько вопросов.Я постараюсь ответить по частям (редактируя мой промежуточный ответ), так как это будет длинный ответ (может быть даже полезно разделить его на несколько ответов).

Во-первых, синтаксис создания таблицы был близок, но неверен,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))

Обратите внимание, что после последнего поля у вас нет запятой и;в VFP означает продолжить команду на следующей строке (поэтому удалено в последних строках определения поля).Я также изменил имена двух полей, чтобы они были совместимы с именами полей свободной таблицы (максимум 10 по длине и должны начинаться с буквы, без пробелов).Проще использовать таблицы таким способом. Или курсорами, если вы делаете это за один раз и не пытаетесь изменить структуру позже.

Если вы хотите использовать длинные имена полей, вы можете сделать это так же, как и с бесплатнымитаблицы, но таблица должна быть частью базы данных.Это также будет работать для курсоров, если вы сделаете это за один раз и не будете пытаться изменить структуру впоследствии.

Хотя я добавил туда код для создания TableA, TableB, вы говорите, что данные этих таблиц будут получены изExcel.Вы на самом деле не предоставили подробную информацию о его части в Excel (как данные представлены - это диапазоны данных?).Существует большая вероятность, что вы создадите эти две таблицы, просто выбрав данные из Excel, используя ODBC / OLEDB напрямую.

Для получения данных из Excel я разместил подробную информацию о Foxite, вы можете проверитьпост в этой ссылке .Я не даю здесь никакого примера кода, так как на самом деле я еще не знаю часть Excel.

Предполагая, что мы получили данные из Excel, давайте проверим другие части (кстати, в таблице B id называется внешним ключом, а неосновной. Он связывает строки в верхней части TableB (TableA).

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

Мы можем использовать либо команду REPLACE (команда xBase), либо команду SQL Update, чтобы выполнить это.Давайте не будем думать о различиях здесь (на самом деле не стоит) и выберем SQL Update для выполнения работы (синтаксис можно будет использовать повторно и в других базах данных - скажем, MS SQL server, 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.Есть несколько способов написания этих функций, и имхо проще всего было бы написать их как 2 отдельных файла .prg, где эти prg-файлы находятся в вашем пути поиска, когда вы выполняете указанную выше команду SQL:

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

Функции, указанные в приведенной вами ссылке, мне не подходят для работы (но его было непросто понять и проверить, поэтому я не тратил время на тщательную проверку).

Я не уверен, что один лист, содержащий обе таблицы, подходит.Я не могу вспомнить, если бы коллекция таблиц была членом 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 равно .T., ... - как в Type = 0.

В этом случае я не использовал внутреннее соединение, потому что здесь достаточно использовать таблицу А из таблицы А (то же самое в других бэкэндах, хотя общая тенденция состоит в том, чтобы писать это с использованием соединения).

РЕДАКТИРОВАТЬ:Добавил код в качестве другого ответа.

По вашим вопросам: Внутреннее объединение не требуется для явного определения, здесь есть неявное объединение.Вместо того, чтобы писать обновление SQL, я предпочел использовать возможности VFP xBase и вместо этого использовать scan ... endcan (может быть с 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 для тех, которые возвращают значение - но на самом деле любой PROCEDURE \ FUNCTION возвращает значение, поэтому, скажем, те, которыеиспользуются для возвращаемого значения.)

Я не думаю, что логический тип автоматически означает «1» или «0», правильно?Если это так, я бы оставил его как тип int, потому что для столбца типа вход всегда определяется как 1 или 0.

Хорошо, формально сложно ответить.В VFP логический тип данных определяется литералами .F.и т.Вы можете разыграть (от aBoolean до int), и вы получите 0 и 1 соответственно.Или вы можете разыграть (1 как логическое), чтобы получить .T.IOW 1 \ 0 и .T..F.являются взаимозаменяемыми в некотором смысле.Все зависит от того, где вы хотите его использовать.Если данные поступают из внешнего источника, они поступают как 1 \ 0.Просто приведение или получение его в столбце логического типа данных (неявное приведение) обрабатывается как .T..F.Или вы отправляете данные из логического во внешний источник (например, XML, сервер MS SQL, postgreSql, другой источник данных OLEDB \ ODBC), а затем .T..F.кастуется как 1 \ 0.

...