数据库、关系查询

Database, relational query

化学品有一个商品名(这是通常所说的)和一个实际的化学名称。我需要查找商品名称,找到其正确的化学名称,然后获取该化学品的特性。例如:

$tradeName = "Voranol"

if $tradeName == "Voranol" then
    $productName = "Polyether Polyol"
    $flare = "List I"
    $bay = "1"
    $listPos = 3
EndIf

我有一个包含大量产品的 .au3 文件。它工作正常,但做起来有点乏味 if $tradeName == "Name1" or $tradeName == "Name2" or $tradeName == "Name4" or $tradeName == "Name5"。我还需要能够以编程方式对其进行编辑。这是我现在使用的:

if $product == "dowanol pmb" or $product == "dowanol pma" or $product == "dipropylene glycol" or $product == "dowanol pnp" or $productCheck2 == "dowanol pmb" or $productCheck2 == "dowanol pma" or $productCheck2 == "dipropylene glycol" or $productCheck2 == "dowanol pnp" then
    $result = "DIPROPYLENE GLYCOL"
    $bay = 4
    $useFlare = 1
    $listPos = 2

elseif $product == "glycol blend" then
    $result = "GLYCOL"
    $bay = 3
    $useFlare = 2
    $listPos = 1

elseif $product == "isopar e" or $product == "isopar c" or $product == "isopar h" or $productCheck2 == "isopar h" then
    $result = "PETROLEUM NAPTHA"
    $bay = 5
    $useFlare = 0
    $listPos = 1 

EndIf
; Note: 0 = No Flare, 1 = Normal Flare, 2 = CAS Flare

Autoit 中最好的数据库解决方案是 SQLite。

如果你想像专业人士那样做,你应该使用 SQLite。

#include <SQLite.au3>
#include <SQLite.dll.au3>

Local $hQuery, $aRow
_SQLite_Startup()
ConsoleWrite("_SQLite_LibVersion=" & _SQLite_LibVersion() & @CRLF)
_SQLite_Open()
; Without $sCallback it's a resultless statement
_SQLite_Exec(-1, "Create table tblTest (a,b int,c single not null);" & _
        "Insert into tblTest values ('1',2,3);" & _
        "Insert into tblTest values (Null,5,6);")

Local $d = _SQLite_Exec(-1, "Select rowid,* From tblTest", "_cb") ; _cb will be called for each row

Func _cb($aRow)
    For $s In $aRow
        ConsoleWrite($s & @TAB)
    Next
    ConsoleWrite(@CRLF)
    ; Return $SQLITE_ABORT ; Would Abort the process and trigger an @error in _SQLite_Exec()
EndFunc   ;==>_cb
_SQLite_Close()
_SQLite_Shutdown()

; Output:
; 1     1   2   3
; 2         5   6

作为更简单的解决方案,我建议使用 INI 文件作为数据库。

[Voranol]
ProductName=Polyether Polyol
Flare=List I
Bay=1
ListPos=3

[Chem2]
ProductName=..
...

然后

   ; Read the INI section names. This will return a 1 dimensional array.
    Local $aArray = IniReadSectionNames($sFilePath)

    ; Check if an error occurred.
    If Not @error Then
        ; Enumerate through the array displaying the section names.
        For $i = 1 To $aArray[0]
            MsgBox($MB_SYSTEMMODAL, "", "Section: " & $aArray[$i])
        Next
    EndIf

现在 windows INI 文件大小 (32kb) 有限制。 这意味着,如果违反该限制,某些 Autoit INI 功能将无法工作。

这可以通过使用您自己的 INI 函数来解决:

Func _IniReadSectionNamesEx($hFile)
    Local $iSize = FileGetSize($hFile) / 1024
    If $iSize <= 31 Then
        Local $aNameRead = IniReadSectionNames($hFile)
        If @error Then Return SetError(@error, 0, '')
        Return $aNameRead
    EndIf
    Local $aSectionNames = StringRegExp(@CRLF & FileRead($hFile) & @CRLF, '(?s)\n\s*\[(.*?)\]s*\r', 3)
    If IsArray($aSectionNames) = 0 Then Return SetError(1, 0, 0)
    Local $nUbound = UBound($aSectionNames)
    Local $aNameReturn[$nUbound + 1]
    $aNameReturn[0] = $nUbound
    For $iCC = 0 To $nUBound - 1
        $aNameReturn[$iCC + 1] = $aSectionNames[$iCC]
    Next
    Return $aNameReturn
EndFunc

Func _IniReadSectionEx($hFile, $vSection)
    Local $iSize = FileGetSize($hFile) / 1024
    If $iSize <= 31 Then
        Local $aSecRead = IniReadSection($hFile, $vSection)
        If @error Then Return SetError(@error, 0, '')
        Return $aSecRead
    EndIf
    Local $sFRead = @CRLF & FileRead($hFile) & @CRLF & '['
    $vSection = StringStripWS($vSection, 7)
    Local $aData = StringRegExp($sFRead, '(?s)(?i)\n\s*\[\s*' & $vSection & '\s*\]\s*\r\n(.*?)\[', 3)
    If IsArray($aData) = 0 Then Return SetError(1, 0, 0)
    Local $aKey = StringRegExp(@LF & $aData[0], '\n\s*(.*?)\s*=', 3)
    Local $aValue = StringRegExp(@LF & $aData[0], '\n\s*.*?\s*=(.*?)\r', 3)
    Local $nUbound = UBound($aKey)
    Local $aSection[$nUBound +1][2]
    $aSection[0][0] = $nUBound
    For $iCC = 0 To $nUBound - 1
        $aSection[$iCC + 1][0] = $aKey[$iCC]
        $aSection[$iCC + 1][1] = $aValue[$iCC]
    Next
    Return $aSection
EndFunc

数据库选项的另一种替代方法是使用 XML 文档来存储产品(化学品)信息。

您可以使用 XPath 轻松查询 XML 以获得产品 name/properties。维护文件也相当容易。您甚至可以创建一个 XML 架构来验证以确保您的文件在修改后仍然有效。

AutoIt 中 XML 的处理可以通过几种不同的方式完成:

  • 正在创建一个 MSXML 对象
  • 运行 类似 xmlstarlet
  • 的命令行工具
  • 从命令行使用 XPath/XQuery/XSLT 处理器(即 java 到 运行 Saxon

运行 像 Saxon 这样的东西可能对你的需要有点过分了。

MSXML 还不错,应该已经存在多个 UDF。

Xmlstarlet 将是我的投票。 (注意:我以前没有以这种方式使用过 xmlstarlet。我是 Saxon 的超级粉丝,几乎只使用它。特别是对于 AutoIt,我结合使用了 MSXML 和 Saxon;Saxon 到将复杂数据转换为更小、更简单的子集,然后 MSXML 对该子集执行简单的 xpath 查询。但是,如果我打算做这样的事情,我会认真考虑 xmlstarlet。 )

此外,如果您的数据增长到单个 XML 文件没有意义的程度,您始终可以将其拆分为一组较小的文件;个别产品也许。您可能还会达到将这些文件加载​​到实际的 XML 数据库中可能有意义的程度(eXistdb 是一个选项)。

这是您的 XML(架构和实例)的一个简单示例:

XSD (products.xsd)

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
  <xs:element name="products">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="product"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="product">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="name"/>
        <xs:element ref="tradenames"/>
      </xs:sequence>
      <xs:attribute name="bay" use="required" type="xs:integer"/>
      <xs:attribute name="listpos" use="required" type="xs:integer"/>
      <xs:attribute name="useflare" use="required" type="xs:integer"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="tradenames">
    <xs:complexType>
      <xs:sequence>
        <xs:element maxOccurs="unbounded" ref="name"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="name" type="xs:string"/>
</xs:schema>

XML (products.xml)

<products xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="products.xsd">
    <product bay="4" useflare="1" listpos="2">
        <name>DIPROPYLENE GLYCOL</name>
        <tradenames>
            <name>dowanol pmb</name>
            <name>dowanol pma</name>
            <name>dipropylene glycol</name>
            <name>dowanol pnp</name>
        </tradenames>
    </product>
    <product bay="3" useflare="2" listpos="1">
        <name>GLYCOL</name>
        <tradenames>
            <name>glycol blend</name>
        </tradenames>
    </product>
    <product bay="5" useflare="0" listpos="1">
        <name>PETROLEUM NAPTHA</name>
        <tradenames>
            <name>isopar e</name>
            <name>isopar c</name>
            <name>isopar h</name>
        </tradenames>
    </product>
</products>

这里有一点 AutoIt,它使用 MSXML 来演示加载 XML(针对 XSD 进行验证)并搜索商品名称包含 "glycol".

AutoIt

;~  AutoIt Version: 3.3.14.2

;String to search on.
$searchString = "glycol"
ConsoleWrite("Search string: '" & $searchString & "'" & @CRLF)

;XPath for searching trade names. Search string is injected (code injection; escaping of strings would be a very good idea!).
$xpath_tradename = "/products/product[tradenames/name[contains(.,'" & $searchString & "')]]"
ConsoleWrite("XPath: '" & $xpath_tradename & "'" & @CRLF)

$msxml = ObjCreate('MSXML2.DOMDocument.6.0')
If IsObj($msxml) Then
    $msxml.async = False
    $msxml.validateOnParse = True
    $msxml.resolveExternals = True
    $msxml.setProperty("SelectionLanguage", "XPath")
    $msxml.load('products.xml')
    If $msxml.parseError.errorCode = 0 Then
        $prods = $msxml.SelectNodes($xpath_tradename)
        If IsObj($prods) And $prods.Length > 0 Then
            ConsoleWrite("Number of products found: '" & $prods.Length & "'" & @CRLF)
            For $prod In $prods
                ConsoleWrite(@CRLF & "------ PRODUCT ------" & @CRLF)
                ConsoleWrite("Product name: '" & $prod.SelectSingleNode('name').text & "'" & @CRLF)
                ConsoleWrite("Product bay: '" & $prod.getAttribute('bay') & "'" & @CRLF)
            Next
            ConsoleWrite(@CRLF)
        Else
            ConsoleWrite("PRODUCT NOT FOUND" & @CRLF)
        EndIf
    Else
        MsgBox(17, 'Error', 'Error opening XML file: ' & @CRLF & @CRLF & $msxml.parseError.reason)
        SetError($msxml.parseError.errorCode)
    EndIf
EndIf

控制台输出

Search string: 'glycol'
XPath: '/products/product[tradenames/name[contains(.,'glycol')]]'
Number of products found: '2'

------ PRODUCT ------
Product name: 'DIPROPYLENE GLYCOL'
Product bay: '4'

------ PRODUCT ------
Product name: 'GLYCOL'
Product bay: '3'

I need to look up a trade name, find its proper chemical name, then get properties of that chemical.

关系查询(SQLite

  1. (创建和)使用 _SQLite_Open() 打开数据库。
  2. 使用 _SQLite_Exec().
  3. 创建 table 、索引和视图(并插入记录)
  4. 使用 _SQLite_GetTable2d() return 的查询结果作为二维数组。

结构

SQLite data types. Inserting records to product and substance table may be .

Table product

定义商品名称。

id name
1 dowanol pmb
2 dowanol pma
3 dipropylene glycol
4 dowanol pnp
5 glycol blend
6 isopar e
7 isopar c
8 isopar h
9 Polyether Polyol
10 Voranol

创作:

CREATE TABLE IF NOT EXISTS product (
   id   INTEGER PRIMARY KEY,
   name TEXT    UNIQUE
);

插入记录:

INSERT INTO product (name) VALUES ('dowanol pmb');

Table substance

定义化学名称(和特性)。

id name flare bay
1 Polyether Polyol 1 1
2 DIPROPYLENE GLYCOL 1 4
3 GLYCOL 2 3
4 PETROLEUM NAPHTA 0 5

创作:

CREATE TABLE IF NOT EXISTS substance (
   id    INTEGER PRIMARY KEY,
   name  TEXT    UNIQUE,
   flare INTEGER,
   bay   INTEGER
);

插入记录:

INSERT INTO substance (name, flare, bay) VALUES ('Polyether Polyol', 1, 1);

Tablerelation

定义 productsubstance table 的记录之间的关系(使用 foreign keys)。

id product substance
1 1 2
2 2 2
3 3 2
4 4 2
5 5 3
6 6 4
7 7 4
8 8 4
9 9 4
10 10 1

创作:

CREATE TABLE IF NOT EXISTS relation (
   id        INTEGER PRIMARY KEY,
   product   INTEGER REFERENCES product(id),
   substance INTEGER REFERENCES substance(id),
   UNIQUE(  /* Constraint applies to *combination* of columns. */
      product,
      substance
   )
);

插入记录:

INSERT INTO relation (
   product,
   substance
) VALUES (
   (SELECT id FROM product   WHERE name = 'dowanol pmb'),
   (SELECT id FROM substance WHERE name = 'DIPROPYLENE GLYCOL')
);

或者只是:

INSERT INTO relation (product, substance) VALUES (0, 1);

查看view_relation

View (stored query), joining (combines) productsubstance tables' 字段,根据 relation table 的记录(作为虚拟table,否定每个查询都需要包含基础 JOIN 运算符。

name_product name_substance flare bay
dowanol pmb DIPROPYLENE GLYCOL 1 4
dowanol pma DIPROPYLENE GLYCOL 1 4
dipropylene glycol DIPROPYLENE GLYCOL 1 4
dowanol pnp DIPROPYLENE GLYCOL 1 4
glycol blend GLYCOL 2 3
isopar e PETROLEUM NAPTHA 0 5
isopar c PETROLEUM NAPTHA 0 5
isopar h PETROLEUM NAPTHA 0 5
Polyether Polyol PETROLEUM NAPTHA 0 5
Voranol Polyether Polyol 1 1

创作:

CREATE VIEW IF NOT EXISTS view_relation AS
   SELECT
      product.name    AS name_product,
      substance.name  AS name_substance,
      substance.flare,
      substance.bay
   FROM
      relation
   LEFT OUTER JOIN product   ON relation.product   = product.id
   LEFT OUTER JOIN substance ON relation.substance = substance.id
   ORDER BY
      product.id ASC
;

按商号查询

name_product name_substance flare bay
Voranol Polyether Polyol 1 1

查询:

SELECT
   *
FROM
   view_relation
WHERE
   name_product = 'Voranol'
;

或(无视图):

SELECT
   product.name    AS name_product,
   substance.name  AS name_substance,
   substance.flare,
   substance.bay
FROM
   relation
WHERE
   product.name = 'Voranol'
LEFT OUTER JOIN product   ON relation.product   = product.id
LEFT OUTER JOIN substance ON relation.substance = substance.id
;

按物质查询

name_product name_substance flare bay
dowanol pmb DIPROPYLENE GLYCOL 1 4
dowanol pma DIPROPYLENE GLYCOL 1 4
dipropylene glycol DIPROPYLENE GLYCOL 1 4
dowanol pnp DIPROPYLENE GLYCOL 1 4

查询:

SELECT
   *
FROM
   view_relation
WHERE
   name_substance = 'DIPROPYLENE GLYCOL'
;

或(无视图):

SELECT
   product.name    AS name_product,
   substance.name  AS name_substance,
   substance.flare,
   substance.bay
FROM
   relation
WHERE
   substance.name = 'DIPROPYLENE GLYCOL'
LEFT OUTER JOIN product   ON relation.product   = product.id
LEFT OUTER JOIN substance ON relation.substance = substance.id
;

AutoIt

创建(并将记录插入)单个 table 并查询它的示例:

#include <SQLite.au3>
#include <SQLite.dll.au3>
#include <Array.au3>

Global Const $g_iRecords   = 50
Global Const $g_sFileDb    = @ScriptDir & '\example.sqlite'
Global       $g_aCreate    = [ _
                             'CREATE TABLE IF NOT EXISTS example (id INTEGER PRIMARY KEY, name TEXT UNIQUE);' & @LF, _
                             '--CREATE INDEX IF NOT EXISTS index_example_name ON example(name);' & @LF _
                             ]
Global Const $g_sInsert    = 'INSERT INTO example (name) VALUES (%i);\n'
Global Const $g_sTransact1 = 'BEGIN TRANSACTION;' & @LF
Global Const $g_sTransact2 = 'END TRANSACTION;' & @LF
Global Const $g_sQuery     = 'SELECT * FROM example ORDER BY id ASC;'
Global Const $g_sMsgError  = 'sqlite.dll not found.' & @LF

Global       $g_hDb        = 0

Main()

Func Main()
    Local $sQuery     = ''
    Local $iResultCol = 0
    Local $iResultRow = 0
    Local $aResult

    For $i1 = 0 To UBound($g_aCreate, 1) -1
        $sQuery &= $g_aCreate[$i1]
    Next

    For $i1 = 1 To $g_iRecords
        $sQuery &= StringFormat($g_sInsert, _SQLite_FastEscape('example' & $i1))
    Next

    $sQuery = $g_sTransact1 & $sQuery & $g_sTransact2
    ConsoleWrite($sQuery)

    _SQLite_Startup()
    If @error Then
        ConsoleWrite($g_sMsgError)
        Exit
    EndIf

    _SQLite_Open($g_sFileDb)
    _SQLite_Exec($g_hDb, $sQuery)
    _SQLite_GetTable2d($g_hDb, $g_sQuery, $aResult, $iResultRow, $iResultCol)
    _SQLite_Close($g_hDb)
    _SQLite_Shutdown()

    _ArrayDisplay($aResult)
    Exit
EndFunc