这是您的代码的新版本。这样做有点混乱,但我认为如果我在评论中出现问题的地方添加了评论和解释,这将是最容易理解的。
首先要说的是,尽管检查给定行的数据集字段为Null并不是坏事,但如果数据库不允许将Null存储在可能被查询的列中,通常会更好,通过应用程序或原始Sql查询。整本书的章节都是从理论和实践的角度写出了空值的意义或不意味着什么。在实践中,他们通常被认为仅仅意味着“缺少信息”。所以我们不能回答这个问题:“这个用户想要一只狗吗?”存在一个空值。因此,关于Null的处理策略和设计选择是一个问题,但是通过对列实施NOT NULL约束,在实现数据库的过程中决定事情通常要好得多。
当我在评论中说德尔福在尊重空白和空白字段之间的差异方面不是特别好时,我的意思是这样的:在字符串字段的情况下,对于字段为空的行,当你调用Field.AsString时,Delphi返回一个空字符串''。有些“纯粹主义者”会说,如果TField在包含Null时被要求提供AsXXX属性,它应该会生成一个异常,因为它不应该试图“伪造”一个实际上没有值的值。它的作用相反,即返回一个空字符串,0代表数字字段等等,这是一种务实的妥协方式:它避免了初学者因存在空值而被绊倒,但是如果你想让你的代码处理空值,你可以使用TField.IsNull。
如果你坚持使用包含空值的数据库 - 并且经常令人沮丧的是(除非你在数据库设计中有过手) - 考虑在SQL中最好处理它们的可能性,数据在你的Delphi代码看到它之前。
第二件事是,如果没有明确的设计说明,我们并不真正知道“想要一只狗”究竟意味着什么。例如,是否意味着用户想要拥有单数,复数或是/否的狗? “是/否”最简单,但并非所有数据库都支持显式布尔列类型,所以您经常会看到(希望为单字符)CHAR,VARCHAR(或其等同Unicode)列。或整数列,如果挑剔的用户的需求。
第三件事是一个字段的Delphi数据类型不一定与数据库的列类型完全相同,尽管它们之间有标准映射。无论如何,通常最好在你的Delphi代码中使用值表示(例如.AsString,.AsInteger,.AsFloat,仅举几例),它们与db列类型最匹配。
不可否认,并非所有的数据库实现都很挑剔,对不起,我的意思是小心,因为德尔福正在处理列的数据类型,但大多数都是。一个值得注意的例外是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;
['IsNull'](http://docwiki.embarcadero.com/Libraries/XE6/en /Data.DB.TField.IsNull) – TLama 2014-08-30 18:04:42
你似乎错过了第三个'狗'后的空间。错字?无论如何,通过使用结构“tblusers ['Dogs wanted']”,您可以访问该领域的内容(如果有的话)作为变体(请参阅OLH),并且我怀疑您目前有点太深入了解了解什么麻烦,可以导致你与你目前的任务。使用tblusers.FieldByName('想要的狗')来到现场。它的DataType属性会告诉你它是什么类型。顺便说一句,从来没有你的“For”循环迭代数据集。使用“虽然不tblusers.Eof做......”之一。而且,你使用的数据库类型是什么? – MartynA 2014-08-30 19:24:39
此外,Delphi将数据集中任何给定列的数据类型视为表中的每一行。 Null是* not *值,它是* state *,意味着该列(字段)对于所讨论的行没有值。设置你的项目的人应该已经解释了这一点,并且空的<> NULL的事实。这是一个重要的区别。如果有一个中间名称的列,而您没有,那么该字段应该是空白的('')还是NULL?德尔福其实并不擅长尊重这种差异,但你需要清楚这一点。 – MartynA 2014-08-30 19:33:46