Freigeben über


Datenvirtualisierung mit Azure SQL-Datenbank (Vorschau)

Gilt für:Azure SQL-Datenbank

Mit der Datenvirtualisierungsfunktion von Azure SQL Database können Sie Transact-SQL (T-SQL)-Abfragen für Dateien ausführen, die Daten in gängigen Datenformaten wie CSV (ohne die Notwendigkeit eines getrennten Textformats), Parquet und Delta (1.0) speichern. Sie können diese Daten in Azure Data Lake Storage Gen2 oder Azure Blob Storage abfragen und mit lokal gespeicherten relationalen Daten mithilfe von Verknüpfungen kombinieren. Auf diese Weise können Sie transparent auf externe Daten (schreibgeschützt) zugreifen, während diese in ihrem ursprünglichen Format und an ihrem ursprünglichen Speicherort verbleiben – dies wird auch als Datenvirtualisierung bezeichnet.

Überblick

Datenvirtualisierung bietet zwei Möglichkeiten zum Abfragen von Dateien für verschiedene Szenarios:

  • OPENROWSET-Syntax: Optimiert für die Ad-hoc-Abfrage von Dateien. Wird in der Regel zum schnellen Untersuchen von Inhalt und Struktur eines neuen Dateisatzes verwendet.
  • CREATE EXTERNAL TABLE-Syntax: Optimiert für die wiederholte Abfrage von Dateien mit identischer Syntax, so als ob die Daten lokal in der Datenbank gespeichert wären. Externe Tabellen erfordern im Vergleich zur OPENROWSET-Syntax mehrere Vorbereitungsschritte, ermöglichen aber eine bessere Kontrolle über den Datenzugriff. Externe Tabellen werden in der Regel für Analyseworkloads und Berichte verwendet.

In beiden Fällen muss wie in diesem Artikel dargestellt eine externe Datenquelle mit der T-SQL-Syntax CREATE EXTERNAL DATA SOURCE erstellt werden.

Dateiformate

Parquet- und CSV-Dateiformate (durch Trennzeichen getrennte Textdateien) werden direkt unterstützt. Das JSON-Dateiformat wird indirekt unterstützt, indem das CSV-Dateiformat angegeben wird, bei dem Abfragen jedes Dokument als separate Zeile zurückgeben. Sie können Zeilen mithilfe JSON_VALUE und OPENJSON weiter parsen.

Speichertypen

Dateien können in Azure Data Lake Storage Gen2 oder Azure Blob Storage gespeichert werden. Um Dateien abzufragen, müssen Sie den Speicherort in einem bestimmten Format bereitstellen und das Speicherorttyppräfix verwenden, das dem Typ der externen Quelle und des Endpunkts/Protokolls entspricht, wie z. B. in den folgenden Beispielen:

--Blob Storage endpoint
abs://<container>@<storage_account>.blob.core.windows.net/<path>/<file_name>.parquet
--or
abs://<storage_account_name>.blob.core.windows.net/<container_name>/

--Data Lake endpoint
adls://<container>@<storage_account>.dfs.core.windows.net/<path>/<file_name>.parquet
--or
adls://<storage_account_name>.dfs.core.windows.net/<container_name>/

Von Bedeutung

Verwenden Sie stets endpunktspezifische Präfixe. Das angegebene Standorttyppräfix wird verwendet, um das optimale Protokoll für die Kommunikation auszuwählen und alle erweiterten Funktionen zu nutzen, die von diesem bestimmten Speichertyp angeboten werden.

Das generische https:// Präfix wird nur für BULK INSERT, aber nicht für andere Anwendungsfälle unterstützt, einschließlich OPENROWSET oder EXTERNAL TABLE.

Loslegen

Wenn Sie sich bisher noch nicht mit der Datenvirtualisierung beschäftigt haben und die Funktionalität schnell testen möchten, beginnen Sie mit der Abfrage öffentlicher Datasets, die in Azure Open Datasets zur Verfügung stehen. Dazu gehört beispielsweise das „Bing COVID-19“-Dataset, auf das anonym zugegriffen werden kann.

Verwenden Sie die folgenden Endpunkte, um die Bing COVID-19-Datasets abzufragen:

  • Parkett: abs://public@pandemicdatalake.blob.core.windows.net/curated/covid-19/bing_covid-19_data/latest/bing_covid-19_data.parquet
  • CSV: abs://public@pandemicdatalake.blob.core.windows.net/curated/covid-19/bing_covid-19_data/latest/bing_covid-19_data.csv

Führen Sie für einen Schnellstart diese einfache T-SQL-Abfrage aus, um erste Einblicke in den Datensatz zu erhalten. Diese Abfrage verwendet OPENROWSET, um eine Datei abzufragen, die in einem öffentlich verfügbaren Speicherkonto gespeichert ist:

--Quick query on a file stored in a publicly available storage account:
SELECT TOP 10 *
FROM OPENROWSET(
 BULK 'abs://public@pandemicdatalake.blob.core.windows.net/curated/covid-19/bing_covid-19_data/latest/bing_covid-19_data.parquet',
 FORMAT = 'parquet'
) AS filerows;

Sie können die Datensatzexploration fortsetzen, indem Sie WHERE, GROUP BY und andere Klauseln basierend auf dem Resultset der ersten Abfrage anfügen.

Wenn Sie mit dem Abfragen öffentlicher Datensätze vertraut sind, sollten Sie mit nicht öffentlichen Datensätzen fortfahren, die Anmeldeinformationen, Zugriffsrechte und das Konfigurieren von Firewallregeln benötigen. In vielen realen Szenarios arbeiten Sie in erster Linie mit privaten Datensätzen.

Zugriff auf nicht öffentliche Speicherkonten

Ein Benutzer, der bei einer Azure SQL-Datenbank angemeldet ist, muss für den Zugriff und die Abfrage von Dateien autorisiert sein, die in nicht öffentlichen Speicherkonten gespeichert sind. Autorisierungsschritte hängen davon ab, wie azure SQL-Datenbank den Speicher authentifiziert. Die Authentifizierungstypen und alle zugehörigen Parameter werden nicht direkt mit jeder Abfrage bereitgestellt. Sie werden in das datenbankbezogene Anmeldeinformationenobjekt gekapselt, das in der Benutzerdatenbank gespeichert ist. Die Anmeldeinformationen werden von der Datenbank verwendet, um auf das Speicherkonto zuzugreifen, wann immer die Abfrage ausgeführt wird.

Azure SQL-Datenbank unterstützt die folgenden Authentifizierungstypen:

  • Shared Access Signature (SAS)
  • Verwaltete Identität
  • Microsoft Entra Pass-Through-Authentifizierung über Benutzeridentität

Eine freigegebene Zugriffssignatur (SHARED Access Signature, SAS) bietet delegierten Zugriff auf Dateien in einem Speicherkonto. SAS bietet präzise Kontrolle über die Art des Zugriffs, den Sie gewähren, einschließlich Gültigkeitsintervall, erteilten Berechtigungen und akzeptablem IP-Adressbereich. Nachdem das SAS-Token erstellt wurde, kann es nicht widerrufen oder gelöscht werden, und es ermöglicht den Zugriff, bis der Gültigkeitszeitraum abgelaufen ist.

  1. Sie können ein SAS-Token auf mehrere Arten abrufen:

  2. Gewähren Sie Lese- und Auflistungsberechtigungen über die SAS, um auf externe Dateien zugreifen zu können. Derzeit ist die Datenvirtualisierung mit Azure SQL-Datenbank schreibgeschützt.

  3. Zum Erstellen von datenbankbezogenen Anmeldeinformationen in der Azure SQL-Datenbank müssen Sie zuerst den Datenbank-Hauptschlüssel erstellen, falls noch keiner vorhanden ist. Ein Datenbankhauptschlüssel ist erforderlich, wenn die Anmeldeinformation SECRET erfordert.

    -- Create MASTER KEY if it doesn't exist in the database:
    CREATE MASTER KEY 
    ENCRYPTION BY PASSWORD = '<Some Very Strong Password Here>';
    
  4. Wenn ein SAS-Token generiert wird, enthält es am Anfang des Tokens ein Fragezeichen (?). Um das Token zu verwenden, müssen Sie das Fragezeichen (?) beim Erstellen der Anmeldeinformation entfernen. Beispiel:

    CREATE DATABASE SCOPED CREDENTIAL MyCredential
    WITH IDENTITY = 'SHARED ACCESS SIGNATURE',
    SECRET = 'sv=secret string here';
    

Zugriff auf öffentlichen Speicher über anonyme Konten

Wenn das gewünschte Dataset den öffentlichen Zugriff ermöglicht (auch als anonymer Zugriff bezeichnet), sind keine Anmeldeinformationen erforderlich, solange der Azure Storage ordnungsgemäß konfiguriert ist, siehe Konfigurieren des anonymen Lesezugriffs für Container und Blobs.

Externe Datenquelle

Bei einer externen Datenquelle handelt es sich um eine Abstraktion, die eine einfache Referenzierung eines Dateispeicherorts über mehrere Abfragen ermöglicht. Zum Abfragen öffentlicher Speicherorte müssen Sie beim Erstellen einer externen Datenquelle nur den Dateispeicherort angeben:

CREATE EXTERNAL DATA SOURCE MyExternalDataSource
WITH (
    LOCATION = 'abs://public@pandemicdatalake.blob.core.windows.net/curated/covid-19/bing_covid-19_data/latest'
);

Wenn Sie auf nicht öffentliche Speicherkonten zugreifen, müssen Sie sowohl auf den Speicherort als auch auf datenbankbezogene Anmeldeinformationen mit gekapselten Authentifizierungsparametern verweisen. Das folgende Skript erstellt eine externe Datenquelle, die auf den Dateipfad zeigt und verweist auf eine datenbankbezogene Anmeldeinformationen.

--Create external data source pointing to the file path, and referencing database-scoped credential:
CREATE EXTERNAL DATA SOURCE MyPrivateExternalDataSource
WITH (
    LOCATION = 'abs://<privatecontainer>@privatestorageaccount.blob.core.windows.net/dataset/' 
       CREDENTIAL = [MyCredential]
);

Abfragen von Datenquellen mit OPENROWSET

Die OPENROWSET-Syntax ermöglicht sofortige Ad-hoc-Abfragen, wobei nur eine minimale Anzahl von Datenbankobjekten erstellt werden muss.

OPENROWSET erfordert lediglich die Erstellung der externen Datenquelle (und möglicherweise der Anmeldeinformationen), wohingegen beim Ansatz mit externen Tabellen ein externes Dateiformat und die externe Tabelle selbst benötigt werden.

Der Wert des Parameters DATA_SOURCE wird automatisch dem Parameter BULK vorangestellt, um den vollständigen Pfad zur Datei zu bilden.

Geben Sie bei Verwendung von OPENROWSET das Format der Datei an, wie im folgenden Beispiel, das eine einzelne Datei abfragt:

SELECT TOP 10 *
FROM OPENROWSET(
 BULK 'bing_covid-19_data.parquet',
 DATA_SOURCE = 'MyExternalDataSource',
 FORMAT = 'parquet'
) AS filerows;

Abfragen mehrerer Dateien und Ordner

Mit dem Befehl OPENROWSET können auch mehrere Dateien oder Ordner abgefragt werden, indem Platzhalter im BULK-Pfad verwendet werden.

Das folgende Beispiel verwendet das offene Dataset mit NYC Yellow Taxi-Fahrtenaufzeichnungen.

Erstellen Sie zunächst die externe Datenquelle:

--Create the data source first:
CREATE EXTERNAL DATA SOURCE NYCTaxiExternalDataSource
WITH (LOCATION = 'abs://nyctlc@azureopendatastorage.blob.core.windows.net');

Jetzt können wir alle Dateien mit der Erweiterung .parquet in Ordnern abfragen. Hier werden beispielsweise nur die Dateien abfragt, die einem Namensmuster entsprechen:

--Query all files with .parquet extension in folders matching name pattern:
SELECT TOP 10 *
FROM OPENROWSET(
 BULK 'yellow/puYear=*/puMonth=*/*.parquet',
 DATA_SOURCE = 'NYCTaxiExternalDataSource',
 FORMAT = 'parquet'
) AS filerows;

Bei der Abfrage mehrerer Dateien oder Ordner müssen alle Dateien, auf die mit einem einzelnen OPENROWSET zugegriffen wird, die gleiche Struktur aufweisen (z. B. die gleiche Anzahl von Spalten und Datentypen). Ordner können nicht rekursiv durchlaufen werden.

Schemarückschluss

Mithilfe des automatischen Schemarückschlusses können Sie auch ohne Kenntnis des Dateischemas schnell Abfragen schreiben und Daten untersuchen. Der Schemarückschluss funktioniert nur mit Parquet-Dateien.

Die abgeleiteten Datentypen sind zwar praktisch, können aber größer sein als die tatsächlichen Datentypen, da in den Quelldateien möglicherweise genügend Informationen vorhanden sind, um sicherzustellen, dass der richtige Datentyp verwendet wird. Dies kann die Abfrageleistung beeinträchtigen. Beispielsweise enthalten Parquet-Dateien keine Metadaten zur maximalen Länge von Zeichenspalten, deshalb leitet die Instanz diese als varchar(8000) ab.

Verwenden Sie die gespeicherte Prozedur sp_describe_first_results_set, um die resultierenden Datentypen Ihrer Abfrage zu überprüfen, wie im folgenden Beispiel gezeigt:

EXEC sp_describe_first_result_set N'
 SELECT
 vendorID, tpepPickupDateTime, passengerCount
 FROM
 OPENROWSET(
  BULK ''yellow/*/*/*.parquet'',
  DATA_SOURCE = ''NYCTaxiExternalDataSource'',
  FORMAT=''parquet''
 ) AS nyc';

Sobald Sie die Datentypen kennen, können Sie sie mithilfe der WITH-Klausel angeben, um die Leistung zu verbessern:

SELECT TOP 100
 vendorID, tpepPickupDateTime, passengerCount
FROM
OPENROWSET(
 BULK 'yellow/*/*/*.parquet',
 DATA_SOURCE = 'NYCTaxiExternalDataSource',
 FORMAT='PARQUET'
 )
WITH (
vendorID varchar(4), -- we're using length of 4 instead of the inferred 8000
tpepPickupDateTime datetime2,
passengerCount int
) AS nyc;

Da das Schema von CSV-Dateien nicht automatisch bestimmt werden kann, müssen Spalten immer mit der WITH-Klausel angegeben werden:

SELECT TOP 10 id, updated, confirmed, confirmed_change
FROM OPENROWSET(
 BULK 'bing_covid-19_data.csv',
 DATA_SOURCE = 'MyExternalDataSource',
 FORMAT = 'CSV',
 FIRSTROW = 2
)
WITH (
 id int,
 updated date,
 confirmed int,
 confirmed_change int
) AS filerows;

Datei-Metadatenfunktionen

Bei der Abfrage mehrerer Dateien oder Ordner können Sie mit den Funktionen filepath() und filename() Dateimetadaten lesen und einen Teil oder den vollständigen Pfad und Namen der Datei abrufen, aus der die Zeile im Resultset stammt:

--Query all files and project file path and file name information for each row:
SELECT TOP 10 filerows.filepath(1) as [Year_Folder], filerows.filepath(2) as [Month_Folder],
filerows.filename() as [File_name], filerows.filepath() as [Full_Path], *
FROM OPENROWSET(
 BULK 'yellow/puYear=*/puMonth=*/*.parquet',
 DATA_SOURCE = 'NYCTaxiExternalDataSource',
 FORMAT = 'parquet') AS filerows;

Beim Aufruf ohne Parameter gibt die Funktion filepath() den Dateipfad zurück, aus dem die Zeile stammt. Bei Verwendung von DATA_SOURCE in OPENROWSET wird der Pfad relativ zu DATA_SOURCE zurückgegeben, andernfalls der vollständige Dateipfad.

Beim Aufruf mit Parameter wird ein Teil eines Pfads zurückgegeben, der dem Platzhalterzeichen an der im Parameter angegebenen Position entspricht. Der Parameterwert 1 würde z. B. den Teil eines Pfads zurückgeben, der dem ersten Platzhalterzeichen entspricht.

Die Funktion filepath() kann auch zum Filtern und Aggregieren von Zeilen verwendet werden:

SELECT
 r.filepath() AS filepath
 ,r.filepath(1) AS [year]
 ,r.filepath(2) AS [month]
 ,COUNT_BIG(*) AS [rows]
FROM OPENROWSET(
 BULK 'yellow/puYear=*/puMonth=*/*.parquet',
DATA_SOURCE = 'NYCTaxiExternalDataSource',
FORMAT = 'parquet'
 ) AS r
WHERE
 r.filepath(1) IN ('2017')
 AND r.filepath(2) IN ('10', '11', '12')
GROUP BY
 r.filepath()
 ,r.filepath(1)
 ,r.filepath(2)
ORDER BY
 filepath;

Erstellen einer Ansicht oberhalb von OPENROWSET

Sie können Ansichten erstellen und verwenden, um OPENROWSET-Abfragen zu umschließen, sodass die zugrunde liegende Abfrage problemlos wiederverwendet werden kann:

CREATE VIEW TaxiRides AS
SELECT *
FROM OPENROWSET(
 BULK 'yellow/puYear=*/puMonth=*/*.parquet',
 DATA_SOURCE = 'NYCTaxiExternalDataSource',
 FORMAT = 'parquet'
) AS filerows;

Es ist ebenfalls praktisch, einer Ansicht mithilfe der Funktion filepath() Spalten mit Daten zum Dateispeicherort hinzuzufügen, um eine einfachere und leistungsfähigere Filterung zu ermöglichen. Durch die Verwendung von Sichten kann die Anzahl der Dateien und die von der Abfrage oberhalb der Ansicht zu lesende und zu verarbeitende Datenmenge verringert werden, wenn nach einer dieser Spalten gefiltert wird:

CREATE VIEW TaxiRides AS
SELECT *
 , filerows.filepath(1) AS [year]
 , filerows.filepath(2) AS [month]
FROM OPENROWSET(
 BULK 'yellow/puYear=*/puMonth=*/*.parquet',
 DATA_SOURCE = 'NYCTaxiExternalDataSource',
 FORMAT = 'parquet'
) AS filerows;

Darüber hinaus können Berichts- und Analysetools wie Power BI mithilfe von Sichten die Ergebnisse von OPENROWSET nutzen.

Externe Tabellen

Externe Tabellen kapseln den Zugriff auf Dateien, wodurch die Abfrage fast identisch mit der Abfrage lokaler relationaler Daten ist, die in Benutzertabellen gespeichert sind. Zum Erstellen einer externen Tabelle müssen die externe Datenquelle und die externen Dateiformatobjekte vorhanden sein:

--Create external file format
CREATE EXTERNAL FILE FORMAT DemoFileFormat
WITH (
 FORMAT_TYPE=PARQUET
);

--Create external table:
CREATE EXTERNAL TABLE tbl_TaxiRides(
 vendorID VARCHAR(100) COLLATE Latin1_General_BIN2,
 tpepPickupDateTime DATETIME2,
 tpepDropoffDateTime DATETIME2,
 passengerCount INT,
 tripDistance FLOAT,
 puLocationId VARCHAR(8000),
 doLocationId VARCHAR(8000),
 startLon FLOAT,
 startLat FLOAT,
 endLon FLOAT,
 endLat FLOAT,
 rateCodeId SMALLINT,
 storeAndFwdFlag VARCHAR(8000),
 paymentType VARCHAR(8000),
 fareAmount FLOAT,
 extra FLOAT,
 mtaTax FLOAT,
 improvementSurcharge VARCHAR(8000),
 tipAmount FLOAT,
 tollsAmount FLOAT,
 totalAmount FLOAT
)
WITH (
 LOCATION = 'yellow/puYear=*/puMonth=*/*.parquet',
 DATA_SOURCE = NYCTaxiExternalDataSource,
 FILE_FORMAT = DemoFileFormat
);

Nachdem die externe Tabelle erstellt wurde, können Sie sie wie jede andere Tabelle abfragen:

SELECT TOP 10 *
FROM tbl_TaxiRides;

Genau wie OPENROWSET ermöglichen externe Tabellen die Abfrage mehrerer Dateien und Ordner mithilfe von Platzhaltern. Schemainference wird bei externen Tabellen nicht unterstützt.

Leistungsüberlegungen

Es gibt keine feste Grenze für die Anzahl der Dateien oder die Menge an Daten, die abgefragt werden kann. Die Abfrageleistung hängt jedoch von der Datenmenge, dem Datenformat, der Art, wie die Daten organisiert sind, und der Komplexität der Abfragen und Joins ab.

Abfragen von partitionierten Daten

Daten werden häufig in Unterordnern organisiert, die auch Partitionen genannt werden. Sie können die Abfrage anweisen, nur bestimmte Ordner und Dateien zu lesen. Dies reduziert die Anzahl der Dateien und die Datenmenge, die die Abfrage lesen und verarbeiten muss, was zu einer besseren Leistung führt. Diese Art der Abfrageoptimierung wird als Partitionsbereinigung oder Partitionsentfernung bezeichnet. Sie können Partitionen aus der Abfrageausführung entfernen, indem Sie die Metadatenfunktion filepath() in der WHERE Klausel der Abfrage verwenden.

Die folgende Beispielabfrage liest nur die Datendateien von „NYC Yellow Taxi“ für die letzten drei Monate des Jahres 2017:

SELECT
    r.filepath() AS filepath
    ,r.filepath(1) AS [year]
    ,r.filepath(2) AS [month]
    ,COUNT_BIG(*) AS [rows]
FROM OPENROWSET(
        BULK 'yellow/puYear=*/puMonth=*/*.parquet',
        DATA_SOURCE = 'NYCTaxiExternalDataSource',
        FORMAT = 'parquet'
    )
WITH (
    vendorID INT
) AS [r]
WHERE
    r.filepath(1) IN ('2017')
    AND r.filepath(2) IN ('10', '11', '12')
GROUP BY
    r.filepath()
    ,r.filepath(1)
    ,r.filepath(2)
ORDER BY
    filepath;

Wenn Ihre gespeicherten Daten nicht partitioniert sind, sollten Sie sie für eine verbesserte Abfrageleistung partitionieren.

Wenn Sie externe Tabellen verwenden, werden die Funktionen filepath() und filename() unterstützt, aber nicht in der WHERE-Klausel.

Fehlersuche

Probleme mit der Abfrageausführung werden in der Regel dadurch verursacht, dass azure SQL-Datenbank nicht auf den Dateispeicherort zugreifen kann. Die zugehörigen Fehlermeldungen verweisen möglicherweise auf unzureichende Zugriffsrechte, einen nicht vorhandenen Speicherort oder Dateipfad, eine Datei, die von einem anderen Prozess verwendet wird, oder ein Verzeichnis, das nicht aufgelistet werden kann. In den meisten Fällen bedeutet dies, dass der Zugriff auf Dateien durch Richtlinien zur Steuerung des Netzwerkdatenverkehrs oder aufgrund fehlender Zugriffsrechte blockiert wird. Folgendes sollte überprüft werden:

  • Falscher oder falsch eingegebener Pfad zum Speicherort.
  • Gültigkeit des SAS-Schlüssels: Er könnte abgelaufen sein, einen Rechtschreibfehler enthalten oder mit einem Fragezeichen beginnen.
  • Zugelassene SAS-Schlüsselberechtigungen: Mindestens Lesen und Auflisten, wenn Platzhalter verwendet werden.
  • Blockierter eingehender Datenverkehr für das Speicherkonto. Überprüfen Sie die Verwaltung virtueller Netzwerkregeln für Azure Storage.
  • Zugriffsberechtigungen für verwaltete Identitäten: Stellen Sie sicher, dass die verwaltete Identität der Azure SQL-Datenbank Zugriffsrechte für das Speicherkonto gewährt wird.
  • Der Kompatibilitätsgrad der Datenbank muss mindestens 130 betragen, damit Datenvirtualisierungsabfragen funktionieren.

Einschränkungen

  • Derzeit werden Statistiken zu externen Tabellen in der Azure SQL-Datenbank nicht unterstützt.
  • CREATE EXTERNAL TABLE AS SELECT ist derzeit nicht in Azure SQL-Datenbank verfügbar.
  • Das Feature Sicherheit auf Zeilenebene wird bei externen Tabellen nicht unterstützt.
  • Die Regel Dynamischen Datenmaskierung kann nicht für eine Spalte in einer externen Tabelle definiert werden.
  • Managed Identity unterstützt keine mandantenübergreifenden Szenarien. Wenn sich Ihr Azure Storage-Konto in einem anderen Mandanten befindet, ist die Shared Access Signature (SAS) die unterstützte Methode.

Bekannte Probleme

  • Wenn Parametrisierung für Always Encrypted in SQL Server Management Studio (SSMS) aktiviert ist, schlagen die Datenvirtualisierungsabfragen mit der Fehlermeldung Incorrect syntax near 'PUSHDOWN' fehl.