Query Builder
TsgcHTMLComponent_QueryBuilder:可视化的查询和筛选构建器。用户把条件(字段、运算符、值)组合成 AND 和 OR 分组,可以手动操作,也可以用自然语言提问并由 AI 助手回答,服务器再把结果转换成参数化的 SQL 条件、行匹配或 Grid 上的筛选。适用于 Delphi、C++ Builder 和 .NET。
TsgcHTMLComponent_QueryBuilder:可视化的查询和筛选构建器。用户把条件(字段、运算符、值)组合成 AND 和 OR 分组,可以手动操作,也可以用自然语言提问并由 AI 助手回答,服务器再把结果转换成参数化的 SQL 条件、行匹配或 Grid 上的筛选。适用于 Delphi、C++ Builder 和 .NET。
添加规则可以引用的字段,或链接一个 TsgcHTMLComponent_Grid 以使用它的列,然后渲染。每个按钮都会提交一个服务器事件,组件验证该请求的树,OnApply 把树交给您,用来查询您的数据。
TsgcHTMLComponent_QueryBuilder,条件树使用 TsgcHTMLQueryNode
以 Bootstrap 5 标记渲染的条件和分组树,由服务器渲染,无需脚本
Delphi, C++ Builder, .NET
Fields 列出规则可以引用的内容。OnApply 接收请求中已验证的树,GetSQLWhere 把它写成带命名参数的 SQL 条件,ToJSON 把它保存在用户的会话中。
uses
sgcHTML_AI, sgcHTML_Component_QueryBuilder;
// 只创建一次:它自行响应服务器事件
oQB := TsgcHTMLComponent_QueryBuilder.Create(Self);
oQB.ElementName := 'orders';
oField := oQB.Fields.Add;
oField.Name := 'customer';
oField.Caption := 'Customer';
oField := oQB.Fields.Add;
oField.Name := 'amount';
oField.Caption := 'Amount';
oField.DataType := aidtInteger;
oField.SQLName := 'o.amount';
oField := oQB.Fields.Add;
oField.Name := 'status';
oField.Caption := 'Status';
oField.Options.Add('open=Open');
oField.Options.Add('paid=Paid');
oQB.HTMXEngine := HTMXEngine1;
oQB.AIAssistant := sgcHTMLAIAssistant1;
oQB.OnApply := QueryBuilderApply;
Result := oQB.HTML;
// aRules 是本次请求的树,不会改动 Rules
procedure TForm1.QueryBuilderApply(Sender: TObject;
const aRules: TsgcHTMLQueryNode; var aResponse: string);
var
oQB: TsgcHTMLComponent_QueryBuilder;
oParams: TStringList;
vWhere: string;
i: Integer;
begin
oQB := TsgcHTMLComponent_QueryBuilder(Sender);
oParams := TStringList.Create;
try
vWhere := oQB.GetSQLWhere(oParams, aRules);
FDQuery1.Close;
FDQuery1.SQL.Text := 'select * from orders';
if vWhere <> '' then
FDQuery1.SQL.Add('where ' + vWhere);
for i := 0 to oParams.Count - 1 do
FDQuery1.Params.ParamByName(oParams.Names[i]).AsString :=
oParams.ValueFromIndex[i];
FDQuery1.Open;
DoSaveFilter(oQB.ToJSON(aRules)); // 您自己的会话存储
aResponse := DoOrdersHTML(FDQuery1); // 标有 hx-swap-oob 的行
finally
oParams.Free;
end;
end;
// 包含:sgcHTML_AI.hpp, sgcHTML_Component_QueryBuilder.hpp
oQB = new TsgcHTMLComponent_QueryBuilder(this);
oQB->ElementName = "orders";
TsgcHTMLQueryField *oField = oQB->Fields->Add();
oField->Name = "customer";
oField->Caption = "Customer";
oField = oQB->Fields->Add();
oField->Name = "amount";
oField->Caption = "Amount";
oField->DataType = aidtInteger;
oField->SQLName = "o.amount";
oQB->HTMXEngine = HTMXEngine1;
oQB->OnApply = QueryBuilderApply;
html = oQB->HTML;
void __fastcall TForm1::QueryBuilderApply(TObject *Sender,
TsgcHTMLQueryNode* const aRules, UnicodeString &aResponse)
{
TsgcHTMLComponent_QueryBuilder *oQB = (TsgcHTMLComponent_QueryBuilder *)Sender;
TStringList *oParams = new TStringList();
try
{
UnicodeString vWhere = oQB->GetSQLWhere(oParams, aRules);
FDQuery1->Close();
FDQuery1->SQL->Text = "select * from orders";
if (vWhere != "")
FDQuery1->SQL->Add("where " + vWhere);
for (int i = 0; i < oParams->Count; i++)
FDQuery1->Params->ParamByName(oParams->Names[i])->AsString =
oParams->ValueFromIndex[i];
FDQuery1->Open();
DoSaveFilter(oQB->ToJSON(aRules));
aResponse = DoOrdersHTML(FDQuery1);
}
__finally
{
delete oParams;
}
}
using esegece.sgcWebSockets;
var qb = new TsgcHTMLComponent_QueryBuilder();
qb.ElementName = "orders";
var field = qb.Fields.Add();
field.Name = "customer";
field.Caption = "Customer";
field = qb.Fields.Add();
field.Name = "amount";
field.Caption = "Amount";
field.DataType = TsgcHTMLAIDataType.aidtInteger;
field.SQLName = "o.amount";
qb.HTMXEngine = htmxEngine;
qb.AIAssistant = aiAssistant;
// aRules 是本次请求的树
qb.OnApply += (object sender, TsgcHTMLQueryNode aRules, ref string aResponse) =>
{
var parameters = new List<string>();
string where = qb.GetSQLWhere(parameters, aRules);
using var cmd = connection.CreateCommand();
cmd.CommandText = "select * from orders" + (where != "" ? " where " + where : "");
foreach (var p in parameters)
{
var kv = p.Split(new[] { '=' }, 2);
cmd.Parameters.AddWithValue(":" + kv[0], kv[1]);
}
DoSaveFilter(qb.ToJSON(aRules));
aResponse = DoOrdersHTML(cmd.ExecuteReader());
};
string html = qb.HTML;
为不会写 SQL 的人准备的筛选界面,以及您的代码可以在任何数据库上安全运行的条件。
每个条件都指定一个字段、一个运算符以及一个或两个值,每个分组用 AND 或 OR 连接其子项。分组最多嵌套 MaxDepth 层,一棵树最多包含 MaxRules 条规则。“添加条件”和“添加分组”以当前生效的第一个字段开始。
字段的 DataType 决定用户可以选择哪些运算符:等于、不等于、包含、开头是、结尾是、大于、小于、介于、是以下任一项、为空和不为空。带有 Options(封闭的值列表)的字段只提供较短的列表。
设置 AIAssistant 并把 AIPrompt 设为 True 后,提问框会把问题转换成针对当前生效字段的 AND 规则。规则会进入构建器,用户在应用之前仍可修改,回答的说明显示在提问框下方。
未配置提供程序的助手会用基于规则的解析器回答,因此提问框可以离线使用。当提供程序调用失败时,UseRuleBasedFallback(默认为 True)会让同一个解析器来回答。
链接一个 TsgcHTMLComponent_Grid 后,当 Fields 为空时,构建器会从网格的列中获取字段。ApplyToGrid 通过网格的 RowFilter 筛选行,LoadFromGridOps 则为网格操作集的每个筛选器在树中填入一条规则。
每个控件都命名为 <ElementName>.<path>.<key>,其中 path 是节点的索引路径,key 表示组合符、字段、运算符或值。LoadFromRequest 会读回这些名称,因此构建器也可以在不使用 htmx 提交的普通表单中工作。
您最常用到的成员。
Fields 保存规则可以引用的字段,每个字段都有 Caption、DataType、SQLName 和可选的 Options(每行一个值,或 value=caption)。没有项目时,Grid 的列就是生效的字段。FindField、GetFieldCount 和 IsOperatorAllowed 可用来查询这些字段。
OnApply(Sender, aRules, aResponse) 在“应用”“清除”和已回答的提问之后触发,带有请求中已验证的树。您在 aResponse 中返回的内容会追加到响应中,例如标有 hx-swap-oob 的行。没有 OnApply 时,ApplyToGrid 会把树复制到 Rules 并筛选所链接的 Grid。
返回不含 WHERE 关键字的条件,树为空时返回空字符串。值只会写成命名参数 :qb1、:qb2 等,并以 qb1=<value> 的形式添加到 aParams,因此用户文本无法改变语句。列是字段的 SQLName,或其 Name。
所有从外部填充树的内容,无论是 JSON、AI 的回答还是提交的表单,都要经过同样的验证。针对未生效字段的规则、该类型不允许的运算符、超过 MaxRules 的规则以及比 MaxDepth 更深的分组都会被丢弃,值会被截断为 200 个字符,空的子分组会被删除。SQLName 会原样进入 SQL,因此请在代码中设置它,绝不要从请求中获取。
ToJSON 把分组写成 {"c":"and","r":[...]},把规则写成 {"f":"field","o":"operator","v":"value"},介于运算符还会多一个 "v2"。ParseJSON 和 LoadFromJSON 会读回并验证它。
Matches 对以 Name=Value 键值对给出的一行数据计算这棵树,适用于内存中的行。每个辅助方法都接受一个可选的树,nil 表示 Rules,因此 OnApply 处理程序可以操作请求中的树,而不触碰共享组件。
一个组件为所有会话服务,因为服务器事件作用于请求中的树。在多个用户共享的页面上,请分配 OnApply,把 ToJSON(aRules) 保存在会话中,并用 ParseJSON 和 GetBuilderHTML 渲染每个用户的构建器。
每个按钮,以及字段和运算符选择框在变化时,都会把服务器事件 qb 提交到 HTMX 引擎的组件事件端点,或提交到 ActionURL。响应是标有 hx-swap-oob="true" 的构建器,htmx 按它的 id 替换:QueryBuilderID,或 qb_ 加上 ElementName。
Demos\60.HTML\01.RunTime\25.QueryBuilder 是端口 8127 上的订单页面,构建器位于网格上方,还有一张 SQL 卡片,显示同样的规则在数据库上会运行的参数化 WHERE 子句。它的 AI 助手没有 API 密钥,因此提问框离线回答,每位访问者在会话中保存自己的规则。设计时、Lazarus 和 .NET 版本与之对应。