In der heutigen Welt die Zuverlässigkeit von Datenlösungen ist alles. Wenn wir Dashboards und Berichte erstellen, erwartet man, dass die Zahlen, die sich dort widerspiegeln, korrekt und aktuell sind. Basierend auf diesen Zahlen werden Erkenntnisse gezogen und Maßnahmen ergriffen. Aus irgendeinem unvorhergesehenen Grund, wenn die Dashboards gebrochen sind oder wenn die Zahlen falsch sind, wird es ein Feuerkampf alles reparieren. Wenn die Probleme nicht rechtzeitig behoben werden, schädigt dies die Vertrauen in das Information -Staff und ihre Lösungen platziert.

Aber warum sollten Dashboards defekt sein oder falsche Zahlen haben? Wenn das Dashboard beim ersten Mal korrekt erstellt wurde, stammt das Downside in 99% der Zeit aus den Daten, die die Dashboards füttern – aus dem Information Warehouse. Einige mögliche Szenarien sind:

  • Nur wenige ETL -Pipelines sind fehlgeschlagen, daher sind die neuen Daten noch nicht in
  • Ein Tisch wird durch einen anderen neuen ersetzt
  • Einige Spalten in der Tabelle werden fallen gelassen oder umbenannt
  • Schemas im Information Warehouse haben sich geändert
  • Und noch viel mehr.

Es besteht immer noch die Möglichkeit, dass sich das Downside auf der Tableau -Web site befindet, aber meiner Erfahrung nach liegt es in den meisten Fällen immer auf einige Änderungen im Information Warehouse. Obwohl wir die Grundursache kennen, ist es nicht immer einfach, an einer Lösung zu arbeiten. Es gibt Kein zentraler Ort Wo Sie überprüfen können, welche Tableau -Datenquellen auf bestimmten Tabellen angewiesen sind. Wenn Sie das Tableau haben Datenverwaltung Add-On könnte helfen, aber soweit ich weiß, ist es schwierig, Abhängigkeiten von benutzerdefinierten SQL-Abfragen zu finden, die in Datenquellen verwendet werden.

Trotzdem ist das Add-On zu teuer und die meisten Unternehmen haben es nicht. Der wahre Schmerz beginnt, wenn Sie alle Datenquellen manuell durchgehen müssen, um sie zu reparieren. Darüber hinaus haben Sie ungeduldig eine Reihe von Benutzern auf Ihrem Kopf, die auf einen Schnellfix warten. Das Repair selbst ist möglicherweise nicht schwierig, es wäre nur zeitaufwändig.

Was wäre, wenn wir könnten? Vorwegnehmen diese Probleme Und Identifizieren Sie betroffene Datenquellen Bevor jemand ein Downside bemerkt? Wäre das nicht einfach großartig? Nun, jetzt gibt es einen Weg mit dem Tableau Metadaten -API. Der Metadaten API verwendet GraphQL, eine Abfragesprache für APIs, die nur die Daten zurückgibt, an denen Sie interessiert sind. Weitere Informationen zu dem, was mit GraphQL möglich ist, lesen GraphQL.org.

In diesem Weblog -Beitrag zeige ich Ihnen, wie Sie sich mit dem verbinden können Tableau Metadaten -API Verwendung Python’s Tableau Server Shopper (Tsc) Bibliothek, um proaktiv zu identifizieren Datenquellen verwenden bestimmte Tabellen, damit Sie schnell handeln können, bevor Probleme auftreten. Sobald Sie wissen, welche Tableau -Datenquellen von einer bestimmten Tabelle betroffen sind, können Sie einige Aktualisierungen selbst vornehmen oder die Eigentümer dieser Datenquellen über die bevorstehenden Änderungen aufmerksam machen, damit sie darauf vorbereitet werden können.

Verbindung mit der Tableau -Metadaten -API

Verbinden wir uns mit dem Tableau Server mit TSC. Wir müssen in allen Bibliotheken importieren, die wir für die Übung benötigen würden!

### Import all required libraries
import tableauserverclient as t
import pandas as pd
import json
import ast
import re

Um eine Verbindung zu der Metadaten -API herzustellen, müssen Sie zuerst ein persönliches Zugriffstoken in Ihren Tableau -Kontoeinstellungen erstellen. Dann aktualisieren Sie die <API_TOKEN_NAME> & <TOKEN_KEY> Mit dem Token, das Sie gerade erstellt haben. Auch Replace <YOUR_SITE> mit Ihrer Tableau -Web site. Wenn die Verbindung erfolgreich hergestellt wird, wird „verbunden“ im Ausgangsfenster gedruckt.

### Connect with Tableau server utilizing private entry token
tableau_auth = t.PersonalAccessTokenAuth("<API_TOKEN_NAME>", "<TOKEN_KEY>", 
                                           site_id="<YOUR_SITE>")
server = t.Server("https://dub01.on-line.tableau.com/", use_server_version=True)

with server.auth.sign_in(tableau_auth):
        print("Linked")

Erhalten wir nun eine Liste aller Datenquellen, die auf Ihrer Web site veröffentlicht werden. Es gibt viele Attribute, die Sie abrufen können, aber für den aktuellen Anwendungsfall können Sie es einfach halten und nur die Kontaktinformationen für ID, Identify und Eigentümer für jede Datenquelle erhalten. Dies wird unsere Grasp -Liste sein, zu der wir alle anderen Informationen hinzufügen werden.

############### Get all of the record of knowledge sources in your Website

all_datasources_query = """ {
  publishedDatasources {
    identify
    id
    proprietor {
    identify
    e-mail
    }
  }
}"""
with server.auth.sign_in(tableau_auth):
    consequence = server.metadata.question(
        all_datasources_query
    )

Da ich möchte, dass dieser Weblog darauf konzentriert ist, wie man proaktiv identifiziert, welche Datenquellen von einer bestimmten Tabelle betroffen sind, werde ich nicht in die Nuancen der Metadaten -API eingehen. Um besser zu verstehen, wie die Abfrage funktioniert, können Sie sich auf ein sehr detailliertes Tableaus beziehen Metadaten -API -Dokumentation.

Eine Sache zu beachten ist, dass die Metadaten -API Daten in einem JSON -Format zurückgibt. Abhängig von dem, was Sie abfragen, werden Sie mehrere verschachtelte JSON -Hear haben und es kann sehr schwierig werden, dies in einen Pandas -Datenrahmen umzuwandeln. Für die obige Metadatenabfrage erhalten Sie ein Ergebnis, das unten erwartet wird (dies sind Scheindaten, nur um Ihnen eine Vorstellung davon zu geben, wie die Ausgabe aussieht):

{
  "knowledge": {
    "publishedDatasources": (
      {
        "identify": "Gross sales Efficiency DataSource",
        "id": "f3b1a2c4-1234-5678-9abc-1234567890ab",
        "proprietor": {
          "identify": "Alice Johnson",
          "e-mail": "(e-mail protected)"
        }
      },
      {
        "identify": "Buyer Orders DataSource",
        "id": "a4d2b3c5-2345-6789-abcd-2345678901bc",
        "proprietor": {
          "identify": "Bob Smith",
          "e-mail": "(e-mail protected)"
        }
      },
      {
        "identify": "Product Returns and Profitability",
        "id": "c5e3d4f6-3456-789a-bcde-3456789012cd",
        "proprietor": {
          "identify": "Alice Johnson",
          "e-mail": "(e-mail protected)"
        }
      },
      {
        "identify": "Buyer Segmentation Evaluation",
        "id": "d6f4e5a7-4567-89ab-cdef-4567890123de",
        "proprietor": {
          "identify": "Charlie Lee",
          "e-mail": "(e-mail protected)"
        }
      },
      {
        "identify": "Regional Gross sales Tendencies (Customized SQL)",
        "id": "e7a5f6b8-5678-9abc-def0-5678901234ef",
        "proprietor": {
          "identify": "Bob Smith",
          "e-mail": "(e-mail protected)"
        }
      }
    )
  }
}

Wir müssen diese JSON -Antwort in einen Datenrahmen umwandeln, damit es einfach zu arbeiten ist. Beachten Sie, dass wir den Namen und die E -Mail des Eigentümers aus dem Eigentümerobjekt extrahieren müssen.

### We have to convert the response into dataframe for simple knowledge manipulation

col_names = consequence('knowledge')('publishedDatasources')(0).keys()
master_df = pd.DataFrame(columns=col_names)

for i in consequence('knowledge')('publishedDatasources'):
    tmp_dt = {ok:v for ok,v in i.objects()}
    master_df = pd.concat((master_df, pd.DataFrame.from_dict(tmp_dt, orient='index').T))

# Extract the proprietor identify and e-mail from the proprietor object
master_df('owner_name') = master_df('proprietor').apply(lambda x: x.get('identify') if isinstance(x, dict) else None)
master_df('owner_email') = master_df('proprietor').apply(lambda x: x.get('e-mail') if isinstance(x, dict) else None)

master_df.reset_index(inplace=True)
master_df.drop(('index','proprietor'), axis=1, inplace=True)
print('There are ', master_df.form(0) , ' datasources in your web site')

So die Struktur von master_df würde aussehen wie:

Beispielausgabe des Codes

Sobald wir die Hauptliste bereit haben, können wir die Namen der Tabellen in die Datenquellen eingebettet werden. Wenn Sie ein begeisterter Tableau -Benutzer sind, wissen Sie, dass es zwei Möglichkeiten gibt, Tabellen in einer Tableau -Datenquelle auszuwählen. Eine besteht darin, die Tabellen direkt auszuwählen und eine Beziehung zwischen ihnen und die andere zu verwenden, um eine benutzerdefinierte SQL -Abfrage mit einem oder mehreren Tabellen zu verwenden, um eine neue resultierende Tabelle zu erreichen. Daher müssen wir beide Fälle angehen.

Verarbeitung von benutzerdefinierten SQL -Abfragetabellen

Im Folgenden finden Sie die Abfrage, um die Liste aller benutzerdefinierten SQLs zusammen mit ihren Datenquellen zu erhalten. Beachten Sie, dass ich die Liste gefiltert habe, um nur die ersten 500 benutzerdefinierten SQL -Abfragen zu erhalten. Falls mehr in Ihrer Organisation vorhanden ist, müssen Sie einen Offset verwenden, um den nächsten Satz benutzerdefinierter SQL -Abfragen zu erhalten. Es besteht auch die Möglichkeit, die Cursormethode in der Pagination zu verwenden, wenn Sie eine große Liste von Ergebnissen abrufen möchten (siehe Hier). Aussofern verwende ich nur die Offset -Methode, wie ich weiß, da auf der Web site weniger als 500 benutzerdefinierte SQL -Abfragen verwendet werden.

# Get the information sources and the desk names from all of the customized sql queries used in your Website

custom_table_query = """  {
  customSQLTablesConnection(first: 500){
    nodes {
        id
        identify
        downstreamDatasources {
        identify
        }
        question
    }
  }
}
"""

with server.auth.sign_in(tableau_auth):
    custom_table_query_result = server.metadata.question(
        custom_table_query
    )

Basierend auf unseren Scheindaten würde unsere Ausgabe so aussehen:

{
  "knowledge": {
    "customSQLTablesConnection": {
      "nodes": (
        {
          "id": "csql-1234",
          "identify": "RegionalSales_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Regional Gross sales Tendencies (Customized SQL)"
            }
          ),
          "question": "SELECT r.region_name, SUM(s.sales_amount) AS total_sales FROM ecommerce.sales_data.Gross sales s JOIN ecommerce.sales_data.Areas r ON s.region_id = r.region_id GROUP BY r.region_name"
        },
        {
          "id": "csql-5678",
          "identify": "ProfitabilityAnalysis_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Product Returns and Profitability"
            }
          ),
          "question": "SELECT p.product_category, SUM(s.revenue) AS total_profit FROM ecommerce.sales_data.Gross sales s JOIN ecommerce.sales_data.Merchandise p ON s.product_id = p.product_id GROUP BY p.product_category"
        },
        {
          "id": "csql-9101",
          "identify": "CustomerSegmentation_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Buyer Segmentation Evaluation"
            }
          ),
          "question": "SELECT c.customer_id, c.location, COUNT(o.order_id) AS total_orders FROM ecommerce.sales_data.Clients c JOIN ecommerce.sales_data.Orders o ON c.customer_id = o.customer_id GROUP BY c.customer_id, c.location"
        },
        {
          "id": "csql-3141",
          "identify": "CustomerOrders_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Buyer Orders DataSource"
            }
          ),
          "question": "SELECT o.order_id, o.customer_id, o.order_date, o.sales_amount FROM ecommerce.sales_data.Orders o WHERE o.order_status = 'Accomplished'"
        },
        {
          "id": "csql-3142",
          "identify": "CustomerProfiles_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Buyer Orders DataSource"
            }
          ),
          "question": "SELECT c.customer_id, c.customer_name, c.section, c.location FROM ecommerce.sales_data.Clients c WHERE c.active_flag = 1"
        },
        {
          "id": "csql-3143",
          "identify": "CustomerReturns_CustomSQL",
          "downstreamDatasources": (
            {
              "identify": "Buyer Orders DataSource"
            }
          ),
          "question": "SELECT r.return_id, r.order_id, r.return_reason FROM ecommerce.sales_data.Returns r"
        }
      )
    }
  }
}

Genauso wie zuvor, als wir die Masterliste der Datenquellen erstellt haben, haben wir auch JSON für die nachgeschalteten Datenquellen verschachtelt, in denen wir nur den Teil des „Namens“ extrahieren müssten. In der Spalte „Abfrage“ wird der gesamte benutzerdefinierte SQL abgeladen. Wenn wir das Regex -Muster verwenden, können wir problemlos nach den Namen der in der Abfrage verwendeten Tabelle suchen.

Wir wissen, dass die Tabellennamen immer nach oder einer Be a part of -Klausel stammen und im Allgemeinen dem Format folgen <database_name>.<schema>.<table_name>. Der <database_name> ist optionally available und meistens nicht verwendet. Es gab einige Abfragen, die ich gefunden habe, die dieses Format verwendeten, und ich erhielt nur die Datenbank- und Schema -Namen und nicht den vollständigen Tabellennamen. Sobald wir die Namen der Datenquellen und die Namen der Tabellen extrahiert haben, müssen wir die Zeilen professional Datenquelle zusammenführen, da in einer einzelnen Datenquelle mehrere benutzerdefinierte SQL -Abfragen verwendet werden können.

### Convert the customized sql response into dataframe
col_names = custom_table_query_result('knowledge')('customSQLTablesConnection')('nodes')(0).keys()
cs_df = pd.DataFrame(columns=col_names)

for i in custom_table_query_result('knowledge')('customSQLTablesConnection')('nodes'):
    tmp_dt = {ok:v for ok,v in i.objects()}

    cs_df = pd.concat((cs_df, pd.DataFrame.from_dict(tmp_dt, orient='index').T))

# Extract the information supply identify the place the customized sql question was used
cs_df('data_source') = cs_df.downstreamDatasources.apply(lambda x: x(0)('identify') if x and 'identify' in x(0) else None)
cs_df.reset_index(inplace=True)
cs_df.drop(('index','downstreamDatasources'), axis=1,inplace=True)

### We have to extract the desk names from the sql question. We all know the desk identify comes after FROM or JOIN clause
# Notice that the identify of desk will be of the format <data_warehouse>.<schema>.<table_name>
# Relying on the format of how desk is known as, you'll have to modify the regex expression

def extract_tables(sql):
    # Regex to match database.schema.desk or schema.desk, keep away from alias
    sample = r'(?:FROM|JOIN)s+((?:(w+)|w+).(?:(w+)|w+)(?:.(?:(w+)|w+))?)b'
    matches = re.findall(sample, sql, re.IGNORECASE)
    return record(set(matches))  # Distinctive desk names

cs_df('customSQLTables') = cs_df('question').apply(extract_tables)
cs_df = cs_df(('data_source','customSQLTables'))

# We have to merge datasources as there will be a number of customized sqls utilized in the identical knowledge supply
cs_df = cs_df.groupby('data_source', as_index=False).agg({
    'customSQLTables': lambda x: record(set(merchandise for sublist in x for merchandise in sublist))  # Flatten & make distinctive
})

print('There are ', cs_df.form(0), 'datasources with customized sqls utilized in it')

Nachdem wir alle oben genannten Operationen ausgeführt haben, so ist die Struktur von der Struktur von cs_df würde aussehen wie:

Beispielausgabe des Codes

Verarbeitung regulärer Tabellen in Datenquellen

Jetzt müssen wir die Liste aller regulären Tabellen erhalten, die in einer Datenquelle verwendet werden, die nicht Teil des benutzerdefinierten SQL sind. Es gibt zwei Möglichkeiten, es zu tun. Verwenden Sie entweder die publishedDatasources Objekt und prüfen nach upstreamTables oder verwenden DatabaseTable und prüfen Sie upstreamDatasources. Ich werde die erste Methode nachgeben, da ich die Ergebnisse auf einer Datenquellenebene möchte (im Grunde möchte ich, dass ein Code wiederverwendet wird, wenn ich eine bestimmte Datenquelle ausführlich überprüfen möchte). Auch hier, um der Einfachheit um den Einfachheit halber zu pagieren, durchlasse ich jede Datenquelle, um sicherzustellen, dass ich alles habe. Wir bekommen das upstreamTables Innerhalb des Feldobjekts muss das ausgeräumt werden.

############### Get the information sources with the common desk names utilized in your web site

### Its finest to extract the tables data for each knowledge supply after which merge the outcomes.
# Since we solely get the desk data nested below fields, in case there are lots of of fields 
# utilized in a single knowledge supply, we'll hit the response limits and will be unable to retrieve all the information.

data_source_list = master_df.identify.tolist()

col_names = ('identify', 'id', 'extractLastUpdateTime', 'fields')
ds_df = pd.DataFrame(columns=col_names)

with server.auth.sign_in(tableau_auth):
    for ds_name in data_source_list:
        question = """ {
            publishedDatasources (filter: { identify: """"+ ds_name + """" }) {
            identify
            id
            extractLastUpdateTime
            fields {
                identify
                upstreamTables {
                    identify
                }
            }
            }
        } """
        ds_name_result = server.metadata.question(
        question
        )
        for i in ds_name_result('knowledge')('publishedDatasources'):
            tmp_dt = {ok:v for ok,v in i.objects() if ok != 'fields'}
            tmp_dt('fields') = json.dumps(i('fields'))
        ds_df = pd.concat((ds_df, pd.DataFrame.from_dict(tmp_dt, orient='index').T))

ds_df.reset_index(inplace=True)

So die Struktur von ds_df würde aussehen:

Beispielausgabe des Codes

Wir können das abflachen müssen fields Objekt und extrahieren Sie die Feldnamen sowie die Tabellennamen. Da die Tabellennamen mehrmals wiederholt werden, müssten wir uns deduplizieren, um nur die eindeutigen zu halten.

# Perform to extract the values of fields and upstream tables in json lists
def extract_values(json_list, key):
    values = ()
    for merchandise in json_list:
        values.append(merchandise(key))
    return values

ds_df("fields") = ds_df("fields").apply(ast.literal_eval)
ds_df('field_names') = ds_df.apply(lambda x: extract_values(x('fields'),'identify'), axis=1)
ds_df('upstreamTables') = ds_df.apply(lambda x: extract_values(x('fields'),'upstreamTables'), axis=1)

# Perform to extract the distinctive desk names 
def extract_upstreamTable_values(table_list):
    values = set()a
    for inner_list in table_list:
        for merchandise in inner_list:
            if 'identify' in merchandise:
                values.add(merchandise('identify'))
    return record(values)

ds_df('upstreamTables') = ds_df.apply(lambda x: extract_upstreamTable_values(x('upstreamTables')), axis=1)
ds_df.drop(("index","fields"), axis=1, inplace=True)

Sobald wir die oben genannten Operationen ausführen, die endgültige Struktur von ds_df würde ungefähr so ​​aussehen:

Beispielausgabe des Codes

Wir haben alle Teile und jetzt müssen wir sie nur zusammenführen:

###### Be a part of all the information collectively
master_data = pd.merge(master_df, ds_df, how="left", on=("identify","id"))
master_data = pd.merge(master_data, cs_df, how="left", left_on="identify", right_on="data_source")

# Save the outcomes to analyse additional
master_data.to_excel("Tableau Information Sources with Tables.xlsx", index=False)

Dies ist unser Finale master_data:

Beispielausgabe des Codes

Aufprallanalyse auf Tabellenebene

Nehmen wir an, es gab einige Schema -Änderungen in der „Verkaufstabelle“ und Sie möchten wissen, welche Datenquellen betroffen sind. Dann können Sie einfach eine kleine Funktion schreiben, die überprüft, ob in einer der beiden Spalten eine Tabelle vorhanden ist –upstreamTablesoder customSQLTables Wie unten.

def filter_rows_with_table(df, col1, col2, target_table):
    """
    Filters rows in df the place target_table is a part of any worth in both col1 or col2 (helps partial match).
    Returns full rows (all columns retained).
    """
    return df(
        df.apply(
            lambda row: 
                (isinstance(row(col1), record) and any(target_table in merchandise for merchandise in row(col1))) or
                (isinstance(row(col2), record) and any(target_table in merchandise for merchandise in row(col2))),
            axis=1
        )
    )
# For example 
filter_rows_with_table(master_data, 'upstreamTables', 'customSQLTables', 'Gross sales')

Unten ist die Ausgabe. Sie können sehen, dass 3 Datenquellen durch diese Änderung beeinflusst werden. Sie können die Datenquellenbesitzer Alice und Bob auch im Voraus darüber aufmerksam machen, damit sie an einem Repair arbeiten können, bevor etwas auf den Tableau -Dashboards bricht.

Beispielausgabe des Codes

Sie können die vollständige Model des Code in meinem GitHub -Repository überprüfen Hier.

Dies ist nur eine der potenziellen Anwendungsfälle der Tableau-Metadaten-API. Sie können auch die in benutzerdefinierten SQL-Abfragen verwendeten Feldnamen extrahieren und zum Datensatz hinzufügen, um eine Impression-Analyse auf Feldebene zu erhalten. Man kann auch die veralteten Datenquellen mit dem überwachen extractLastUpdateTime Um festzustellen, ob diese Probleme haben oder archiviert werden müssen, wenn sie nicht mehr verwendet werden. Wir können auch die verwenden dashboards Objekt, Informationen auf einer Dashboard -Ebene abzurufen.

Letzte Gedanken

Wenn Sie so weit gekommen sind, ein großes Lob. Dies ist nur ein Anwendungsfall für die Automatisierung von Tableau -Datenverwaltung. Es ist Zeit, über Ihre eigene Arbeit nachzudenken und zu überlegen, welche dieser anderen Aufgaben Sie automatisieren könnten, um Ihr Leben zu erleichtern. Ich hoffe, dass dieses Miniprojekt eine angenehme Lernerfahrung diente, um die Kraft der Tableau-Metadaten-API zu verstehen. Wenn Sie dies gerne gelesen haben, mögen Sie vielleicht auch einen anderen meiner Weblog -Beiträge über Tableau zu einigen der Herausforderungen, mit denen ich mit Large konfrontiert struggle.

Schauen Sie sich auch meinen vorherigen Weblog an, in dem ich mit Python, Streamlit und SQLite eine interaktive, datenbankbetriebene App erstellt habe.


Bevor du gehst …

Folgen Sie mir, damit Sie keine neuen Beiträge verpassen, die ich in Zukunft schreibe. Sie werden mehr von meinen Artikeln finden Mein . Sie können sich auch mit mir verbinden LinkedIn oder Twitter!



Von admin

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert