
Mit zunehmender Komplexität geht Data Studio in die Knie und bedankt sich mit langen Ladezeiten. Ein Multichannel-Reporting umfasst schnell mal 110 Diagramme und 48 Filter auf 13 Seiten. Daran hat sich auch mit der Rückbenennung von Looker Studio in Data Studio nichts geändert. Wer bei jedem Seitenwechsel zehn Sekunden auf drehende Kreise schaut, verliert die Lust am Reporting.
Die gute Nachricht: Es geht auch in wenigen Sekunden, selbst mit Hunderttausenden Produkten und Millionen Datenpunkten. Die lange Ladezeit entsteht vor allem, weil Data Studio Daten selbst zusammenführen, aggregieren und berechnen muss. Bereitest du die Daten vorher in BigQuery auf, entfällt dieser Schritt fast vollständig.
Wie sieht eine optimierte Datenquelle aus?
Ziel ist eine einzige, möglichst flache Datenquelle. Aus ihr zieht Data Studio die Daten für alle Diagramme und Filter, ohne sie erst mit anderen Quellen zu verknüpfen oder aufwendige berechnete Felder auszuwerten.
Überleg dir deshalb zuerst, welche Kennzahlen du zeigen willst und nach welchen Dimensionen du sie filtern oder aufteilen möchtest. In unserem Beispiel:
- Messwerte: Besuche, Bestellungen, Neukunden, Umsatz, Kosten
- Berechnete Felder: Konversionsrate, Kosten-Umsatz-Relation, Neukundenquote
- Dimensionen: Datum, Kanal, Unterkanal
Damit du bei Zeiträumen, Vergleichszeiträumen und Darstellungen nach Woche oder Monat flexibel bleibst, brauchst du die Daten auf Tagesbasis. Die fertige Tabelle hat dann diese Spalten:
datum | kanal | unterkanal | besuche | bestellungen | neukunden | umsatz | kosten
Die berechneten Felder fehlen hier bewusst, dazu später mehr.
Woher kommen die Daten?
Besuche, Bestellungen, Neukunden und Umsatz je Datum und Kampagne kommen idealerweise aus deinem Webanalyse-Tool. Für Google Analytics 4 gibt es den kostenlosen BigQuery-Export, der die Rohdaten täglich direkt in BigQuery schreibt. Alternativ rufen Tools wie Supermetrics die Daten automatisiert ab und speichern sie in einem Google Sheet oder direkt in BigQuery.
Die Kosten stammen von den Werbeplattformen. In unserem Beispiel kommen sie von Google Ads, Microsoft Advertising, Meta und drei Preisvergleichsportalen. Für Google Ads gibt es den BigQuery Data Transfer Service, für die anderen großen Plattformen Tools wie Supermetrics. Bei den Preisvergleichern musst du vermutlich improvisieren: Der erste schickt die Daten als E-Mail-Anhang, der zweite lädt sie per FTP auf deinen Server und der dritte bietet eine API. Spätestens hier wechseln viele zur Handarbeit. Wir automatisieren das mit einem kleinen Bash-Script und Google Apps Script, Details im Anhang am Ende.
Die Daten in Form bringen
Die Kosten müssen später anhand von Datum und Kampagne den richtigen Zeilen aus der Webanalyse zugeordnet werden. Dabei hilft eine einheitliche Benennung aller Kampagnen oder ein durchgängiges Tracking mit URL-Parametern.
In unserem Beispiel nutzen wir vom Kunden definierte Kampagnen-Codes, die in allen Kampagnen als URL-Parameter angehängt werden. Über die Codes und eine Matching-Tabelle ordnen wir sowohl Kosten als auch Webanalyse-Daten eindeutig einem Kanal und Unterkanal zu. Als Zwischenstufe gibt es also drei Arten von Rohdaten:
Webanalyse: datum | kampagnen_code | besuche | bestellungen | neukunden | umsatz
Kosten je Quelle: datum | kampagnen_code | kosten
Matching-Tabelle: kampagnen_code | kanal | unterkanal
Liegen die Daten so vor, ist es mit BigQuery relativ einfach, sie zu einer flachen Datenquelle zu kombinieren.
Vorbereitung
Du brauchst ein Google-Cloud-Projekt mit BigQuery und darin ein Dataset, zum Beispiel reporting. Zum Ausprobieren reicht die kostenlose BigQuery-Sandbox. Einen Überblick über Abfragen in BigQuery gibt die Einführung in BigQuery-Analysen.
Daten aus Google Sheets in BigQuery einbinden
Daten, die in einem Google Sheet landen, bindest du als externe Tabelle ein. In BigQuery legst du dazu im Dataset eine neue Tabelle an:
- Quelle Drive, Dateiformat Google Sheets, dazu die URL des Sheets.
- Als Ziel einen sprechenden Tabellennamen, zum Beispiel
import_webanalyse. - Das Schema legst du von Hand fest, mit klaren Feldnamen wie
datum,kampagnen_codeoderbesucheund den passenden Datentypen. Das macht die SQL-Abfragen später lesbarer. - Unter den erweiterten Optionen überspringst du die Kopfzeile des Sheets.
Mit „Tabelle erstellen“ ist das Sheet verknüpft. Das wiederholst du für jedes Sheet.
Bei jeder Abfrage liest BigQuery die Daten frisch aus dem Sheet. Bei großen Datenmengen bremst das. Nutze die Sheets deshalb besser als Zwischenspeicher: Eine geplante Abfrage hängt die neuen Zeilen regelmäßig an eine interne BigQuery-Tabelle an. Diese Methode funktioniert sehr gut für alles, was Tools automatisiert abrufen oder was in einem festen Format ankommt.
Die Datenquelle für Data Studio erstellen
Mit SQL führst du die Daten nicht nur zusammen, sondern kannst sie dabei auch umrechnen oder bereinigen. Eine Abfrage über sechs Kostenquellen und eine Matching-Tabelle ist sauber formatiert schnell über 100 Zeilen lang. Das Prinzip zeigt diese gekürzte Fassung:
CREATE OR REPLACE TABLE `projekt.reporting.report_daten`
PARTITION BY datum AS
WITH
kosten_google AS (
SELECT datum, kampagnen_code, kosten
FROM `projekt.reporting.kosten_google_ads`
),
kosten_meta AS (
-- Der Code steckt im Kampagnennamen, z. B. "Herbst-Sale [K123]"
SELECT
datum,
REGEXP_EXTRACT(kampagne, r'\[(K\d+)\]') AS kampagnen_code,
ausgaben AS kosten
FROM `projekt.reporting.kosten_meta`
),
kosten AS (
SELECT * FROM kosten_google
UNION ALL
SELECT * FROM kosten_meta
),
zeilen AS (
SELECT datum, kampagnen_code, besuche, bestellungen, neukunden, umsatz, 0 AS kosten
FROM `projekt.reporting.webanalyse`
UNION ALL
SELECT datum, kampagnen_code, 0, 0, 0, 0, kosten
FROM kosten
)
SELECT
z.datum,
IFNULL(m.kanal, 'Nicht zugeordnet') AS kanal,
IFNULL(m.unterkanal, 'Nicht zugeordnet') AS unterkanal,
SUM(z.besuche) AS besuche,
SUM(z.bestellungen) AS bestellungen,
SUM(z.neukunden) AS neukunden,
SUM(z.umsatz) AS umsatz,
SUM(z.kosten) AS kosten
FROM zeilen AS z
LEFT JOIN `projekt.reporting.matching_kampagnen` AS m
ON z.kampagnen_code = m.kampagnen_code
GROUP BY datum, kanal, unterkanal
Was hier passiert:
- WITH-Blöcke bringen jede Kostenquelle in dieselbe Form. Das ist besonders hilfreich, wenn eine wichtige ID wie der Kampagnen-Code nicht als eigenes Feld vorliegt, sondern mit
REGEXP_EXTRACTaus einem Textfeld gezogen werden muss. - UNION ALL stapelt Webanalyse-Zeilen und Kosten-Zeilen untereinander. So gehen weder Kosten ohne Besuche noch Besuche ohne Kosten verloren.
- LEFT JOIN auf die Matching-Tabelle ordnet jeden Code einem Kanal und Unterkanal zu. Codes ohne Treffer landen sichtbar unter „Nicht zugeordnet“, statt still zu verschwinden.
- GROUP BY verdichtet alles auf eine Zeile pro Tag, Kanal und Unterkanal.
- PARTITION BY datum teilt die Tabelle nach Tagen auf. Data Studio fragt meist nur einen Zeitraum ab, BigQuery liest dann nur die nötigen Partitionen. Das ist schneller und günstiger.
Statt CREATE OR REPLACE TABLE kannst du das Ergebnis auch über die Abfrageeinstellungen im BigQuery-Editor in eine Zieltabelle schreiben. Aktuell hältst du die Tabelle mit einer geplanten Abfrage, die täglich läuft.
Datenquelle in Data Studio einbinden
In Data Studio fügst du die Tabelle über „Daten hinzufügen“ mit dem BigQuery-Connector hinzu: Projekt, Dataset und die Tabelle report_daten auswählen. Ab hier baust du deinen Report wie gewohnt, nur ohne verknüpfte Datenquellen und mit deutlich schnelleren Seiten.
Die berechneten Felder legst du direkt in der Data-Studio-Datenquelle an, zum Beispiel:
Konversionsrate = SUM(bestellungen) / SUM(besuche)
Kosten-Umsatz-Relation = SUM(kosten) / SUM(umsatz)
Neukundenquote = SUM(neukunden) / SUM(bestellungen)
Warum nicht schon in SQL? Quoten lassen sich nicht einfach summieren. Rechnest du die Konversionsrate pro Tag in BigQuery aus, zeigt Data Studio für einen Monat schnell einen falschen Durchschnitt. Mit SUM() im Zähler und Nenner stimmt der Wert für jeden Zeitraum und jeden Filter.
Noch mehr Tempo
Reicht das nicht, gibt es zwei weitere Stellschrauben:
- Datenextrakt: Mit dem Connector „Datenextrakt“ speichert Data Studio einen Auszug der Daten selbst zwischen (mit Größenlimit) und aktualisiert ihn nach Zeitplan. Für kompakte, flache Tabellen wie unsere ist das oft der schnellste Weg.
- BI Engine: BigQuery kann Daten für Data Studio im Arbeitsspeicher halten. Das kostet extra, lohnt sich aber bei vielen Nutzern und großen Tabellen.
Darauf kannst du zählen
Was die Optimierung bringt, zeigt sich vor allem bei umfangreichen Reports. Eines unserer Projekte besteht aus:
- 123 Diagrammen auf 16 Seiten
- vier optimierten Datenquellen, davon eine für 110 Diagramme und 48 Filter auf 13 Seiten
- 12 Diagrammen aus zwei weiteren Datenquellen und einer Statustabelle für die Importe
Im gesamten Reporting gibt es nur sechs berechnete Felder und keine einzige zusammengeführte Datenquelle. Die Seiten laden spürbar schneller als ein vergleichbares Reporting mit direkter Anbindung von Google Analytics und Google Ads.
Anhang: Sonderfälle automatisieren
Manche Daten lassen sich nicht über Tools abrufen. Dann gilt es zwei Aufgaben zu lösen: die Daten automatisch empfangen und sie in eine einheitliche Form bringen. Erschwerend kommt oft hinzu, dass gleichartige Quellen völlig unterschiedlich ankommen.
In unserem Beispiel schickt der erste Preisvergleicher die Kosten pro Tag inklusive Impressionen und Klicks per E-Mail. Der zweite lädt per FTP eine Datei mit einer Zeile pro Klick hoch, ohne Kosten. Der dritte bietet eine REST-API für Klicks pro Tag, aber ohne Impressionen. Am Ende soll ein Google Sheet mit dieser Kopfzeile entstehen:
datum | preisvergleicher | impressionen | klicks | kosten
Dafür nutzen wir Google Apps Script, weil es sehr einfach mit Gmail und Google Sheets zusammenarbeitet. Eine Variante liest E-Mail-Anhänge aus einem eigenen Reporting-Postfach, die andere fragt APIs ab. Beide schreiben über dieselbe Funktion ins gemeinsame Sheet. Die FTP-Dateien schickt ein Bash-Script ebenfalls ans Reporting-Postfach. Das fertige Sheet bindest du wie oben beschrieben in BigQuery ein.
Die folgenden Beispiele sind bewusst schlank gehalten. Passe Spalten, Trennzeichen und Suchbegriffe an deine Quellen an.
Ins Google Sheet schreiben und Dubletten vermeiden
Neue Zeilen sammeln wir in output und schreiben sie am Ende in einem Rutsch ins Sheet. Vorher prüfen wir, ob es für Datum und Preisvergleicher schon eine Zeile gibt.
const FILE_ID = 'DEINE-SHEET-ID';
const SHEET_NAME = 'kosten_preisvergleicher';
const TIMEZONE = 'Europe/Berlin';
const output = [];
let existingKeys = null;
function addRow(date, provider, impressions, clicks, cost) {
const key = date + '|' + provider;
if (getExistingKeys().has(key)) {
return;
}
existingKeys.add(key);
output.push([date, provider, impressions, clicks, cost]);
}
function getExistingKeys() {
if (existingKeys === null) {
const sheet = SpreadsheetApp.openById(FILE_ID).getSheetByName(SHEET_NAME);
const lastRow = sheet.getLastRow();
const values = lastRow > 1 ? sheet.getRange(2, 1, lastRow - 1, 2).getDisplayValues() : [];
existingKeys = new Set(values.map(row => row[0] + '|' + row[1]));
}
return existingKeys;
}
function writeData() {
if (output.length === 0) {
return;
}
const sheet = SpreadsheetApp.openById(FILE_ID).getSheetByName(SHEET_NAME);
const range = sheet.getRange(sheet.getLastRow() + 1, 1, output.length, output[0].length);
range.offset(0, 0, output.length, 1).setNumberFormat('yyyy-mm-dd');
range.setValues(output);
}
function formatDate(date) {
return Utilities.formatDate(date, TIMEZONE, 'yyyy-MM-dd');
}
function toNumber(value) {
return parseFloat(String(value).replace(',', '.')) || 0;
}
E-Mail-Anhänge auslesen
Das Script sucht ungelesene Mails eines Absenders mit Anhang, verarbeitet den CSV-Anhang und archiviert den Thread, damit er beim nächsten Lauf nicht noch einmal auftaucht.
function readMailAttachments(provider, query) {
const threads = GmailApp.search(query, 0, 20);
for (const thread of threads) {
const messages = thread.getMessages();
const message = messages[messages.length - 1];
const attachment = message.getAttachments({ includeInlineImages: false })[0];
if (attachment) {
processAttachment(provider, message.getDate(), attachment);
}
thread.moveToArchive();
}
}
// Beispiel: readMailAttachments('anbieter_a', 'from:report@anbieter-a.example has:attachment in:inbox');
Unterschiedliche Formate vereinheitlichen
Jede Quelle liefert ihr eigenes Format. Eine switch-Anweisung übersetzt sie in unsere einheitliche Zeile.
function processAttachment(provider, mailDate, attachment) {
const rows = Utilities.parseCsv(attachment.getDataAsString(), ';').slice(1);
let date = formatDate(mailDate);
let impressions = 0;
let clicks = 0;
let cost = 0;
switch (provider) {
case 'anbieter_a': // eine Zeile pro Kampagne: Impressionen, Klicks, Kosten in Spalte C bis E
for (const row of rows) {
impressions += toNumber(row[2]);
clicks += toNumber(row[3]);
cost += toNumber(row[4]);
}
break;
case 'anbieter_b': // eine Zeile pro Klick, Kosten in Spalte F, Datum im Dateinamen
date = attachment.getName().match(/(\d{4}-\d{2}-\d{2})/)[1];
for (const row of rows) {
if (row.length > 5) {
clicks += 1;
cost += toNumber(row[5]);
}
}
break;
}
addRow(date, provider, impressions, clicks, cost);
}
Abruf über eine REST-API
Am Beispiel von idealo: Das Script holt sich ein Zugriffstoken und lädt den Klick-Report für gestern. Weil der Report keine Kosten enthält, rechnen wir sie über den vereinbarten Klickpreis aus. Zugangsdaten gehören nicht in den Code, sondern in die Script-Eigenschaften (Projekteinstellungen › Script-Properties).
function idealoMain() {
const props = PropertiesService.getScriptProperties();
const shopId = props.getProperty('IDEALO_SHOP_ID');
const token = idealoGetAccessToken(props.getProperty('IDEALO_CLIENT_ID'), props.getProperty('IDEALO_CLIENT_SECRET'));
const yesterday = formatDate(new Date(Date.now() - 24 * 60 * 60 * 1000));
const cpc = 0.12; // vereinbarter Klickpreis
const report = idealoDownloadDailyClickReport(shopId, 'IDEALO_DE', yesterday, yesterday, token);
for (const row of report.slice(1)) {
if (!row[0]) {
break;
}
const clicks = toNumber(row[1]);
addRow(row[0].substring(0, 10), 'idealo', 0, clicks, clicks * cpc);
}
writeData();
}
function idealoGetAccessToken(clientId, clientSecret) {
const response = UrlFetchApp.fetch('https://businessapi.idealo.com/api/v1/oauth/token', {
method: 'post',
headers: { Authorization: 'Basic ' + Utilities.base64Encode(clientId + ':' + clientSecret) },
muteHttpExceptions: true,
});
return JSON.parse(response.getContentText()).access_token;
}
function idealoDownloadDailyClickReport(shopId, siteId, fromDate, toDate, token) {
const url = 'https://businessapi.idealo.com/api/v1/shops/' + shopId +
'/daily-click-reports/download?site=' + siteId + '&from=' + fromDate + '&to=' + toDate;
const response = UrlFetchApp.fetch(url, {
headers: { Authorization: 'Bearer ' + token },
muteHttpExceptions: true,
});
return Utilities.parseCsv(response.getContentText(), ';');
}
Endpunkte und Spalten des Reports können sich ändern. Prüf sie in der aktuellen API-Dokumentation des jeweiligen Anbieters.
Workaround für FTP-Uploads
Damit per FTP empfangene Dateien denselben Weg nehmen, schickt ein Bash-Script jede neue Datei als Anhang ans Reporting-Postfach und verschiebt sie danach ins Archiv. Es läuft dauerhaft auf dem Server und braucht inotify-tools und mutt.
#!/bin/bash
WATCH_DIR="/pfad/zum/ftp-upload"
ARCHIVE_DIR="/pfad/zum/archiv"
MAILTO="reporting@example.com"
inotifywait -m -r "$WATCH_DIR" -e create -e moved_to |
while read -r dir action file; do
sleep 10 # warten, bis der Upload vollständig ist
subdir="main"
if [[ $dir =~ /([^/]*)/$ ]]; then
subdir="${BASH_REMATCH[1]}"
fi
echo "Datei '$file' von '$subdir' via FTP empfangen." |
mutt -s "Reporting $subdir" -a "$dir$file" -- "$MAILTO"
target="$ARCHIVE_DIR/$subdir/$(date +%Y%m%d)"
mkdir -p "$target"
mv -f "$dir$file" "$target/"
done
Das Archiv liegt bewusst außerhalb des überwachten Ordners. Sonst würde das Verschieben selbst wieder eine Mail auslösen.
Fazit
Langsame Data-Studio-Reports sind fast immer ein Datenproblem, kein Diagrammproblem. Verlagerst du das Zusammenführen und Aufbereiten nach BigQuery, bekommt Data Studio nur noch eine flache, tagesgenaue Tabelle und muss kaum noch rechnen. Mit geplanten Abfragen, ein paar Zeilen Apps Script für die Sonderfälle und berechneten Feldern an der richtigen Stelle läuft das Reporting danach weitgehend von allein und lädt in Sekunden.
Dieser Artikel ist 2022 zuerst bei adtraffic erschienen und wurde für rsc24 überarbeitet. Code und Hinweise wurden dabei aktualisiert.
Erstveröffentlichung: www.adtraffic.de
