【问题标题】:How to find DB Keys that uniquely identify records in a TFDQuery?如何在 TFDQuery 中找到唯一标识记录的 DB Key?
【发布时间】:2018-02-09 19:07:24
【问题描述】:

简而言之,我如何知道哪些(主键和唯一键)唯一标识 FireDAC 查询记录?

我正在使用 Delphi 10.1 & FireDAC & Firebird。

我正在使用代码生成来生成一个包含我的应用程序的所有查询的文件。它是一个遗留应用程序,因此当前的数据库结构无处不在。设计它的人在设计它时没有知识(仍在学习)。所以我们没有一个连贯的系统来唯一地识别记录。其中一些在 autoinc int 生成器上。有些有另一个字段用于唯一标识。有些有组合。链接到这些字段的外键也不连贯。

所以。由于数据库的整体重构不是一个直接的选择,我们必须处理它。我能想到的最好方法是能够检索给定查询的每个可能的键。这将使我们能够系统地检索我们可以唯一标识查询和表中的记录的所有可能方式。这将为我们提供一个记录标识符(基本上是一组键)的抽象,我们可以在我们的应用程序中传递它,同时我们重构。

第一步是使用这种结构(为便于阅读而简化)生成数据库的虚拟表示(表、字段、字段类型、长度、键、外键):

TTable = class(TCollectionItem)
property Name : string;
property Fields : TFieldCollection;
property Keys : TKeyCollection;
end;

TField = class(TCollectionItem)
property Name : string;
property Table : TTable;
property Type : TFieldType;
property Size : integer;
property Required : Boolean;
end;

TKey = class(TCollectionItem)
property KeyType : TKeyType;
property Name : string;
property Table : TTable;
end;

TKeyType = (ktPrimary, ktUnique, ktForeign);

TKeyCollection = class(TCollection)
end;

TTableCollection = class(TCollection)
end;

TSchema = class(TObject)
property Tables : TTableCollection;
end;

其次,我实现了从这个模式生成代码,为应用程序的每个表生成一个类,以及所有可能的键。看起来像这样:

CUSTOMERS table
---------------
ID int (autoinc, pk PK_CUSTOMERS)
CODE int (uk UK_CODE)
NAME string
GROUP_ID int (fk on GROUPS.ID)


GROUPS table
------------
ID int (autoinc, pk PK_GROUPS)
NAME string

// Generated code (simplified for readability)

TCustomerPk = class(TKey)
property ID : Integer;
end;

TCustomerCode = class(TKey)
property Code : Integer;
end;

TCustomerKeys = class(TKeys)
property CustomerPk : TCustomerPk ...
property CustomerCode : TCustomerCode ...
end;

TCustomers = class(TTableBase)
property ID : TIntegerField;
property Code : TIntegerField;
property Name : TWideStringField;
property Keys : TCustomerKeys; 
end;

TGroupsPK = class(TKey)
property ID : Integer;
end;

TGroupsKeys = class(TKeys)
property GroupsPk : TGroupsPk;
end;

TGroups = class(TTableBase)
property ID : TIntegerField;
property NAME : TStringField;
property Keys : TGroupKeys;
end;

现在表格已经完成,我攻击查询。我的目标是知道查询中存在哪些键。

为了知道这个查询有哪些键:

  1. 获取此查询从中获取数据的表的列表
  2. 遍历这些表的键
  3. 检查键的所有字段是否都存在于查询中

示例(为简单起见,跳过外键的大小写):

for lQueryField in lQuery.Fields do
begin
  if not lTableList.Contains(lQueryField.Table)
    lTableList.Add(lQueryField.Table)
end;

for lQueryTable in lTableList do
begin
  for lKey in lQueryTable.Keys do
    begin
      lFound := true;
      for lKeyField in lKey.Fields do
      begin
        if not lQuery.Fields.Contains(lKeyField) then lFound := false;
      end;

      if lFound then
        // Query has key lKey

    end;
end;

使用这种方法,并给出一个TFDQuery

SELECT CUSTOMERS.ID CUSTOMERID, CUSTOMERS.CODE CUSTOMERCODE, GROUPS.ID GROUPID FROM CUSTOMERS INNER JOIN GROUPS ON GROUPS.ID = CUSTOMERS.GROUP_ID

我们可以很容易地生成这段代码,因为我们知道这个查询有PK_CUSTOMERS 键和PK_GROUPS 键使用我们上面的测试:

TCustomersQuery = class(TQueryBase)
property CUSTOMERID : TIntegerField;
property GROUPID : TIntegerField; 
property CUSTOMERCODE : TIntegerField;
property CustomerPk : TCustomerPk;
property CustomerCodeUk : TCustomerCode;
property GroupPk : TGroupPk;
end;

现在,我面临的挑战如下。我们知道TCustomersQuery 中的每条记录唯一标识CUSTOMERS 表中的一条记录。换句话说,TCustomersQuery 中的每条记录对应于CUSTOMERS 表中的一条不同的、非重复出现的记录。

TCustomersQuery 中的每条记录并不能唯一标识GROUPS 表中的非重复记录。

换句话说,PK_CUSTOMERSUK_CODE 键在此查询的上下文中仍然有效,但 PK_GROUPS 无效。

换句话说,PK_CUSTOMERSUK_CODE 键的唯一性在此查询中被传达,而不是 PK_GROUPS 键的唯一性。

我想找出在任意查询的上下文中哪些键仍然有效。

FireDAC 在 IDE 中首次创建查询时会自动获取查询字段 ProviderFlags。它以某种方式知道哪些字段是主键的一部分。我想扩展它以了解适用于此查询的所有主键和唯一键。 我如何知道所有可能具有ProviderFlags = pfInKey 值的字段?

简而言之,我如何知道哪些(主键和唯一键)唯一标识了 FireDAC 查询记录?

【问题讨论】:

  • 好问,+1。 FireDAC 的作者有时会在这里回答,所以也许这会引起他的注意,否则这里有一个固定的贡献者,@Victoria,他似乎对 FireDAC 非常了解,可能会提供帮助。
  • 这不是 FireDAC 直接支持的(或支持,但仅适用于基表,不适用于连接表)。您的目标是获取 SQL 命令中使用的所有表的主键和唯一约束吗?如果是这样,对您来说是否会出现小幅性能下降(看来您是从命令生成 Delphi 类,所以我猜它不应该)?
  • 嗨@Victoria。谢谢回答。是的,你是对的。我的目标是获取 SQL 查询的主键和唯一键。代码是预先生成的,因此最终用户没有性能问题。这里棘手的部分是知道哪个字段(或字段组合)代表查询中的唯一键/主键,并且可以用作唯一记录标识符。我能够使用元数据查询来检索表的键,但在这个级别的查询似乎不存在。
  • 深入挖掘,您将能够(无需修改源代码)为获取的字段构建表名列表(但您将无法访问例如其列的关联实体( s) 未在命令输出中列出)并从此列表查询表约束。但请注意,我正在阅读比您更旧的 FireDAC,因此这可能已经改变(尽管我对此有点怀疑,因此 FireDAC 和用户都不需要有关其字段未获取到客户端的表的信息)。跨度>
  • @Victoria 我只对获取 SQL 查询中所有字段都存在的主键和唯一键感兴趣。但更重要的是,知道哪些键“唯一性”在此查询的上下文中仍然有效。类似于查找所有可能具有 ProviderFlags pfInKey 值的字段

标签: sql database delphi code-generation firedac


【解决方案1】:

我认为这不是 FireDAC 问题,而是一个更普遍的问题。

要实现您的目标,您首先需要获取属于源表中唯一键的所有列的查询元数据(源表和源列)。 (注意表和列别名)

然后,按每个“外键”的源表和源列分组,您应该检查查询中是否有源表的完整键(考虑多列 PK/AK 索引)。
为此,您应该获取每个源表的元数据并尝试匹配每个键列。

如果未覆盖完整键,则它在查询结果中不能是唯一的(不能保证)。
如果涵盖了完整键,则无论如何您都必须检查查询结果中的唯一性,因为根据您的查询语法,您可能在结果中重复多次相同的键(源表中的唯一键)。

小心!!
具有不同参数的相同查询可能具有唯一性或不具有唯一性的列。

select t1.PKColumn, t2.PKColumn
from Table1 t1
cross join (
    select top @ExternalParameter * 
    from Table2
) t2

对于 ExternalParameter = 1 t1.PKColumn 在上面的查询中也是唯一的,否则它不会

如您所见,所有这些东西都需要大量额外的工作和 sql 查询..

我不是 Firebird 专家,所以我在 SQLServer 中进行了测试,但我认为您可以使用 Firebird 系统表获得相同的架构信息 (Look here for some example)

在 SQLServer 中,您可以使用sys.dm_exec_describe_first_result_set 提取元数据信息:

select column_ordinal, d.name, source_table, source_column, is_part_of_unique_key, is_identity_column, c.object_id, c.column_id
from sys.dm_exec_describe_first_result_set(N'SELECT CUSTOMERS.ID CUSTOMERID, CUSTOMERS.CODE CUSTOMERCODE, GROUPS.ID GROUPID FROM CUSTOMERS INNER JOIN GROUPS ON GROUPS.ID = CUSTOMERS.GROUP_ID',null,1) d
join sys.columns c on c.object_id = OBJECT_ID(d.source_table) and c.name=d.source_column
where (is_hidden=0 and is_part_of_unique_key=1 or is_identity_column=1) 

这样您就可以提取有关要测试的列的所有索引的详细信息:

select *
from sys.indexes i 
join sys.index_columns c on i.object_id = c.object_id and i.index_id = c.index_id
where c.object_id = @TestObjectID 
and exists (
    select 1 
    from sys.index_columns cc 
    where cc.object_id = c.object_id and cc.index_id = c.index_id and cc.column_id = @TestColumnID
)

来自 delphi,类似这样的测试密钥唯一性:

test_uniqe := 'if exists(select ' + SourceColumnList + ', count(*) from ' +  SourceTable + ' group by ' + SourceColumnList + ' having count(*)>1) select 0 uk else select 1 uk

这只是最简单的案例分析!考虑不是 PK/AK 索引但可能是对源表的唯一引用的标识列, 考虑涉及多个键的字段..

这可能会成为一项非常艰巨的任务..

我认为您最好的选择是自动和手动之间的中间方式..

【讨论】:

    猜你喜欢
    • 2022-11-30
    • 1970-01-01
    • 2018-12-07
    • 1970-01-01
    • 2013-05-21
    • 2021-11-19
    • 1970-01-01
    • 2014-05-24
    • 1970-01-01
    相关资源
    最近更新 更多