【问题标题】:what data type is a empty DB field?什么数据类型是空数据库字段?
【发布时间】:2014-10-24 11:18:09
【问题描述】:

我正在尝试为学校项目计算数据库中的所有非空字段。请注意,我不允许使用 SQL。

这里是代码

var i,k : Integer ;
begin 
  i := 0 ;
  with dmregisteredusers do
  begin
    tblusers.Sort := 'Nommer ASC';
    tblusers.Edit;
    tblusers.First;
    For k:= 1 to tblusers.RecordCount do
    begin
      If (tblusers['Dogs wanted'] = '') OR (tblusers['Dogs wanted'] = ' ') 
        OR (tblusers['Dogswanted'] = 0) 
      then 
        tblusers.Next
      else 
      begin
        inc(i);
        tblusers.Next;
      end;//else
    end;//with
  end;//for
  ShowMessage('There are ' + IntToStr(i) + ' new dogs added to wishlist ,please contact the users regarding this matter and them remove the dogs from their wishlist !');

每次我运行代码时都会显示为空的 Db 字段的数据类型为 null 的错误。

如何验证数据库字段何时为空?

【问题讨论】:

  • 您似乎缺少第三个“狗”之后的空格。错字?无论如何,通过使用构造“tblusers ['Dogs Want']”,您正在访问该字段的内容(如果有)作为变体(请参阅 OLH),我怀疑您目前理解的深度有点太远了有什么麻烦会给你带来像你现在这样的任务。使用 tblusers.FieldByName('Dogs Want') 到达现场。它的 DataType 属性会告诉你它是什么类型。顺便说一句,永远不要使用“For”循环来迭代数据集。使用“While not tblusers.Eof do ...”之一。而且,您使用的是什么数据库类型?
  • 此外,Delphi 将数据集的任何给定列的数据类型视为与表中的每一行相同。 Null 不是一个值,它是一个状态,这意味着列(字段)没有相关行的值。谁设置你的项目应该已经解释了这一点,以及空 NULL 的事实。这是一个重要的区别。如果中间名有一列而您没有,该字段应该由空白('')还是NULL? Delphi其实并不特别擅长尊重这种差异,但你需要清楚这一点。
  • 有趣的写作方式:“愿望清单,请”而不是“愿望清单,请”。 AFAIK单词和标点符号之间没有空格,但标点符号和下一个单词之间有一个空格。想想自动换行发生了什么
  • 在第二个/第三个想法上,关于您的代码要在 cmets 中处理的事情太多了。我会在几分钟后发布一个更正的版本作为答案。

标签: string delphi variables types null


【解决方案1】:

这是您的代码的新版本。我这样做的方式有点混乱,但我认为如果我在其中包含 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;

【讨论】:

  • 虽然使用常量比重复自己要好,但是一个持有对该字段的引用的变量会更好。
  • @TLama:我完全同意,但只是想知道这样做是否会掩盖这一点,即一致地引用字段名称是一个很好且相当必要的第一步,尤其是对于嵌入空格的字段名(我个人讨厌)。我会在下一次通过时这样做。
【解决方案2】:

伙计们,尽管我很感谢您为答案付出的努力,但我无法使用它们,因为我的老师会知道这不是我自己的工作... @MartynA 你的编码回答了我的问题 这是新的编码 我知道它很草率,还有很多其他方法可以工作,但我真的很赶时间,所以不能花时间学习代码……这就是我将使用的方法

For k:= 1 to tblusers.RecordCount do
begin
If dmregisteredusers.tblusers.FieldByName('Dogs wanted').IsNull then tblusers.Next
else
    begin
   inc(i) ;
   tblusers.Next;
   end//then begin

我使用了 db form.onshow 的排序,所以当 DBGrid 显示时,它会以正确的顺序排列...

【讨论】:

  • 当然,我们都知道在时间压力下工作是什么,没有问题,无论如何,对于未来的读者和提问者的直接需求一样多。但如果我是你,我会在导师的额头上贴一张纸条,解释 Null 和它们引起的问题,并在你的代码中说,你实际上假设 Null 的意思是“没有狗,谢谢”,这可能不反映数据的实际输入方式。
  • 是的,完全正确..用户要狗,必须通知管理员有多少人想要一只,如果不为空,则表示他们想要一只狗...听起来很简单,直到您必须转换将其转换为代码...我也使用代码将数据输入数据库,因此字段中只能存在某些值...
猜你喜欢
  • 2011-01-11
  • 1970-01-01
  • 2018-02-16
  • 1970-01-01
  • 2019-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多