这是您的代码的新版本。我这样做的方式有点混乱,但我认为如果我在其中包含 cmets 和解释点出现的地方,它会最容易理解。
首先要说的是,虽然检查给定行的数据集字段是否为 Null 并不是一件坏事,但如果不允许数据库将 Null 存储在可能被查询的列中,通常会更好,通过应用程序或原始 Sql 查询。整本书的章节都以理论和实践的方式写了关于 Null 的作用或不意味着什么的内容。在实践中,通常最好将它们视为“信息缺失”的意思。所以我们不能回答“这个用户想要狗吗?”这个问题。在 Null 存在的情况下。因此,如何处理 Null 是政策和设计选择的问题,但通常最好在数据库的实现中通过对列施加 NOT NULL 约束来决定此事。
当我在评论中说 Delphi 并不特别擅长尊重 Null 和空白字段之间的区别时,我的意思是:对于字符串字段,对于字段为 Null 的行,Delphi当您调用 Field.AsString 时,返回一个空字符串 ''。一些“纯粹主义者”会说,如果在 TField 包含 Null 时询问其 AsXXX 属性,则 TField 应该生成异常,因为它不应该尝试“伪造”实际上没有值的值。相反,它的作用是返回一个空字符串、0 表示数字字段等,这是一种务实的妥协:它避免了初学者被 Null 的存在绊倒,但如果您希望您的代码处理 Null,您可以使用 TField.IsNull。
如果你被一个包含 Null 的数据库困住了——而且你会经常感到沮丧(除非你参与了数据库的设计)——考虑它们可能最好在获取数据的 SQL 中处理的可能性在你的 Delphi 代码看到之前。
第二件事是,在没有明确的设计简介的情况下,我们并不真正知道“想要一只狗”的确切含义。例如,这是否意味着用户想要一只狗单数、复数或是/否? Yes/No 最容易处理,但并非所有数据库都支持显式布尔列类型,因此您经常会看到使用(希望是单字符)CHAR、VARCHAR(或它们的 Unicode 等效项)列来代替。或整数列,如果要满足挑剔的用户要求。
第三件事是字段的 Delphi DataType 不一定与 db 的列类型完全相同,尽管它们之间存在标准映射,无论如何,通常最好在 Delphi 代码中使用值表示(例如 .AsString、.AsInteger、.AsFloat 等等)与 db 列类型最匹配。
诚然,并不是所有的数据库实现都那么挑剔,抱歉,我的意思是小心,因为 Delphi 在处理列的数据类型,但大多数都是。一个值得注意的例外是 Sqlite,虽然您可以在表的 DDL 中定义列类型,但 Sqlite 引擎将这些视为建议而不是规则,您可以在其中存储几乎任何内容。
正如我之前在评论中所说,Null 是一个列/字段状态,而不是一个值。所以问“什么数据类型是空数据库字段?”是各种“分类错误”。列的数据类型是“正如它在锡上所说的那样”,即它被定义为在表的 DDL 中的内容。当然,您真正要问的是“我如何确定该字段是否为空”,这与 Delphi 编码一样是设计选择和数据库实现的问题。
不管怎样,概括性已经够多了……
procedure CountDogsWanted;
var
// i,k : Integer ; <- names like i and k are usually as used for loop variables
DogsWanted : Integer;
Wanted : Boolean;
S : String; // contrast this naming style with what I said about the likes of i, j, k
// I've done this because we might want to do several tests & operations on its value
// and they will be easier to read with a shorter variable name. Not such a good idea
// when there are several such variables.
AField : TField;
const
scDogsWanted = 'Dogs wanted'; // this is to avoid making mistakes with typos
begin
{i := 0 ;}
DogsWanted := 0;
// The point of the following line is to retrieve the field we're working with
// only once, rather than doing a FieldByName (which involves a serial iteration
// through the dataset's Fields collection) for each row in the dataset.
// The AField variable will remain valid for the duration of this procedure
// or until the dataset is closed if its Fields aren't defined as persistent ones.
// Persistent fields remain valid for the lifetime of their owners (usually a
// datamodule or form). OTOH, "dynamic" fields are owned by the dataset and created
// and destroyed when the dataset is opened and closed.
AField := dmregisteredusers.tblusers.FieldByName(scDogsWanted);
{with dmregisteredusers do <- Don't use "with", it only ever causes problems}
{begin}
{tblusers.Sort := 'Nommer ASC'; <- pointless, it makes no difference when you count what order the things are in}
{tblusers.Edit; <- No! This puts the table into Edit state, but there's not point because you're not changing anything, and in any case, it will drop out of Edit state when you do a .Next}
dmregisteredusers.tblusers.First;
while not dmregisteredusers.tblusers.Eof do
{For k:= 1 to tblusers.RecordCount do <- Never, ever, iterate a dataset with a For loop.
The RecordCount is best avoided, too, because not all types of dataset return a meaningful
RecordCount }
begin
// You need to decide what to do about users whose 'Dogs wanted' field is Null
// If is is Null maybe we should ask for the record to be changed to indicate
// explicitly whether a dog is wanted or not
if AField.IsNull then begin
// to be filled in by you
end;
// You haven't told us what DataType the 'Dogs wanted' field is
// There are several possibilities. For simplicity, let's assume that the DataType of the field is ftString
// Let's also make a design decision that *only* if the field only contains a 'Y' in upper or lower
// case, then that means a dog is wanted.
// First copy the field's string value into a local variable so we don't have to keep getting it for the
// following operation;
S := dmregisteredusers.tblUsers.FieldByName(scDogsWanted).AsString;
S := Trim(S); { Trim() is a statndard function which removes leading and trailing whitespace }
Wanted := CompareText(S, 'Y') = 0; { CompareText does a case-insensitive comparison and returns zero if equal}
If
{ (tblusers['Dogs wanted'] = '') OR (tblusers['Dogs wanted'] = ' ')
OR (tblusers['Dogswanted'] = 0)}
Wanted
then
{tblusers.Next <- this is in the wrong place, you want to do a .Next regardless of the outcome of the "if"}
else
begin
inc(DogsWanted);
{tblusers.Next;}
end;//else
dmregisteredusers.tblusers.Next;
{end;//with}
end;//for
ShowMessage('There are ' + IntToStr(DogsWanted) + ' new dogs added to wishlist ,please contact the users regarding this matter and them remove the dogs from their wishlist !')
end;