【问题标题】:Delphi TSQLDataSet no output with SQL inner join queryDelphi TSQLDataSet没有带有SQL内部连接查询的输出
【发布时间】:2013-05-27 05:12:48
【问题描述】:

我正在使用 TSQLConnection 和 TSQLDataSet 从 Delphi 应用程序中查询 SQL Server (2012) 数据库。到目前为止,我的所有查询都运行良好,但是我现在正在尝试使用 INNER JOIN 编写一个 SELECT 查询,并且我无法访问 TSQLDataSet 的任何输出。

代码:

Query_text:='SELECT Table1.Price
            'FROM [Table1]
              'INNER JOIN [Table2]
              'ON Table1.Code_ID = Table2.ID'   
            'WHERE (Table2.Code = '+QuotedStr(Temp_code)+')';

SQL_dataset.CommandType:=ctQuery; 
SQL_dataset.CommandText:=Query_text;
SQL_dataset.Open;  

If SQL_dataset.RecordCount>0 then .... { THIS RETURNS NOTHING }

如果我将此查询输入 SSMS,则会返回正确的信息。在我使用的所有其他 SELECT 查询(没有 INNER JOIN)中,SQL_dataset 按预期返回记录数和字段名。

关于问题是什么以及如何解决问题的任何想法?

更新:

我关于 TSQLDataset.RecordCount 的信息:

http://docwiki.embarcadero.com/Libraries/XE4/en/Data.SqlExpr.TCustomSQLDataSet.RecordCount

从这里我没有得到它不适用于简单查询的印象 - 到目前为止,我已经成功地使用简单的 SELECT 查询作为查询是否返回任何数据的标志...我只是幸运?但是,上面的链接确实指出它不适用于参数化查询和多表连接,所以这似乎解释了我原来的问题!非常感谢您为我指明了正确的方向。

此链接表明,如果 Bof 和 Eof 都为真,则结果集为空:

http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.Eof

If SQL_dataset.Bof=True and SQL_dataset.Eof=True then  
begin 
  Found:=False;

这是更好的选择吗?

更新 2:

感谢您的解释,这对我来说开始有意义了。我已删除对 RecordCount 的所有引用,并按照建议替换为 TSQLDataset.isEmpty(我完全错过了该方法,谢谢)。

我原以为只要您调用 TSQLDataset.Open 就会填充 TSQLDataset.RecordCount,但如果我理解正确,情况并非如此?

有时我会滚动浏览结果如下:

SQL_dataset.CommandType:=ctQuery; 
SQL_dataset.CommandText:=Query_text;
SQL_dataset.Open;

If SQL_dataset.IsEmpty=False then 
begin
  SQL_dataset.First;

  While not SQL_dataset.Eof do  
  begin
    { DO SOMETHING }
    SQL_dataset.Next;
  end;
end;

这显然确实调用了 TSQLDataset.Next,所以我假设这会完成您所说的所有内存缓冲(根据 RecordCount)。这究竟是在什么时候发生的?

【问题讨论】:

  • 1) 使用参数。 bobby-tables.com/delphi.html 2) 最好不要使用 RecordCount - 那是用于 DBF 文件,而不是用于 SQL。使用函数 DataSet.EOF、DataSet.BOF 和 DataSet.Empty
  • “代码”列的类型是什么?

标签: sql-server delphi inner-join delphi-xe4


【解决方案1】:

这是文件的代码,例如 DBF 和 CSV,而不是 SQL 远程数据集。

1) 不保证RecordCount 将包含除本地文件之外的任何有用信息。如果会 - 这意味着所有数据都从 mremote 服务器读取到本地客户端内存。为 SQL 调用 RecordsCount 意味着“我希望我的应用程序冻结一个小时,直到所有数据库内容从服务器拉到客户端,然后因‘内存不足’错误而崩溃”。使用属性.Empty.BOF.EOF

实际上,你从哪里得到 RecordCount ???当您阅读文档时,您确实看到文档明确指出 RecordCount 与数据库中的记录数不对应,因此检查 RecordCount > 0 是否确实没有说明数据库数据。http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.RecordCount

2) 使用参数。

试试这样:

with SQL_dataset do begin
     Close;
     CommandType := ctQuery;
     ParamCheck := true;
     CommandText := 'SELECT Table1.Price FROM "Table1"  ' +
          'INNER JOIN "Table2" ON Table1.Code_ID = Table2.ID  '   +
          'WHERE Table2.Code = :Temp_code ';
     Params[0].AsString := 'abcdefgh';
     Open;

     if not IsEmpty then begin
....
     end;
end;

另外,请编辑问题的标签并指定 Delphi 版本并指定数据库访问驱动程序


更新:

关于

if SQL_dataset.Bof=True and SQL_dataset.Eof=True then  
begin 
  Found:=False;

这可以写得更简单。

Found := not (SQL_dataset.Bof and SQL_dataset.Eof)

或者在现代德尔福中

Found := not SQL_dataset.IsEmpty;

http://docwiki.embarcadero.com/Libraries/XE4/en/Data.DB.TDataSet.IsEmpty


关于没有得到它不适用于简单查询的印象

您无法可靠地区分简单查询和复杂查询。如果 XXX 是一个存储过程或一个 VIEW 或一个表,其中某些列是由子查询计算的虚拟数据,则 SELECT * FROM XXX 可能是一个非常复杂的查询。

还要重新阅读我上面写的内容。获取最终记录数意味着服务器应该将查询执行到最后。并且网络应该将所有数据传输到最后。而TDataSet 应该将所有接收到的数据缓存到内存中,这样你就可以调用.Next

想象一个对一个简单表的简单查询,该表包含 10M 行,每行 1000 字节(如客户的姓名 + 照片) - 总计接近 10 GB。考虑典型的 100 Mb/s(大约 10 MB/s)网络。传输所有数据以了解它们的数量需要多长时间?在大约 2Gb 传输时,您的 32 位应用程序将因内存不足错误而死。 当您真正想知道是否至少有一行时,所有这些都会加载。

更新 2

作为。在做while ... EOF 之前检查.IsEmpty 对我来说似乎有点过度设计。在这些情况下,当数据集实际上为空时,while-loop 将退出而无需进入迭代主体。所以就个人而言,如果您没有针对空数据集的具有特定代码路径的 else-branch,则可以删除此类循环之前的此类检查。

至于缓存...这很难确定。通常有一个链条: database file -> database server + query -> db access library -> TDataSet -> Grid or other consumer

在每个箭头处可能会发生也可能不会发生缓存。

然后是单向和双向 SQL 查询/游标。 对于双向,您无论如何都可以使用.Next.Prior。这对网格很有用。但是对于服务器来说,这意味着它要么缓存所有内部行 ID,直到游标(查询)关闭,要么引擎和索引自然允许在两个方向上进行查询。我敢打赌,数据库服务器的数据结构和算法的自然优化选择会选择前一种方法。至少我不会考虑假设后者是一个可靠的隐含假设。

如果 TDataSet 也是单向的,或者如果 TDataSet 和底层库+服务器都是双向的,那么 TDataSet 会将数据缓存在小块中。我估计它大约有十几行。它将创建额外的冗余网络往返或将每一行作为单独的请求获取。但是,获取数百或数千个数据也会对网络造成不必要的负担(因此会出现延迟)。

TDbGrid 通常不缓存自身,而是将 TDataSet 的 BufferCount(未记录,参见 Joanna Carter 的资料或文章)设置为所需的可见行数以及一些重叠以便于滚动。然而,一些高级组件(如 QuantumGrid)实现了自己的内部缓存。

因此,当您将长查询直接放在用户将滚动到底部的某个 Grid 时,很有可能会导致延迟甚至内存耗尽。 OTOH,如果您的代码类似于那个 while 循环并且您设法将查询设置为单向(具体如何发出信号属于具体库和数据集),这将为所有链提供优化内存消耗和缓存的方法表现。当然,“提供”并不意味着所有的链条都确实实现了这一点。你肯定会失去打电话给.Prior.First.Locate 等的能力。

这些是一般性观察,您必须做出明智的判断,这可能会对您的系统造成多大影响。

当您处理大量(或潜在的大量)数据时,没有灵丹妙药。这就是 SQL 被设计为从服务器传输尽可能少的数据的原因,在远程端进行所有过滤。更何况,WWW 潮强调分片或集群的概念,将逻辑上统一的数据库拆分到多个实际服务器中,以保持合理的内存需求。

【讨论】:

  • 您好,感谢您的帮助,我将花一些时间查看您的建议/意见,稍后再发布更完整的回复。我尝试按照您的建议使用参数化查询并且它有效,非常感谢。除了 SELECT 之外,我一直在对所有查询使用参数化查询,所以我需要重新审视为什么我没有这样做,我显然错过了一步。我正在使用 Delphi XE4 和 MSSQL 驱动程序。 'Code' 是一个字符串,ID 是整数。
  • 好的。请使用标签来指定 Delphi 版本并指定库和驱动程序 - 可能存在特定于 lib 和特定于版本的错误。具体来说:你没有指定 dbExpress 库——你把它留给了回答的人去赌它是什么,类似于 FUD。并且 Delphi 版本是否应该进入标签,便于过滤并将重要信息保存在一个易于预测的标准位置。提前致谢。
  • 除了参数,我强烈建议您停止使用.RecordCount,因为这违反了 SQL 原则和 Delphi 文档。希望你也这样做。如果没有 - 请这样做。
  • @Arioch'The:有时(似乎并非如此,但是)查询不需要带有参数的 WHERE 子句,因为它会在join... 在存储过程中更常见,但也可以使用它(例如,使用数字表)..
  • @FabricioAraujo 我知道。例如,当我想在有序表中获取最上面的行时,这是典型的。但是为什么要提呢?原始查询有 WHERE,我只是复制了它
猜你喜欢
  • 1970-01-01
  • 2018-09-15
  • 2011-12-05
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 2021-07-25
  • 2020-11-29
  • 1970-01-01
相关资源
最近更新 更多