跳转到内容

从 Zip/SQLite 开始编程 Gambas

来自 Wikibooks,开放世界中的开放书籍

SQLite 数据库

[编辑 | 编辑源代码]

SQLite Logo

数据库是硬盘上具有结构的文件,因此它可以存储大量信息并快速访问它。

SQLite 是一种类型的数据库。它是由 Dwayne Richard Hipp(1961 年出生于北卡罗来纳州)编写的。它于 2000 年 8 月首次发布。它是公共领域的,这意味着任何人都可以免费使用它。Google Chrome、Firefox、智能手机的 Android 操作系统、Skype、Adobe Reader 和 iPhone 都使用 SQLite。它就是很好。您应该把它读作“S Q L Lite”,正如维基百科所说。

数据库将信息存储在表中。Gambas 有一个表格视图。它也有行和列。您可以将数据库表视为数据库文件中的一个不可见的表格视图。

  • 行称为记录。列称为字段

例如,一位教师可能有一个包含“学生”表的数据库。在该表中,每位学生都有一行。查看整行,您将看到学生 ID、姓氏、名字、性别、出生日期、地址、电话号码。这些是字段。它们是列。

学生 ID 姓氏 名字 性别 出生日期 地址 电话号码
2019001 Mary Smith F 2008-06-23 21 Holly Crt, Bundaberg 07324657
2019002 Jim Jones M 2003-02-19 14 Primrose St, Bundaberg 07123456
2019003 Lucy Watkins F 2003-10-05 5 Flower St, Bundaberg 07938276

这可能是一个表格视图或数据库文件中的一个表。

每个数据库表都必须有一个主键。每条记录都必须为此字段具有唯一的 value:一个其他人都不共享的 value。最简单的办法是称之为RecID 并编号为 1、2、3... 等。在上表中,主键将是学生 ID,它是一个整数。前四位数字是入学年份。(我们可能会有另一个名为入学年份的列,并且只为学生 ID 使用顺序号。)

在 SQLite 中,所有数据都以字符串形式存储,即使您可能将某些列指定为整数、其他列指定为字符串,而其他列指定为日期。SQLite 非常宽容:您可以将非数字内容放入整数列等等,但尽量不要这样做。空单元格为 NULL。也要尽量避免它们。当您创建一个新的空白记录时,将 values 初始化为空字符串“”。

包含数据库功能

[编辑 | 编辑源代码]

Including the database component in Gambas

SQLite 是 Gambas 的一个组件(可选部分)。还有一个数据库访问组件。在项目菜单 > 属性... > 组件页面上,确保选中gb.dbgb.db.sqlite3。如果您的项目中没有这些组件,您将在尝试运行程序时立即收到错误。

SQL - 结构化查询语言

[编辑 | 编辑源代码]

您向SQLite 发送消息,它使用一种称为SQL(“S Q L”或“sequel”,无论哪种读法都可以)的特殊语言将答案发送回您。这意味着学习另一种语言,但最常用的简单语句并不难学。无论如何,这些是我知道的唯一语句。SQL 由 Donald D. Chamberlin 和 Raymond F. Boyce 发明,并于 1974 年首次出现。SQL 是如此普遍,以至于所有编写数据库的人都知道它。它是一个国际标准。SQLite 是它的一个实现。

例如,您可能会向SQLite 发送一条消息,内容为

SELECT * FROM Students

这表示“从‘学生’表中选择所有内容”。这将为您提供整个表。或者您可能只想查看男性学生

SELECT * FROM Students WHERE Sex = 'M'

也许您想要所有人,但您希望女性排在前面,男性排在后面

SELECT * FROM Students ORDER BY Sex

这将首先获取女性,因为“F”位于“M”之前。所有女性将以随机顺序排列,男性也是如此,除非您写入

SELECT * FROM Students ORDER BY Sex DESC, LastName ASCSELECT * FROM Students ORDER BY Sex DESC, LastName ASC

这将返回一个表,其中男性排在前面(按姓氏字母顺序排列),然后是女性(按姓氏字母顺序排列)。您可能只想查看学生姓名,因此您可以写入

SELECT FirstName, LastName FROM Students ORDER BY LastName

也许您只想查看 2019 年入学的学生。现在,这是学生 ID 的一部分。您只想查看学生 ID 号以“2019”开头的学生。您可以使用“通配符”。百分号 (%) 表示“这里可以是任何内容”。

SELECT FirstName, LastName FROM Students WHERE StudentID LIKE '2019%'

当您将这些SELECT 语句发送到数据库时,SQLite 将向您发送回一个表。Gambas 称之为RESULT。假设您有一个名为db1 的数据库(就 Gambas 而言),它连接到您的硬盘上的实际数据库文件MyStudentDatabase.sqlite。您需要一个结果来存储回复

Dim res as Result
res = db1.exec("SELECT * FROM Students")

res 包含您请求的信息。您可能希望打印这些信息,或在表格视图中显示它们,或者在数组中内部保存它们以便对其进行计算。您需要循环遍历这些记录,因此

While res.Available
 'do something with res!FirstName, res!LastName and res!DateOfBirth etc
 res.MoveNext
Wend

对于在表格视图中显示信息,有一个特殊的事件会在每次单元格必须在其内容绘制到屏幕上时触发。如果您的记录集很大,这将特别有用。表格视图不需要在自身中包含所有记录的所有信息。它可以在需要显示时获取信息。这里需要注意的是:如果您依赖于表格视图中的所有信息,那么它可能存在或可能不存在。这是一个使用_Data 事件的示例,在需要显示表格视图中的特定单元格时,从结果表 res 中获取信息

Public Sub TableView1_Data(Row As Integer, Column As Integer)
  res.MoveTo(row)
  If Column = 0 Then
        TableView1.Data.Text = res!FirstName
  Else
        TableView1.Data.Text = res!LastName
  Endif
End

请注意,TableView1.Data.Text 表示单元格中的文本。

请注意,我们使用result.MoveTo 转到特定记录,使用result.MoveNext 一次遍历一个记录,并使用result.Available 检查是否还有另一个记录要移到。在设置表格视图中要包含的行数时,result.RecordCount 很有用。

除了访问数据库中的信息之外,使用数据库时,您还需要能够

  1. 添加记录
  2. 删除记录
  3. 修改记录

除了最简单的数据库之外,所有数据库都包含多个表。表可以相互链接,因此记录可以包含指向其他表中适用的行的指路牌。指路牌是另一个表中记录的记录 ID 或其他主键。例如,政治候选人的数据库可能包含指向其所属政党的指路牌。SQL 非常聪明,它可以同时查看这两个表,以为您提供您需要的信息,例如这两个表的“连接”。(候选人属于特定政党,而政党对各种问题制定了政策。)

SELECT Candidate,PolicyOnPensions FROM Candidates,Parties WHERE Candidate.PartyID = Parties.PartyID AND Candidates.Electorate="Fairfax"

包含随机数的单表数据库

[编辑 | 编辑源代码]

下一个程序来自https://kalaharix.wordpress.com/Gambas/creating-a-databases-and-tables-from-Gambas/,稍作调整。它在您的主文件夹中创建一个名为Test.sqlite 的数据库,用随机的两位数填充它,然后访问数据库以在表格视图中显示它们。

您需要一个包含名为tv1 的表格视图的窗体。将其设置得又长又薄,因为它有两列。

Gambas form with tableview Gambas form showing a database table

代码为

' Gambas class file

Private db1 As New Connection
Private rs As Result

Public Sub SetupTableView()

  tv1.header = GridView.Horizontal
  tv1.grid = True
  tv1.Rows.count = 0
  tv1.Columns.count = 2
  tv1.Columns[0].text = "RecID"
  tv1.Columns[1].text = "Value"
  tv1.Columns[0].width = 55
  tv1.Columns[1].width = 55

End

Public Sub CreateDatabase()

  db1.Type = "sqlite"
  db1.host = User.home
  db1.name = ""

  'delete an existing test.sqlite
  If Exist(User.home & "/Test.sqlite") Then
    Kill User.home & "/Test.sqlite"
  Endif

  'create test.sqlite
  db1.Open
  db1.Databases.Add("Test.sqlite")
  db1.Close

End

Public Sub MakeTable()

  Dim hTable As Table

  db1.name = "Test.sqlite"
  db1.Open
  hTable = db1.Tables.Add("RandomNumbers")
  hTable.Fields.Add("RecID", db.Integer)
  hTable.Fields.Add("Value", db.Integer)
  hTable.PrimaryKey = ["RecID"]
  hTable.Update

End

Public Sub FillTable()

  Dim i As Integer
  Dim rs1 As Result

  db1.Begin
  rs1 = db1.Create("RandomNumbers")
  For i = 1 To 10000
    rs1!RecID = i
    rs1!Value = Rand(10, 99)
    rs1.Update
  Next
  db1.Commit

Catch
  db1.Rollback
  Message.Error(Error.Text)

End

Public Sub ReadData()
  'read the database
  Dim SQL As String = "SELECT * FROM RandomNumbers"
  rs = db1.Exec(SQL)
End

Public Sub Form_Open()
  SetupTableView
  CreateDatabase
  MakeTable
  FillTable
  ReadData
End

Public Sub Form_Activate()
  'change the rowcount of the gridview from 0 to the number of records.
  'This triggers the data handling event
  tv1.Rows.Count = rs.Count
End

Public Sub tv1_Data(Row As Integer, Column As Integer)
  rs.moveTo(row)
  If Column = 0 Then tv1.Data.Text = rs!RecID Else tv1.Data.Text = rs!Value
  'If Column = 0 Then tv1.Data.Text = Str(rs["RecID"]) Else tv1.Data.Text = Str(rs["Value"])
  'Either of these two lines will do it.
End

Public Sub Form_Close()
  db1.Close
End

当您使用数据库时,会创建一个临时“日志”文件。该文件在“提交”时会合并到数据库中。如果您不想提交,您可以将数据库“回滚”到您进行最新更改之前的样子。临时文件包含“事务”,这意味着您刚刚进行的最新更改数据库的工作。这就是db1.Begindb1.Commitdb1.Rollback 的含义。

上面的程序是制作数据库时需要调整的一个很好的模板。

现金支出应用程序

[编辑 | 编辑源代码]

此应用程序保存现金支出的记录。您可以将每项支出分配到一个类别。每次分配到类别时,都会计算出类别的总计,并且您可以查看支出中的几分之几用于每个类别。

如果您知道自己花了例如 100 欧元,但您只能说明例如 85 欧元,那么您可以在类别之间分配剩余的 15 欧元。

在放手编写代码之前,以及查看窗体之后,我们将看一下设计此类应用程序的过程。

Cash Spending Gambas Form

Gambas Cash Spending File Menu Gambas Cash Spending Data Menu


文件菜单包含MenuNewDatabase、MenuOpenMenuQuit 项。

数据菜单包含MenuNewSpending、MenuNewCategory、MenuClearSpending、MenuClearCategories、MenuRound、MenuUnselectAll、MenuCalculateMenuCopy 项。

帮助菜单是可选的。

您无法完全看到其名称的文本框是tbDistribute

Cash Spending Application in Gambas, running

程序启动时会打开上次打开的数据库文件,如果这是第一次使用,则会提示创建新的数据库文件,或者如果上次打开后你偷偷地移动了数据库文件,则会提示你找到它。它还会在支出类别表格视图中添加一个空白行。

当为选定的支出行选择一个类别时(点击类别行中的任何位置,但不包括名称列,然后按 Enter 键),类别总计和百分比将重新计算。

目标文本框中输入内容是可选的。如果文本框中有数字,则会计算“剩余待分配”金额。

在内部,数据库有两个表,名为支出和类别。您可以看到对应于这两个数据库表的两个表格视图。以下是每个表中的字段。

Fields in Cash Spending Database

两个主键是支出ID类别ID。它们按顺序对记录进行编号(1、2、3...)。

支出表的类别字段包含一个数字,当您在类别表中查找该数字时,会得到类别名称。这样做的好处是:如果您更改类别名称的拼写,只需更改一次即可。

隐藏列

[edit | edit source]

用户不需要看到记录 ID。它们是数据库内部的。它们必须是唯一的:每条记录必须有自己的记录 ID。它们是支出类别表的 主键。它们将是表格视图中的第一列,但将隐藏(宽度为零)。此外,在支出表中,用户不希望看到类别 ID(对某个类别的引用)。它将是支出表中的最后一列,也是宽度为零。列从零开始,因此它是第 5 列,位于金额列的右侧。

列出任务

[edit | edit source]

在用铅笔和纸张勾勒出表单设计并规划好表格和字段之后,我们接下来考虑希望程序执行的操作。需要注意的是,数据库可以执行的操作:添加、删除、修改(以及显示数据)。下面列出了要执行的任务。这些将是子程序。

数据库
新建数据库 在磁盘上创建一个新的数据库文件,其中包含两个表。
打开数据库 打开数据库并在启动时显示其中的内容。
常规
计算 为每个类别汇总总计并计算百分比。
计算总计 支出和类别表中金额的总计。
设置表格视图 合适的行数和列数以及列标题。
支出表
新建支出 向支出表添加记录。
显示支出 在 tv1(表格视图)中显示支出表中的内容。
整理支出表 实际上是显示支出的最后部分。交替显示蓝色线条。
汇总支出 “计算总计”的一部分;汇总所有支出总计。
清除类别(使其成为右键菜单)。
在选定行上按 DEL 或 BACKSPACE 键时删除记录。
类别表
新建类别 向类别表添加记录。
显示类别 在 tvCategories 中显示类别表中的内容。
整理类别表 显示类别的最后部分。交替显示蓝色线条。
汇总类别 “计算总计”的一部分;汇总所有类别金额。
将默认类别插入类别表(菜单项)。
在类别行上按 Enter 键 在行上按 Enter 键会在选定的支出行上插入类别。
在选定行上按 DEL 或 BACKSPACE 键时删除记录。
其他功能
计算剩余的分配金额。
将剩余的金额分配到各个类别。
帮助窗口
将当前使用的数据库保存在设置中,以便下次使用。
将所有内容复制为文本,以便粘贴到文字处理文档中。
将数字四舍五入到整数欧元(并检查总计是否差一)。
一个“退出”菜单项,用于关闭程序。
有用函数
从 ID 获取类别名称 给定 CatID 编号,返回类别名称(字符串)。
整理 给定用户选择的文件名,删除非法字符。

现在开始编程。编写子程序。计算子程序何时会被调用以执行其工作。一些子程序可以被分配到菜单中。另一些子程序可以在您点击某些内容时执行。您是将要使用此程序的人:您希望点击按钮吗?您希望在添加新类别或新支出交易时弹出一个窗口吗?有没有什么好的方法,即直观的方法,可以让事情自然而然地发生,正如新用户所期望的那样?我们思考一下,并提出一些想法。

  • 我们可以从每个表中的空白行开始,您可以直接在这些行中输入内容。
  • 在单元格中完成输入后,保存该单元格。避免点击保存按钮。
  • 在行中的最后一个单元格中按Enter键,会创建新的一行。
  • 当选中类别行并且用户按Enter键时,会将该类别放入选定(高亮显示)的支出行中。移动到下一行没有类别的支出行,以便您可以点击类别行并按Enter键输入类别。因此,您可以在输入完其他所有内容后,在最后为所有行输入类别。
  • 在启动时,打开上次打开的数据库。如果没有,则可以选择创建一个新数据库,或者浏览以找到您移动过的数据库,或者其他人可能通过 USB 或电子邮件给您的数据库。
  • 通过点击类别来编辑类别。
  • 通过点击支出表中的单元格来编辑单元格(类别除外,只需在类别表中的一行上按Enter键即可插入新的类别)。
  • 当您将支出行分配到某个类别时,重新计算所有类别的百分比。
  • 当您更改目标文本框中的总计金额时,执行减法运算,以计算出剩余的待分配金额。
  • 在空白的单元格中放入空白,而不是零。
  • 在任一表格视图中按DeleteBackspace键将删除选定(高亮显示)的行,并将从数据库中删除其记录。没有疑问,没有确认请求,它只是执行操作。一次只能删除一行,如果您错误地按了Delete键,重新输入非常容易。
  • 如果表格视图行上的第一个单元格中包含记录 ID 号码,则该记录存在,保存操作只需更新它。如果它是空白的,则数据库必须首先创建一个新的记录,并为其分配下一个最高的记录号码,将记录号码放在第一个单元格中,然后更新它。

以下是FMain表单上的对象名称。

面板:Panel1(粉色)、Panel2(蓝色)。

显示“支出”、“类别”、“目标:”、“= 已完成:”、“+ 剩余待分配:”、“金额:”的标签

名为“LabSpendingTotal”和“LabCategoriesTotal”的标签,位于表格视图的右上角。

表格视图:tv1 用于支出,tvCategories 用于类别。

文本框:tbTarget、tbDone、tbToDo、tbDistribute。

按钮:bDistribute。

文件菜单:MenuNewDatabase、MenuOpen(Ctrl-O)、MenuQuit(Ctrl-Q)。

数据菜单:MenuNewSpending(Ctrl-N)、MenuNewCategory(Ctrl-K)、MenuClearSpending、MenuClearCategories、MenuDefaultCategories、MenuRound(Ctrl-R)、MenuUnselectAll(Ctrl-空格键)、MenuCalculate(F4)、MenuCopy(Ctrl-C)。

帮助菜单:帮助和说明(F1)(打开一个名为帮助的单独表单。在上面放置您喜欢的内容。)

类别菜单(不可见,因此不在主菜单栏上):MenuClearCategory(当您右键单击支出表中的类别单元格时,该菜单会弹出)。

这是代码。代码后面是对 SQL 语句的解释。

Public fdb As New Connection 'finance database
Public rs As Result 'result set after querying database
Public SQL As String

Public Sub Form_Open()

  SetUpTableViews
  If IsNull(Settings["Database/host"]) Then
    Select Case Message.Question("Create a new data file, or open an existing one?", "New...", "Open...", "Quit")
      Case 1 'new
        NewDatabase
      Case 2 'open
        OpenDatabase(Null, Null)
      Case Else
        Quit
    End Select
  Else
    OpenDatabase(Settings["Database/host"], Settings["Database/name"])
  Endif

End

Public Sub Form_Close()
  fdb.Close 'close connection
End

Public Sub OpenDatabase(dbHost As String, dbName As String) 'if these are null, ask where the database is

  If Not Exist(dbHost &/ dbName) Or IsNull(dbHost) Then 'it's not where it was last time, or path not supplied
    Dialog.Title = "Where is the database?"
    Dialog.Filter = ["*.db"]
    Dialog.Path = User.Home &/ "Documents/"
    If Dialog.OpenFile() Then Return ' User pressed Cancel; still can't open a database
    Dim s As String = Dialog.Path
    Dim p As Integer = RInStr(s, "/") 'position of last slash
    fdb.host = Left(s, p)
    fdb.Name = Mid(s, p + 1)
  Else
    fdb.host = dbHost
    fdb.Name = dbName
  End If

  Try fdb.Close
  fdb.type = "sqlite3"
  Try fdb.Open
  If fdb.Opened Then
    FMain.Caption = fdb.host &/ fdb.Name
    Settings["Database/host"] = fdb.host
    Settings["Database/name"] = fdb.Name
  Else
    Message.Info("<b>Couldn't connect.</b><br><br>... please try again or create a new database.")
    Return
  Endif

  ShowSpending
  ShowCategories
  Calculate

End

Public Sub NewDatabase()

  Dialog.Path = User.Home & "/" 'setting it to "~/" doesn't work
  Dialog.Title = "Create a New Database"
  If Dialog.SaveFile() Then Return 'clicked Cancel
  Dim s As String = Dialog.Path & ".db"
  Dim p As Integer = RInStr(s, "/") 'position of last slash
  Dim FName As String = Mid(s, p + 1)
  fdb.host = Left(s, p)
  fdb.Name = "" 'This MUST be left blank. If not, database file will not be created
  fdb.Type = "sqlite3"

  If Exist(s) Then Kill s 'delete existing file of that name
  fdb.Close
  Try fdb.Open 'opens a connection to the database; do this after setting properties and before creating
  If Error Then
    Message("Unable to open the database file<br><br>" & Error.Text)
    Return
  Endif
  fdb.Databases.Add(fName) 'does the creating

  fdb.Close
  Dim dbTable As Table
  fdb.name = fName
  Try fdb.Open
  If Not fdb.opened Then
    Message("Unable to open the data file")
    Return
  Endif
  dbTable = fdb.Tables.Add("Spending")
  dbTable.Fields.Add("SpendingID", db.Integer)
  dbTable.Fields.Add("TransDate", db.String)
  dbTable.Fields.Add("Category", db.Integer)
  dbTable.Fields.Add("Comment", db.String)
  dbTable.Fields.Add("Amount", db.Float)
  dbTable.PrimaryKey = ["SpendingID"]
  dbTable.Update
  rs = fdb.Create("Spending")
  If fdb.Error Then
    Message("Couldn't create the Spending table.<br><br>: " & Error.Text)
    Return
  Endif

  rs!SpendingID = 1
  rs!TransDate = ""
  rs!Category = 0
  rs!Comment = ""
  rs!Amount = 0.0
  rs.Update
  fdb.Commit
  If fdb.Error Then
    Message("Couldn't save a first record in the Spending table.<br><br>: " & Error.Text)
    Return
  Endif

  fdb.Close
  fdb.name = fName
  Try fdb.Open
  If Not fdb.opened Then
    Message("Unable to open the data file")
    Return
  Endif
  dbTable = fdb.Tables.Add("Categories")
  dbTable.Fields.Add("CatID", db.Integer)
  dbTable.Fields.Add("Category", db.String)
  dbTable.PrimaryKey = ["CatID"]
  dbTable.Update
  rs = fdb.Create("Categories")
  If fdb.Error Then
    Message("Couldn't create the Categories table.<br><br>: " & Error.Text)
    Return
  Endif

  rs!CatID = 1
  rs!Category = ""
  rs.Update
  fdb.Commit
  If fdb.Error Then
    Message("Couldn't save a first record in the Categories table.<br><br>: " & Error.Text)
    Return
  Endif

End

Public Sub DoTotals()
  labCategoriesTotal.Text = SumTheCategories()
  labSpendingTotal.text = SumTheSpending()
  tbDone.Text = labSpendingTotal.Text
End

Public Sub ShowSpending()

  rs = fdb.Exec("SELECT * FROM Spending")
  Dim L, CatID As Integer
  Dim CatName As String
  tv1.Rows.Count = 0 'clear
  If Not IsNull(rs) Then
    While rs.Available
      tv1.Rows.Count += 1
      L = tv1.Rows.max
      tv1[L, 0].text = rs!SpendingID
      tv1[L, 1].Text = rs!TransDate
      tv1[L, 2].Text = Format(rs!Amount, "0.00")
      CatName = rs!Category
      If Not IsNull(CatName) Then
        CatID = If(IsNull(Val(CatName)), -1, Val(CatName))
        If CatID > -1 Then tv1[L, 3].Text = CategoryNameFromID(CatID)
      Endif
      tv1[L, 4].Text = rs!Comment
      tv1[L, 5].Text = rs!Category 'Category ID in this hidden column
      rs.MoveNext
    Wend
  Endif
  If tv1.Rows.Count = 0 Then tv1.Rows.Count = 1
  TidySpendingTable

End

Public Sub ShowCategories()

  rs = fdb.Exec("SELECT * FROM Categories")
  Dim L As Integer
  Dim t As Float
  tvCategories.Rows.Count = 0 'clear
  If Not IsNull(rs) Then
    While rs.Available
      tvCategories.Rows.Count += 1
      L = tvCategories.Rows.max
      tvCategories[L, 0].text = rs!CatID
      tvCategories[L, 3].Text = rs!Category
      rs.MoveNext
    Wend
  Endif
  If tvCategories.Rows.Count = 0 Then tvCategories.Rows.Count = 1
  TidyCategoriesTable

End

Public Sub NewSpending()
  tv1.Rows.count = tv1.Rows.count + 1
  tv1.MoveTo(tv1.Rows.Max, 1)
  tv1.Edit
End

Public Sub NewCategory()
  tvCategories.Rows.count = tvCategories.Rows.count + 1
  tvCategories.row += 1
  tvCategories.Edit
End

Public Sub tv1_Insert()
  NewSpending
End

Public Sub tvCategories_Insert()
  NewCategory
End

Public Sub tv1_Click()

  Select Case tv1.Column
    Case 1, 2, 4
      tv1.Edit
    Case 3
      If tvCategories.Rows.Count > 0 Then
        tvCategories.SetFocus
        tvCategories.Rows[0].Selected = True
      Endif
  End Select

End

Public Sub tvCategories_Click()
  If tvCategories.Column = 3 Then tvCategories.Edit
End

Public Sub SetUpTableViews()

  Dim i As Integer
  tv1.Columns.count = 6
  tv1.Rows.count = 1
  tv1.Columns[0].Width = 0
  tv1.Columns[1].Alignment = Align.Center
  tv1.Columns[2].Alignment = Align.Right
  For i = 1 To tv1.Columns.Max - 1
    tv1.Columns[i].Width = Choose(i, 80, 80, 130, tv1.Width - tv1.ClientW - 306)
    tv1.Columns[i].Text = Choose(i, "Date", "Amount", "Category", "Comment")
  Next
  tvCategories.Columns.count = 4
  tvCategories.Rows.count = 1
  tvCategories.Columns[0].Width = 0
  For i = 1 To tvCategories.Columns.Max
    tvCategories.Columns[i].Width = Choose(i, 60, 60, tvCategories.Width - tvCategories.ClientW - 350)
    tvCategories.Columns[i].Text = Choose(i, "Total", "%", "Category")
  Next
  tvCategories.Columns[1].Alignment = Align.right
  tvCategories.Columns[2].Alignment = Align.Center
  tv1.Columns[5].Width = 0

End

Public Sub TidySpendingTable()
  For i As Integer = 0 To tv1.Rows.Max
    For j As Integer = 0 To tv1.Columns.Max
      If j = 2 Or j = 3 Then tv1[i, j].Padding = 4
      If i Mod 2 = 1 Then tv1[i, j].Background = &hF0F0FF
    Next
  Next
End

Public Sub TidyCategoriesTable()
  For i As Integer = 0 To tvCategories.Rows.Max
    For j As Integer = 1 To tvCategories.Columns.Max
      tvCategories[i, j].Padding = 4
      If i Mod 2 = 1 Then tvCategories[i, j].Background = &hF0F0FF
    Next
  Next
End

Public Sub Massage(s As String) As String
  'Doesn't like spaces or hyphens in file names. Doesn't complain; just doesn't create the file.

  Dim z As String

  For i As Integer = 0 To Len(s) - 1
    If IsLetter(s[i]) Or IsDigit(s[i]) Or s[i] = "_" Or s[i] = "." Then z &= s[i] Else z &= "_"
  Next
  Return z

End

Public Sub tvCategories_Save(Row As Integer, Column As Integer, Value As String)

  Dim RecID As Integer
  Dim OriginalValue As String = tvCategories[Row, Column].Text

  tvCategories[Row, Column].Text = Value
  If IsNull(tvCategories[Row, 0].Text) Then 'no record ID, so we need a new record
    Dim Res As Result
    SQL = "SELECT MAX(CatID) as 'TheMax' FROM Categories"
    Res = fdb.Exec(SQL)
    If IsNull(Res!TheMax) Then RecID = 1 Else RecID = Res!TheMax + 1
    tvCategories[Row, 0].Text = RecID
    SQL = "INSERT INTO Categories(CatID,Category) VALUES(" & RecID & ",'')"
    fdb.Exec(SQL)
    If fdb.Error Then Message("Couldn't save:<br><br>" & SQL & "<br><br>" & Error.Text)
  Endif
  'update the record
  RecID = tvCategories[Row, 0].Text
  SQL = "UPDATE Categories SET Category = '" & Value & "' WHERE CatID='" & RecID & "'"
  Try fdb.Exec(SQL)
  If fdb.Error Then Message("Couldn't save:" & SQL & "<br><br>" & Error.Text)
  If Value <> OriginalValue Then ShowSpending 'category name was changed

End

Public Sub tv1_Save(Row As Integer, Column As Integer, Value As String)

  Dim RecID As Integer
  Dim FieldName As String = Choose(Column, "TransDate", "Amount", "Category", "Comment")

  If IsNull(tv1[Row, 0].Text) Then 'There's no Record ID, so insert a new record
    Dim Res As Result
    SQL = "SELECT MAX(SpendingID) as 'TheMax' FROM Spending"
    Try Res = fdb.Exec(SQL)
    If IsNull(Res!TheMax) Then RecID = 1 Else RecID = Res!TheMax + 1
    tv1[Row, 0].Text = RecID
    SQL = "INSERT INTO Spending(SpendingID,TransDate,Amount,Category,Comment) VALUES('" & RecID & "',' ',' ',' ',' ')"
    Try fdb.Exec(SQL)
    If Error Then
      Message("Couldn't save: " & Error.Text)
      Return
    Endif
  Endif
  'update record
  RecID = tv1[Row, 0].Text
  SQL = "UPDATE Spending SET " & FieldName & " = '" & Value & "' WHERE SpendingID='" & RecID & "'"
  Try fdb.Exec(SQL)
  If Error Then
    Message("Couldn't save:" & SQL & "<br><br>" & Error.Text)
    Return
  Endif
  If Column = 2 Then
    tv1[Row, Column].Text = Format(Val(Value), "###0.00")
    Calculate 'amount has changed
  Else
    tv1[Row, Column].Text = Value
  Endif

Catch
  Message("Couldn't save ... have you created and opened a database yet?")
  Stop Event 'Don't go automatically to the next cell. If you do, you'll get this message twice.

End

Public Sub tv1_KeyPress()

  Select Case Key.Code
    Case Key.BackSpace, Key.Del 'remove record
      Dim RecID As Integer = tv1[tv1.Row, 0].Text
      SQL = "DELETE FROM Spending WHERE SpendingID='" & RecID & "'"
      Try fdb.Exec(SQL)
      If Error Then
        Message("Couldn't delete<br><br>" & Error.Text)
      Else
        tv1.Rows.Remove(tv1.Row)
        If tv1.Rows.Count = 0 Then tv1.Rows.Count = 1
      Endif
    Case Key.Enter, Key.Return
      If tvCategories.Rows.Count > 0 Then
        tvCategories.SetFocus
        tvCategories.Rows[0].Selected = True
      Endif
  End Select

End

Public Sub tvCategories_KeyPress()

  Select Case Key.Code
    Case Key.BackSpace, Key.Del 'remove record
      Dim RecID As Integer = tvCategories[tvCategories.Row, 0].Text
      SQL = "DELETE FROM Categories WHERE CatID='" & RecID & "'"
      Try fdb.Exec(SQL)
      If Error Then
        Message("Couldn't delete<br><br>" & Error.Text)
      Else
        tvCategories.Rows.Remove(tvCategories.Row)
      Endif
    Case Key.Enter, Key.Return
      EnterOnCategoryLine 'action on pressing Enter
      tvCategories.UnSelectAll
  End Select

End

Public Sub MenuClearSpending_Click()
  fdb.Exec("DELETE FROM Spending")
  tv1.Rows.count = 1
  tv1.Clear
End

Public Sub MenuClearCategories_Click()
  fdb.Exec("DELETE FROM Categories")
  tvCategories.Rows.count = 1
  tvCategories.Clear
End

Public Sub CategoryNameFromID(ID As Integer) As String

  Dim res As Result = fdb.Exec("SELECT Category FROM Categories WHERE CatID=" & ID)

  If Not res.Available Then Return "?"
  If IsNull(res!Category) Then Return "-"
  Return res!Category

End

Public Sub EnterOnCategoryLine()  'apply this category to the selected Spending line

  If tv1.row < 0 Then Return
  If IsNull(tv1[tv1.row, 0].text) Then
    Message("Please save this spending record first by entering some other item of data; there's no record ID yet.")
    Return
  Endif
  tv1[tv1.row, 3].text = tvCategories[tvCategories.row, 3].Text
  Dim CategoryID As String = tvCategories[tvCategories.row, 0].Text
  Dim SpendingID As String = tv1[tv1.row, 0].text
  tv1[tv1.row, 5].text = CategoryID
  SQL = "UPDATE Spending SET Category='" & CategoryID & "' WHERE SpendingID='" & SpendingID & "'"
  Try fdb.Exec(SQL)
  If Error Then
    Message("Couldn't save the category<br><br>" & SQL & "<br><br>" & Error.text)
    Return
  Endif
  Calculate
  For i As Integer = tv1.row To tv1.Rows.Max
    If IsNull(tv1[i, 3].text) Then
      tv1.Rows[i].Selected = True 'select the next Spending row that needs a category
      tvCategories.SetFocus
      Return
    Endif
  Next
  tv1.SetFocus

End

Public Sub Calculate()

  Dim i, j, CategoryID As Integer
  Dim t, GrandTotal As Float
  Dim res As Result
  Dim s As String

  For i = 0 To tvCategories.Rows.Max 'every category
    If IsNull(tvCategories[i, 0].Text) Then Continue
    CategoryID = tvCategories[i, 0].Text
    Try Res = fdb.Exec("SELECT Total(Amount) AS TotalAmount FROM Spending WHERE Category=" & CategoryID)
    If Error Then
      Message("Couldn't total<br><br>" & Error.Text)
      Continue
    Endif
    While res.Available
      t = res!TotalAmount
      GrandTotal += t
      If t = 0 Then tvCategories[i, 1].Text = "" Else tvCategories[i, 1].Text = Format(t, "##0.00")
      res.MoveNext
    Wend
  Next
  If GrandTotal = 0 Then Return
  For i = 0 To tvCategories.Rows.Max
    s = tvCategories[i, 1].Text
    If Not IsNull(s) And If Val(s) > 0 Then tvCategories[i, 2].Text = Format(100 * Val(s) / GrandTotal, "##0.##") Else tvCategories[i, 2].Text = ""
  Next
  tbDone.Text = Format(GrandTotal, "##0.00")
  labSpendingTotal.Text = tbDone.Text
  labCategoriesTotal.Text = SumTheCategories()
  If Not IsNull(tbTarget.text) Then
    tbToDo.Text = Format(Val(tbTarget.Text) - GrandTotal, "##0.00")
    tbDistribute.Text = tbToDo.Text
  Endif

End

Public Sub SaveCategoriesTable()
  For i As Integer = 0 To tvCategories.Rows.Max
    SaveCategoryLine(i)
  Next
End

Public Sub SaveCategoryLine(i As Integer) 'i is the line number

  Dim RecID As Integer
  Dim t, pct As Float
  Dim s, CategoryName As String
  Dim res As Result

  RecID = Val(tvCategories[i, 0].Text)
  CategoryName = tvCategories[i, 3].Text
  t = If(IsNull(tvCategories[i, 1].Text), 0, Val(tvCategories[i, 1].Text))
  s = tvCategories[i, 2].Text
  pct = If(IsNull(s), 0, Val(s))
  If IsNull(RecID) Then 'new record needed
    res = fdb.Exec("SELECT Max(CatID) AS MaxCatID FROM Categories")
    RecID = res!MaxCatID + 1
    SQL = "INSERT INTO Categories(CatID,Category) VALUES(" & RecID & "," & CategoryName & ")"
    fdb.Exec(SQL)
    If Error Then
      Message("Couldn't insert a new record<br><br>" & SQL & "<br><br>" & Error.text)
      Return
    Endif
  Else
    SQL = "UPDATE Categories SET Category='" & CategoryName & "' WHERE CatID=" & RecID
    Try fdb.Exec(SQL)
    'before checking Error, don't forget to use TRY. Otherwise Error will be set and you'll seem to have an error when you don't
    If Error Then
      Message("Couldn't update a record<br><br>" & SQL & "<br><br>" & Error.text)
      Return
    Endif
  Endif

End

Public Sub SumTheCategories() As String

  Dim t As Float
  Dim s As String

  For i As Integer = 0 To tvCategories.Rows.Max
    s = tvCategories[i, 1].Text
    If Not IsNull(s) Then t += Val(s)
  Next
  Return Format(t, "##0.00")

End

Public Sub SumTheSpending() As String

  Dim t As Float
  Dim s As String
  For i As Integer = 0 To tv1.Rows.Max
    s = tv1[i, 2].Text
    If Not IsNull(s) Then t += Val(s)
  Next
  Return Format(t, "##0.00")

End

Public Sub MenuCalculate_Click()
  Calculate
End

Public Sub tbTarget_LostFocus()

  If Not IsNull(tbTarget.text) Then tbTarget.Text = Format(Val(tbTarget.Text), "##0.00") Else tbTarget.Text = ""
  Calculate

End

Public Sub tbTarget_KeyPress()
  If Key.Code = Key.Enter Or Key.Code = Key.Return Then FMain.SetFocus
End

Public Sub bDistribute_Click()

  Dim t, pct, y, z As Float

  If IsNull(tbDistribute.Text) Then Return
  Dim x As Float = Val(tbDistribute.Text)
  For i As Integer = 0 To tvCategories.Rows.Max
    If IsNull(tvCategories[i, 1].Text) Then Continue
    If IsNull(tvCategories[i, 2].Text) Then Continue
    t = Val(tvCategories[i, 1].Text)
    pct = Val(tvCategories[i, 2].Text)
    y = t + pct / 100 * x
    z += y 'running total
    If y = 0 Then tvCategories[i, 1].Text = "" Else tvCategories[i, 1].Text = Format(y, "##0.00")
    SaveCategoryLine(i)
  Next
  labCategoriesTotal.text = Format(z, "##0.00")
  FMain.SetFocus

End

Public Sub tbDistribute_LostFocus() 'when leaving, fix the appearance
  If Not IsNull(tbDistribute.text) Then tbDistribute.Text = Format(Val(tbDistribute.Text), "##0.00") Else tbDistribute.Text = ""
End

Public Sub tbDistribute_KeyPress() 'enter leaves the textbox
  If Key.Code = Key.Enter Or Key.Code = Key.Return Then FMain.SetFocus
End

Public Sub MenuDefaultCategories_Click()

  Try fdb.Exec("DELETE FROM Categories") 'it might be already cleared
  tvCategories.Rows.Count = 9
  tvCategories.Clear
  Dim s As String
  For i As Integer = 0 To 8
    s = Choose(i + 1, "Provisions", "Travel", "Medical", "Donations", "Papers etc", "Clothes", "Personal", "Phone", "Repairs")
    tvCategories[i, 3].text = s
    tvCategories[i, 0].text = i + 1
    SQL = "INSERT INTO Categories(CatID,Category) VALUES(" & Str(i + 1) & ",'" & s & "')"
    Try fdb.Exec(SQL)
    If Error Then Message("Couldn't insert a new record in the categories table.<br><br>" & SQL & "<br><br>" & Error.Text)
  Next
  labCategoriesTotal.text = ""

End

Public Sub MenuRound_Click()

  Dim s As String
  Dim x, t As Float

  For i As Integer = 0 To tvCategories.Rows.Max
    s = tvCategories[i, 1].Text
    If IsNull(s) Then
      tvCategories[i, 1].Text = ""
    Else
      x = Round(Val(s))
      t = t + x
      tvCategories[i, 1].Text = x
    Endif
  Next
  labCategoriesTotal.Text = Format(t, "##0.00")
  For i As Integer = 0 To tvCategories.Rows.Max
    s = tvCategories[i, 2].Text
    If Not IsNull(s) Then tvCategories[i, 2].Text = Round(Val(s))
  Next

End

Public Sub MenuOpen_Click()
  OpenDatabase(Null, Null)
End

Public Sub MenuNewDatabase_Click()
  NewDatabase
End

Public Sub MenuNewSpending_Click()
  NewSpending
End

Public Sub MenuNewCategory_Click()
  NewCategory
End

Public Sub MenuQuit_Click()
  Quit
End

Public Sub MenuCopy_Click()
  Dim s, z As String
  Dim i, j As Integer

  For i = 0 To tv1.Rows.Max
    s = tv1[i, 1].Text
    For j = 2 To 4
      s &= gb.Tab & tv1[i, j].Text
    Next
    z &= If(IsNull(z), "", gb.NewLine) & s
  Next
  z &= gb.NewLine
  For i = 0 To tvCategories.Rows.Max
    s = tvCategories[i, 1].Text
    For j = 2 To 3
      s &= gb.Tab & tvCategories[i, j].Text
    Next
    z &= If(IsNull(z), "", gb.NewLine) & s
  Next
  z &= gb.NewLine & gb.NewLine & "Total Withdrawn: " & tbTarget.Text & gb.tab & " =  Total Accounted For: " & tbDone.Text & gb.tab & " +  Cash on hand: " & tbToDo.Text
  Clipboard.Copy(z)

End

Public Sub MenuShowHelp_Click()
  Help.ShowModal
End

Public Sub MenuClearCategory_Click()
  Dim RecID As Integer = tv1[tv1.row, 0].Text
  fdb.Exec("UPDATE Spending Set Category=' ' WHERE SpendingID=" & RecID)
  tv1[tv1.row, 3].Text = "" 'Cat text
  tv1[tv1.row, 5].Text = "" 'Cat ID
  Calculate
End

Public Sub MenuUnselectAll_Click()
  tv1.Rows.UnSelectAll
  tvCategories.Rows.UnSelectAll
End

SQL 语句

[edit | edit source]

其中一些语句按原样使用。另一些语句是由多个部分组成的字符串。您可能会看到SQL = …。语句的一部分是 SQL,字段名称可能会在正确的位置添加,并存储在变量中,例如。或者记录 ID 可能存储在名为 RecID 的变量中。在发送到 SQLite 的字符串中使用单引号。在 Gambas 中组装语句时使用双引号。

SELECT * FROM Spending 从支出表中选择所有内容。
SELECT * FROM Categories 从类别表中选择所有内容。
SELECT MAX(CatID) as 'TheMax' FROM Categories 从类别表中获取最大的 CatID,并将其命名为“TheMax”。
INSERT INTO Categories(CatID,Category) VALUES(123,'Entertainment') 在类别表中创建一个新记录。

123放入CatID字段中,将Entertainment放入Category字段中。

UPDATE Categories SET Category = 'Entertainment' WHERE CatID='123' 必须更新类别表。

CatID等于123的记录中,将Entertainment放入Category字段中。

SELECT MAX(SpendingID) as 'TheMax' FROM Spending 支出表中找到最大的SpendingID,并将其命名为“TheMax”。
INSERT INTO Spending(SpendingID,TransDate,Amount,Category,Comment) VALUES('123',' ',' ',' ',' ') 在支出表中创建一个新记录。

支出ID = 123

TransDate = 空白

金额 = 空白

类别 = 空白

注释 = 空白

UPDATE Spending SET TransDate = '4-11-2019' WHERE SpendingID='123' 将“4-11-2019”放入支出表中SpendingID为 123 的记录的TransDate字段中。
DELETE FROM Spending WHERE SpendingID='123' 删除支出表中记录 ID 为 123 的记录。
DELETE FROM Categories WHERE CatID='123' 删除类别表中记录 ID 为 123 的记录。
DELETE FROM Spending 删除支出表中的所有记录。所有数据都会消失,永远不会再出现。
DELETE FROM Categories 删除类别表中的所有记录。所有类别记录都会消失,永远不会再出现。
SELECT Category FROM Categories WHERE CatID=123 给我CatID记录号为 123 的类别的名称。
UPDATE Spending SET Category='4' WHERE SpendingID='123' 支出表中记录 123 的Category字段设置为 4。此支出项目归入第四个类别,无论它是什么。要查找第四个类别是什么,请查找类别表,并找到CatID=4 的记录。
SELECT Total(Amount) AS TotalAmount FROM Spending WHERE Category='4' 获取支出表中Category字段为 4 的所有记录的Amount字段中所有数字的总和。简单地说,就是将类别 4 中的所有支出金额加起来。将结果命名为“TotalAmount”。
SELECT Max(CatID) AS MaxCatID FROM Categories 从类别表中获取最大的 CatID。将其命名为MaxCatID
SQL = "INSERT INTO Categories(CatID,Category) VALUES(4,Travel)" 创建一个新的类别记录。将CatID字段设置为 4,将Category字段设置为“Travel”。
UPDATE Categories SET Category='Travel' WHERE CatID=4 更新Categories表中记录 ID 为 4 的记录。将“Travel”放入Category字段中。
UPDATE Spending Set Category=' ' WHERE SpendingID=123 将空白放入Spending表中记录 123 的Category字段中。

这些语句可以是SELECTINSERTDELETEUPDATE

这些模式是

SELECT fields FROM table

SELECT fields FROM table WHERE field = something

SELECT * FROM table

SELECT Total(field) AS nameForIt FROM table

SELECT Max(field) AS nameForIt FROM table

INSERT INTO table(fields) VALUES(values)

DELETE FROM table

DELETE FROM table WHERE field = something

UPDATE table SET field = something WHERE keyfield = something

这些不是唯一的 SQL 语句:还有很多。它们足以让你对 SQL 有一个基本的了解。SQLite 的在线帮助可以在这里找到

http://www.sqlitetutorial.net/

http://sqlitetutorials.com/

https://w3schools.org.cn/sql/

关于使用 UPDATE 语句的最重要的一点

更新记录时要小心。

如果你省略 WHERE 子句,所有记录都将被更新!

例如,不要写这个:UPDATE Spending SET Amount=12.50。这会将 12.50 放入所有记录的Amount字段中。所有金额都将变成 12.50。你应该说UPDATE Spending SET Amount=12.50 WHERE SpendingID=42

从 Zip 中编程 Gambas
 ← 模块 SQLite 打印 → 
华夏公益教科书