3

This question led to a new feature in a package:
datatool

\DTLgdeletedb is available in (v2.13) and later.


I am trying to use a \foreach loop to iterate over a few items. For each iteration of the steps of \ExtractData are:

  1. Load the data into mydb.
  2. Extract the appropriate data.
  3. Delete mydb.

This works just fine outside of a \foreach. But if used within a \foreach, it appears that the database is not deleted, and any subsequent attempt to create a new database with the same name fails with:

Package datatool Error: Database `myDB' already exists.

Notes:

  • The MWE example below, shows that that database access is working fine outside of a \foreach, and within the very first invocation within a \foreach.
  • The failure occurs only if an attempt is made to use \ExtractData after a use within the \foreach. So uncommenting any of the commented blocks will show the failure.
  • Before someone asks why I close and re-open the same db, in my actual usage, the data file opened is different for each member of the \foreach. Just the name of the data base is the same. I guess I could create a new name each time, but I'd really like to know what the actual problem is.

Reference:

Code:

\documentclass{article}

\usepackage{filecontents}
\begin{filecontents*}{foo.dat}
    Smith, John, 1977-05-04
    Doe,   Jane, 1980-05-04
\end{filecontents*}

\usepackage{xstring}
\usepackage{pgffor}
\usepackage{datatool}

\newcommand{\ExtractData}[3]{% #1=file name #2=row, #3=col
    \DTLloaddb[noheader,keys={LastName, FirstName, DOB}]{myDB}{#1}%
    \IfEq{\DTLrowcount{myDB}}{0}{%
        \def\Value{NO VALUE}
    }{%
        \DTLgetvalue{\Value}{myDB}{#2}{#3}%
    }%
    \DTLdeletedb{myDB}%
    \Value%
}

\begin{document}
\section{Works}
\ExtractData{foo.dat}{1}{2}
\ExtractData{foo.dat}{1}{1}

\section{Works in foreach (one element)}
\foreach \x in {2}{
    \ExtractData{foo.dat}{1}{\x}
}

\section{Does NOT work subsequent to foreach (one element)}

%\ExtractData{foo.dat}{1}{1}

%\foreach \x in {1}{
%   \ExtractData{foo.dat}{1}{\x}
%}

\section{Does NOT works in foreach (more than one element)}
%\foreach \x in {2,1}{
%   \ExtractData{foo.dat}{1}{\x}\par\noindent
%}
\end{document}
  • 1
    It's the usual problem: \foreach works in a group, so the \DTLdeletedb inside the \foreach deletes the database only locally. – egreg Apr 14 '12 at 9:50
  • @egreg: I did think of that but since \DTLloaddb and \DTLdeletedb were within the same scope I though they would either both load/delete locally or both load/delete globally so did not think that that was the problem. – Peter Grill Apr 14 '12 at 17:28
  • AFAIK, each step of \foreach is run in a distinct group. – egreg Apr 14 '12 at 17:33
4

\foreach works inside a group, so the deletion of the database is only local. Define a "global" deletion command \DTLgdeletedb and use that.

\makeatletter
\newcommand*{\DTLgdeletedb}[1]{%
  \DTLifdbexists{#1}%
  {%
    \dtlforeachkey(\@dtl@key,\@dtl@col,\@dtl@type,\@dtl@head)\in{#1}\do
    {%
      \global\expandafter\let\csname dtl@ci@#1@\@dtl@key\endcsname\undefined
    }%
    \global\expandafter\let\csname dtldb@#1\endcsname\undefined
    \global\expandafter\let\csname dtlkeys@#1\endcsname\undefined
    \global\expandafter\let\csname dtlrows@#1\endcsname\undefined
    \global\expandafter\let\csname dtlcols@#1\endcsname\undefined
  }%
  {%
    \PackageError{Can't delete database `#1':
       database doesn't exist}{}{}%
  }%
}
\makeatother

It's just the same as the \DTLdeletedb command, but with \global prepended. I'm afraid that heavy use of this approach can pollute the save stack; be warned.

  • 1
    I'll add this (and a global version of \DTLcleardb) to the next version of datatool. (You can track the progress of this feature using my feature tracker.) – Nicola Talbot Dec 10 '12 at 8:51
3

I can't answer this question but I want to mention that the loop \@for works well:

\documentclass{article}

\usepackage{filecontents}
\begin{filecontents*}{foo.dat}
    Smith, John, 1977-05-04
    Doe,   Jane, 1980-05-04
\end{filecontents*}

\usepackage{xstring}
\usepackage{pgffor}
\usepackage{datatool}

\newcommand{\ExtractData}[3]{% #1=file name #2=row, #3=col
    \DTLloaddb[noheader,keys={LastName, FirstName, DOB}]{myDB}{#1}%
    \IfEq{\DTLrowcount{myDB}}{0}{%
        \def\Value{NO VALUE}
    }{%
        \DTLgetvalue{\Value}{myDB}{#2}{#3}%
    }%
    \DTLdeletedb{myDB}%
    \Value%
}
\makeatletter
\let\myloop\@for
\makeatother
\begin{document}
\section{Works}
\ExtractData{foo.dat}{1}{2}
\ExtractData{foo.dat}{1}{1}

\section{Works in foreach (one element)}

\myloop\xx:={2}\do{%
  \ExtractData{foo.dat}{1}{\xx}%
  }

\section{Does NOT work subsequent to foreach (one element)}

\ExtractData{foo.dat}{1}{1}

\myloop\xx:={1}\do{%
   \ExtractData{foo.dat}{1}{\xx}%
}

\section{Does NOT works in foreach (more than one element)}
\myloop\xx:={2,1}\do{%
   \ExtractData{foo.dat}{1}{\xx}\par\noindent%
}
\end{document}
  • Thanks for this. Will keep the @for loop in mind next time I get into trouble with a \foreach, which I am sure will happen. :-) – Peter Grill Apr 14 '12 at 17:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.