使用 datatool 连接数据库

使用 datatool 连接数据库

我正在尝试获取一个可以接受可变数量的 csv 文件作为参数的函数,然后将它们连接到单个数据库中Latex 的数据工具。数据库都是统一格式(相同的列),只需要附加行即可。

我的尝试是制作一个受此启发的递归函数博客文章

\documentclass{article}

\makeatletter
\newcommand{\recursive}[1]{#1 \@ifnextchar\bgroup{\recursive}{}}
\makeatother

\begin{document}

\recursive{lorem ipsum}\par
\recursive{A}{B}{C}{1}\par
\recursive{1}{2}{3}{4}{5}{6}{7}

\end{document}

在此处输入图片描述

<dbX>但我无法唯一地命名数据库,因此出现错误。也许可以通过在参数中加载数据库、将内容添加到当前主数据库<dbY>并清除<dbX>以便可以重复该过程来工作?

\documentclass{article}
\usepackage{datatool}
\DTLsetseparator{;}

%dbA
\begin{filecontents*}{databaseA.csv}
first;  second
A;      B
C;      D
E;      F
\end{filecontents*}

%dbB
\begin{filecontents*}{databaseB.csv}
first;  second
G;      H
I;      J
K;      L
M;      N
\end{filecontents*}

%dbC
\begin{filecontents*}{databaseC.csv}
first;  second
O;      P
\end{filecontents*}


\makeatletter
\newcommand{\recursive}[1]{\DTLloadrawdb{dbA}{#1}\@ifnextchar\bgroup{\recursive}{}}
\makeatother

\recursive{databaseA.csv}{databaseB.csv}{databaseC.csv}

挑战的第二部分是如何将行附加到数据库中。还有更多复杂的例子如何连接额外的列和行。但我希望有一个不太复杂的命令,只需循环遍历所有行并将它们附加到一行中,而不是逐个值地添加,甚至更好的是,只需在一个命令中合并两个数据库。

答案1

在包 datatool 的手册中只需阅读有关\DTLnewdbonloadtrue和的内容\DTLnewdbonloadfalse

默认情况下,在加载文件中给出的数据之前,\DTLloaddb会创建一个名为 的新数据库。⟨db name⟩⟨filename⟩如果你想附加数据
\DTLnewdbonloadfalse
在使用之前使用\DTLloaddb。您可以使用
\DTLnewdbonloadtrue

\documentclass{article}
\usepackage{datatool}
\DTLsetseparator{;}

%dbA
\begin{filecontents*}{databaseA.csv}
first;  second
A;      B
C;      D
E;      F
\end{filecontents*}

%dbB
\begin{filecontents*}{databaseB.csv}
first;  second
G;      H
I;      J
K;      L
M;      N
\end{filecontents*}

%dbC
\begin{filecontents*}{databaseC.csv}
first;  second
O;      P
\end{filecontents*}

\makeatletter
\newcommand\LoadNoMoreDatabases{\LoadNoMoreDatabases}%
\newcommand\LoadDatabases[1]{\DTLnewdbonloadtrue\LoadDatabasesLoop{\DTLnewdbonloadfalse}{#1}}
\newcommand\LoadDatabasesLoop[3]{%
  % #1 tokens to execute after \DTLloadrawdb
  % #2 name of database
  % #3 name of csv-file or end-marker for the loop.
  \ifx\LoadNoMoreDatabases#3\expandafter\@secondoftwo\else\expandafter\@firstoftwo\fi
  {\DTLloadrawdb{#2}{#3}#1\LoadDatabasesLoop{}{#2}}%
  {\DTLnewdbonloadtrue}%
}%
\makeatother

% Syntax of the mechanism:
%
% \LoadDatabases{<database to create>}{<csv-file 1>}{<csv-file 2>}...{<csv-file k>}\LoadNoMoreDatabases

\LoadDatabases{db}{databaseA.csv}{databaseB.csv}{databaseC.csv}\LoadNoMoreDatabases


\begin{document}

\DTLdisplaydb{db}

\end{document}

在此处输入图片描述

以上确实

\DTLnewdbonloadtrue
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 1⟩}%
\DTLnewdbonloadfalse
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 2⟩}%
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file 3⟩}%
...
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file k-1⟩}%
\DTLloadrawdb{⟨database to create⟩}{⟨csv-file k⟩}%
\DTLnewdbonloadtrue

您可以轻松地使用\clist_map_inline:nnexpl3 的 l3clist 包来创建一个宏,您可以在其中传递文件名的逗号列表

\documentclass{article}
\usepackage{xparse}
\usepackage{datatool}
\DTLsetseparator{;}

%dbA
\begin{filecontents*}{databaseA.csv}
first;  second
A;      B
C;      D
E;      F
\end{filecontents*}

%dbB
\begin{filecontents*}{databaseB.csv}
first;  second
G;      H
I;      J
K;      L
M;      N
\end{filecontents*}

%dbC
\begin{filecontents*}{databaseC.csv}
first;  second
O;      P
\end{filecontents*}

\ExplSyntaxOn
\NewDocumentCommand\LoadDatabases{mm}{
  \DTLnewdb{#1} % <- create the new empty database
  \DTLnewdbonloadfalse % <- let's append to that database
  \clist_map_inline:nn {#2} {\DTLloadrawdb{#1}{##1}} % <- have a sequence of calls `\DTLloadrawdb
  \DTLnewdbonloadtrue % <- switch back to \DTLload(raw)db not appending to an existing database but to creating databases anew.
}
\ExplSyntaxOff

\LoadDatabases{db}{databaseA.csv, databaseB.csv, databaseC.csv}

\begin{document}

\DTLdisplaydb{db}

\end{document}

在此处输入图片描述

代码解释:

2021-08-27 发行LaTeX3 接口在章节中说22.5 逗号列表映射

\clist_map_inline:Nn
\clist_map_inline:cn
\clist_map_inline:nn
更新时间:2012-06-29

\clist_map_inline:Nn ⟨comma list⟩ {⟨inline function⟩}

适用于存储在 中的⟨inline function⟩每个 。 应由接收的代码组成。从左到右返回 。⟨item⟩⟨comma list⟩⟨inline function⟩⟨item⟩#1⟨items⟩

我觉得这个解释的最后一句话有点不准确,因为返回的不仅仅是项目。返回的 token 序列数量与非空白项目的数量相同。⟨逗号列表⟩. 每个 token 序列都由一组 token 组成,这些 token 被称为⟨内联函数⟩,其中在该分类中,序列#1被由相应项目组成的那些标记所替换。

定义后

\NewDocumentCommand\LoadDatabases{mm}{
  \DTLnewdb{#1}
  \DTLnewdbonloadfalse
  \clist_map_inline:nn {#2} {\DTLloadrawdb{#1}{##1}}
  \DTLnewdbonloadtrue
}

序列\LoadDatabase{db}{databaseA.csv, databaseB.csv, databaseC.csv}

产量:

  \DTLnewdb{db}
  \DTLnewdbonloadfalse
  \clist_map_inline:nn {databaseA.csv, databaseB.csv, databaseC.csv} {\DTLloadrawdb{db}{#1}}
  \DTLnewdbonloadtrue

(在扩展过程中,\LoadDatabases两个连续的哈希值####1折叠成一个哈希值#。)

\clist_map_inline:nn⟨逗号列表⟩是:databaseA.csv, databaseB.csv, databaseC.csv \clist_map_inline:nn⟨内联函数⟩是:\DTLloadrawdb{db}{#1}
⟨内联函数⟩ #1表示⟨逗号列表⟩,即.csv 文件的名称。

因此\clist_map_inline:nn得到的结果类似

\DTLloadrawdb{db}{databaseA.csv}%
\DTLloadrawdb{db}{databaseB.csv}%
\DTLloadrawdb{db}{databaseC.csv}%

答案2

以下是使用包 (v3.0) 的方法readarray。我将每个文件读入其自己的数组,然后开始合并数组,从而消除除第一个数组之外的所有数组的标题行。

最终结果的\arrayjoined大小为[9,2]。可以访问单个元素,例如,\arrayjoined[5,2]获取第 5 行、第 2 列的数据条目。

%dbA
\begin{filecontents*}{databaseA.csv}
first;  second
A;      B
C;      D
E;      F
\end{filecontents*}

%dbB
\begin{filecontents*}{databaseB.csv}
first;  second
G;      H
I;      J
K;      L
M;      N
\end{filecontents*}

%dbC
\begin{filecontents*}{databaseC.csv}
first;  second
O;      P
\end{filecontents*}

\documentclass{article}
\usepackage{readarray}[2021-08-08]
\begin{document}
\readarraysepchar{;}
\readdef{databaseA.csv}\dbA
\readarray\dbA\arrayA[-,2]
\readdef{databaseB.csv}\dbB
\readarray\dbB\arrayB[-,2]
\readdef{databaseC.csv}\dbC
\readarray\dbC\arrayC[-,2]

\initarray\arrayjoined[\the\numexpr
\arrayAROWS+\arrayBROWS+\arrayCROWS-2\relax,2]

\mergearray\arrayC\arrayjoined[\the\numexpr
\arrayAROWS+\arrayBROWS-1\relax,1]
\mergearray\arrayB\arrayjoined[\arrayAROWS,1]
\mergearray\arrayA\arrayjoined[1,1]

\typesetarray\arrayjoined
\end{document}

在此处输入图片描述

如果允许完全扩展文件内容,下面的方法可能更简单……在这里我使用宏来连接三组文件数据,同时剥离不必要的标题。

%dbA
\begin{filecontents*}{databaseA.csv}
first;  second
A;      B
C;      D
E;      F
\end{filecontents*}

%dbB
\begin{filecontents*}{databaseB.csv}
first;  second
G;      H
I;      J
K;      L
M;      N
\end{filecontents*}

%dbC
\begin{filecontents*}{databaseC.csv}
first;  second
O;      P
\end{filecontents*}

\newcommand\stripheader[1]{\expandafter\stripheaderaux#1\relax}
\def\stripheaderaux#1;#2;#3\relax{#3}

\documentclass{article}
\usepackage{readarray}[2021-08-08]
\begin{document}
\readarraysepchar{;}
\readdef{databaseA.csv}\dbA
\readdef{databaseB.csv}\dbB
\readdef{databaseC.csv}\dbC
\edef\dbjoined{\dbA\stripheader\dbB\stripheader\dbC}

\readarray\dbjoined\arrayjoined[-,2]

\typesetarray\arrayjoined
\end{document}

注意:如果希望“调节”输出\typesetarray以用于tabular格式,那么只需要调节它,如下

\renewcommand\typesetcolsepchar{&}
\renewcommand\typesetrowsepchar{\\}
\begin{tabular}{ll}
\typesetarray\arrayjoined
\end{tabular}

在此处输入图片描述

答案3

我设法自己完成了一些工作,所以这是我最好的尝试。也许其他人会找到更优雅的解决方案。

\documentclass{article}    

\usepackage{datatool}
\DTLsetseparator{;}  
\DTLnewdb{db}

\makeatletter
\newcommand{\AddAllDBs}[1]{
    \DTLloadrawdb{tmpDB}{#1}
        \DTLforeach*{tmpDB}{\DBfirst=first,\DBsecond=second}{
      \DTLnewrow{db}
      {\let\DTLnewdbentry\relax
       \protected@xdef\insertnewdbentry{%
         \DTLnewdbentry{db}{first}{\DBfirst}%
         \DTLnewdbentry{db}{second}{\DBsecond}%
      }}\insertnewdbentry
    }   
    \DTLdeletedb{tmpDB}
    \@ifnextchar\bgroup{\AddAllDBs}{}
}
\makeatother 

  
\begin{document}

\AddAllDBs{databaseA.csv}{databaseB.csv}{databaseC.csv}
\DTLdisplaydb{db}
    
\end{document}

在此处输入图片描述

相关内容