Although LaTeX
is not designed as a DBMS and it is thus really ugly doing a join with it, using a match on the second \DTLforeach
you will get the desired result:
\DTLforeach[\DTLiseq{\subnum}{\reg}]{marks}{%
\subnum=number, \marks=marks}{%
But I strongly recommend using a dedicated DBMS to generate the table you like and only as a final step reading it into LaTeX
. That way you won't need any such nested loops.
As for the second question the datatool
documentation tells us in section 5.8 how to do it:
\DTLsort{number}{names}
just anywhere between loading the names db and displaying the data will do. Keeping the serials in their original order does not make sense to me. But if that is what you need, I would save the order before sorting and later read that. But if you only want a consecutive numbering of the rows, you can use a counter. Define it with \newcounter{row}
and print every row with:
\therow\refstepcounter{row}
& \reg & \name & \marks & \group/\category & \gate & \sponsored & \dept
In the manual of the packagage datatool just read about \DTLnewdbonloadtrue
and \DTLnewdbonloadfalse
:
By default, \DTLloaddb
creates a new database called ⟨db name⟩
before it loads the data given in the file ⟨filename⟩
. If you want to append the data, use
\DTLnewdbonloadfalse
before you use \DTLloaddb
. You can reverse this using
\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}
![enter image description here](https://i.stack.imgur.com/ng5BQ.png)
The above does
\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
You can easily use \clist_map_inline:nn
of expl3's l3clist-package for creating a macro where you can pass a comma-list of filenames.
\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}
![enter image description here](https://i.stack.imgur.com/ng5BQ.png)
Explanation of the code:
Release 2021-08-27 of The LaTeX3 Interfaces says in section 22.5 Mapping over comma lists:
\clist_map_inline:Nn
\clist_map_inline:cn
\clist_map_inline:nn
Updated: 2012-06-29
\clist_map_inline:Nn ⟨comma list⟩ {⟨inline function⟩}
Applies ⟨inline function⟩
to every
⟨item⟩
stored within the ⟨comma list⟩
. The ⟨inline function⟩
should
consist of code which receives the ⟨item⟩
as #1
.
The ⟨items⟩
are returned from left to right.
The last sentence of this explanation seems a bit inaccurate to me, because what is returned is not only the items. What is returned are as many sequences of tokens as there are non-blank items in the ⟨comma list⟩. Each sequence of tokens consists of that assortment of tokens that is called ⟨inline function⟩, whereby in that assortment the sequence #1
is replaced by those tokens, of which the respective item consists.
After defining
\NewDocumentCommand\LoadDatabases{mm}{
\DTLnewdb{#1}
\DTLnewdbonloadfalse
\clist_map_inline:nn {#2} {\DTLloadrawdb{#1}{##1}}
\DTLnewdbonloadtrue
}
the sequence \LoadDatabase{db}{databaseA.csv, databaseB.csv, databaseC.csv}
yields:
\DTLnewdb{db}
\DTLnewdbonloadfalse
\clist_map_inline:nn {databaseA.csv, databaseB.csv, databaseC.csv} {\DTLloadrawdb{db}{#1}}
\DTLnewdbonloadtrue
(During the expansion of \LoadDatabases
the two consecutive hashes ##
of ##1
collapse into a single hash#
.)
\clist_map_inline:nn
's ⟨comma list⟩ is: databaseA.csv, databaseB.csv, databaseC.csv
\clist_map_inline:nn
's ⟨inline function⟩ is: \DTLloadrawdb{db}{#1}
Within the ⟨inline function⟩ #1
denotes an item of the ⟨comma list⟩, i.e., the name of a .csv-file.
So \clist_map_inline:nn
yields something like
\DTLloadrawdb{db}{databaseA.csv}%
\DTLloadrawdb{db}{databaseB.csv}%
\DTLloadrawdb{db}{databaseC.csv}%
Best Answer
I had this problem with
datatool
not recognising tabs. I believe there is a bug in the definition of\DTLsettabseparator
indatatool.sty
.\DTLsettabseparator
tries to make\DTLsetseparator
a tab but fails, but you can override this by making\DTLsetseparator
a tab in your document using:The gap in
{ }
is a tab.