Udostępnij za pośrednictwem


Mapuj dane przy użyciu przepływów danych

Ważne

Ta strona zawiera instrukcje dotyczące zarządzania składnikami operacji usługi Azure IoT przy użyciu manifestów wdrażania platformy Kubernetes, które są w wersji zapoznawczej. Ta funkcja jest udostępniana z kilkoma ograniczeniami i nie powinna być używana w przypadku obciążeń produkcyjnych.

Zobacz Dodatkowe warunki użytkowania wersji zapoznawczych platformy Microsoft Azure, aby zapoznać się z postanowieniami prawnymi dotyczącymi funkcji platformy Azure, które są w wersji beta lub wersji zapoznawczej albo w inny sposób nie zostały jeszcze wydane jako ogólnie dostępne.

Użyj języka mapowania przepływu danych, aby przekształcić dane w operacjach usługi Azure IoT. Składnia jest prostym, ale zaawansowanym sposobem definiowania mapowań, które przekształcają dane z jednego formatu na inny. Ten artykuł zawiera omówienie języka mapowania przepływu danych i kluczowych pojęć.

Mapowanie umożliwia przekształcanie danych z jednego formatu na inny. Rozważmy następujący rekord wejściowy:

{
  "Name": "Grace Owens",
  "Place of birth": "London, TX",
  "Birth Date": "19840202",
  "Start Date": "20180812",
  "Position": "Analyst",
  "Office": "Kent, WA"
}

Porównaj go z rekordem wyjściowym:

{
  "Employee": {
    "Name": "Grace Owens",
    "Date of Birth": "19840202"
  },
  "Employment": {
    "Start Date": "20180812",
    "Position": "Analyst, Kent, WA",
    "Base Salary": 78000
  }
}

W rekordzie wyjściowym zostaną wprowadzone następujące zmiany w danych rekordu wejściowego:

  • Zmieniono nazwę pól: Birth Date pole ma teraz wartość Date of Birth.
  • Zrestrukturyzowane pola: zarówno pola, jak Name i Date of Birth są grupowane w nowej Employee kategorii.
  • Usunięto pole: Place of birth pole zostało usunięte, ponieważ nie jest obecne w danych wyjściowych.
  • Dodano pole: Base Salary Pole jest nowym polem Employment w kategorii.
  • Wartości pól zostały zmienione lub scalone: Position pole w danych wyjściowych łączy Position pola i Office z danych wejściowych.

Przekształcenia są osiągane za pomocą mapowania, które zwykle obejmuje:

  • Definicja danych wejściowych: identyfikowanie pól w używanych rekordach wejściowych.
  • Definicja danych wyjściowych: określanie miejsca i sposobu organizowania pól wejściowych w rekordach wyjściowych.
  • Konwersja (opcjonalnie): Modyfikowanie pól wejściowych w celu dopasowania ich do pól wyjściowych. expression jest wymagany, gdy wiele pól wejściowych jest połączonych w jedno pole wyjściowe.

Oto przykładowe mapowanie:

{
  inputs: [
    'BirthDate'
  ]
  output: 'Employee.DateOfBirth'
}
{
  inputs: [
    'Position'  // - - - - $1
    'Office'    // - - - - $2
  ]
  output: 'Employment.Position'
  expression: '$1 + ", " + $2'
}
{
  inputs: [
    '$context(position).BaseSalary'
  ]
  output: 'Employment.BaseSalary'
}

Przykładowe mapy:

  • Mapowanie jeden do jednego: BirthDate jest bezpośrednio mapowane na Employee.DateOfBirth bez konwersji.
  • Mapowanie wiele-do-jednego: łączy Position i Office w jedno Employment.Position pole. Formuła konwersji ($1 + ", " + $2) scala te pola w sformatowany ciąg.
  • Dane kontekstowe: BaseSalary są dodawane z kontekstowego zestawu danych o nazwie position.

Odwołania do pól

Odwołania do pól pokazują, jak określić ścieżki w danych wejściowych i wyjściowych przy użyciu notacji kropkowej, takiej jak Employee.DateOfBirth lub uzyskiwania dostępu do danych z kontekstowego zestawu danych za pośrednictwem .$context(position)

Właściwości metadanych MQTT i Kafka

Jeśli używasz MQTT lub Kafka jako źródła lub miejsca docelowego, możesz uzyskać dostęp do różnych właściwości metadanych w języku mapowania. Te właściwości można zamapować w danych wejściowych lub wyjściowych.

Właściwości metadanych

  • Temat: działa zarówno w przypadku MQTT, jak i platformy Kafka. Zawiera on ciąg, w którym opublikowano komunikat. Przykład: $metadata.topic.
  • Właściwość użytkownika: W MQTT odnosi się to do par klucz/wartość dowolnej postaci, które może przenosić komunikat MQTT. Jeśli na przykład komunikat MQTT został opublikowany z właściwością użytkownika z kluczem "priority" i wartością "high", $metadata.user_property.priority odwołanie przechowuje wartość "high". Klucze właściwości użytkownika mogą być dowolnymi ciągami i mogą wymagać ucieczki: $metadata.user_property."weird key" używa klucza "dziwnego klucza" (z spacją).
  • Właściwość systemowa: ten termin jest używany dla każdej właściwości, która nie jest właściwością użytkownika. Obecnie obsługiwana jest tylko jedna właściwość systemu: $metadata.system_property.content_type, która odczytuje właściwość typu zawartości komunikatu MQTT (jeśli ustawiono).
  • Nagłówek: jest to odpowiednik platformy Kafka właściwości użytkownika MQTT. Platforma Kafka może używać dowolnej wartości binarnej dla klucza, ale przepływ danych obsługuje tylko klucze ciągów UTF-8. Przykład: $metadata.header.priority. Ta funkcja jest podobna do właściwości użytkownika.

Właściwości metadanych mapowania

Mapowanie danych wejściowych

W poniższym przykładzie właściwość MQTT topic jest mapowana na origin_topic pole w danych wyjściowych:

inputs: [
  '$metadata.topic'
]
output: 'origin_topic'

Jeśli właściwość priority użytkownika znajduje się w komunikacie MQTT, w poniższym przykładzie pokazano, jak mapować ją na pole wyjściowe:

inputs: [
  '$metadata.user_property.priority'
]
output: 'priority'
Mapowanie danych wyjściowych

Właściwości metadanych można również mapować na nagłówek danych wyjściowych lub właściwość użytkownika. W poniższym przykładzie MQTT topic jest mapowany na origin_topic pole we właściwości użytkownika danych wyjściowych:

inputs: [
  '$metadata.topic'
]
output: '$metadata.user_property.origin_topic'

Jeśli przychodzący ładunek zawiera priority pole, w poniższym przykładzie pokazano, jak mapować go na właściwość użytkownika MQTT:

inputs: [
  'priority'
]
output: '$metadata.user_property.priority'

Ten sam przykład dla platformy Kafka:

inputs: [
  'priority'
]
output: '$metadata.header.priority'

Selektory zestawów danych kontekstowych

Selektory te umożliwiają mapowania w celu zintegrowania dodatkowych danych z zewnętrznych baz danych, które są określane jako zestawy danych kontekstowych.

Filtrowanie rekordów

Filtrowanie rekordów obejmuje ustawienie warunków, aby wybrać, które rekordy mają być przetwarzane lub porzucane.

Notacja kropkowa

Notacja kropkowa jest powszechnie używana w nauce komputerowej do odwoływanie się do pól, nawet rekursywnie. W programowaniu nazwy pól zwykle składają się z liter i cyfr. Przykład standardowej notacji kropkowej może wyglądać następująco:

inputs: [
  'Person.Address.Street.Number'
]

W przepływie danych ścieżka opisana przez notację kropkową może zawierać ciągi i niektóre znaki specjalne bez konieczności ucieczki:

inputs: [
  'Person.Date of Birth'
]

W innych przypadkach konieczne jest ucieczka:

inputs: [
  'Person."Tag.10".Value'
]

Poprzedni przykład, między innymi znakami specjalnymi, zawiera kropki w nazwie pola. Bez ucieczki nazwa pola będzie służyć jako separator w notacji kropkowej.

Podczas gdy przepływ danych analizuje ścieżkę, traktuje tylko dwa znaki jako specjalne:

  • Kropki (.) działają jako separatory pól.
  • Pojedyncze cudzysłów, po umieszczeniu na początku lub na końcu segmentu, rozpocznij sekcję ucieczki, w której kropki nie są traktowane jako separatory pól.

Wszystkie inne znaki są traktowane jako część nazwy pola. Ta elastyczność jest przydatna w formatach, takich jak JSON, gdzie nazwy pól mogą być dowolnymi ciągami.

W Bicep wszystkie ciągi są ujęte w pojedynczy cudzysłów ('). Przykłady prawidłowego cytowania w języku YAML dla platformy Kubernetes nie mają zastosowania.

Ucieczki

Podstawową funkcją ucieczki w ścieżce kropkowanej jest zastosowanie kropek, które są częścią nazw pól, a nie separatorów:

inputs: [
  'Payload."Tag.10".Value'
]

W tym przykładzie ścieżka składa się z trzech segmentów: Payload, Tag.10i Value.

Ucieczka reguł w notacji kropkowej

  • Ucieczka każdego segmentu oddzielnie: jeśli wiele segmentów zawiera kropki, te segmenty muszą być ujęte w podwójny cudzysłów. Można również cytować inne segmenty, ale nie ma to wpływu na interpretację ścieżki:

    inputs: [
      'Payload."Tag.10".Measurements."Vibration.$12".Value'
    ]
    

  • Prawidłowe użycie podwójnych cudzysłowów: podwójny cudzysłów musi otwierać i zamykać segment ucieczki. Wszelkie cudzysłów w środku segmentu są traktowane jako część nazwy pola:

    inputs: [
      'Payload.He said: "Hello", and waved'
    ]
    

W tym przykładzie zdefiniowano dwa pola: Payload i He said: "Hello", and waved. Gdy kropka pojawia się w tych okolicznościach, nadal pełni rolę separatora:

inputs: [
  'Payload.He said: "No. It is done"'
]

W takim przypadku ścieżka jest podzielona na segmenty Payload, He said: "Noi It is done" (począwszy od spacji).

Algorytm segmentacji

  • Jeśli pierwszy znak segmentu jest znakiem cudzysłowu, analizator wyszukuje następny cudzysłów. Ciąg ujęta między tymi cudzysłowami jest traktowana jako pojedynczy segment.
  • Jeśli segment nie zaczyna się od cudzysłowu, analizator identyfikuje segmenty, wyszukując następną kropkę lub koniec ścieżki.

Symbole wieloznaczne

W wielu scenariuszach rekord wyjściowy jest ściśle podobny do rekordu wejściowego, z wymaganymi tylko drobnymi modyfikacjami. Jeśli zajmujesz się rekordami zawierającymi wiele pól, ręczne określanie mapowań dla każdego pola może stać się żmudne. Symbole wieloznaczne upraszczają ten proces, umożliwiając uogólnione mapowania, które mogą być automatycznie stosowane do wielu pól.

Rozważmy podstawowy scenariusz, aby zrozumieć użycie gwiazdki w mapowaniach:

inputs: [
  '*'
]
output: '*'

Ta konfiguracja pokazuje podstawowe mapowanie, w którym każde pole w danych wejściowych jest bezpośrednio mapowane na to samo pole w danych wyjściowych bez żadnych zmian. Gwiazdka (*) służy jako symbol wieloznaczny zgodny z dowolnym polem w rekordzie wejściowym.

Oto jak gwiazdka (*) działa w tym kontekście:

  • Dopasowywanie wzorca: gwiazdka może być zgodna z pojedynczym segmentem lub wieloma segmentami ścieżki. Służy jako symbol zastępczy dla wszystkich segmentów w ścieżce.
  • Dopasowywanie pól: podczas procesu mapowania algorytm ocenia każde pole w rekordzie wejściowym względem wzorca określonego w elemecie inputs. Gwiazdka w poprzednim przykładzie jest zgodna ze wszystkimi możliwymi ścieżkami, co skutecznie pasuje do każdego pola w danych wejściowych.
  • Przechwycony segment: część ścieżki, do której pasuje gwiazdka, jest określana jako captured segment.
  • Mapowanie danych wyjściowych: w konfiguracji captured segment wyjściowej jest umieszczana gwiazdka. Oznacza to, że struktura danych wejściowych jest zachowywana w danych wyjściowych z captured segment wypełnieniem symbolu zastępczego dostarczonego przez gwiazdkę.

Inny przykład ilustruje, jak można używać symboli wieloznacznych do dopasowywania podsekcji i przenoszenia ich razem. Ten przykład skutecznie spłaszcza struktury zagnieżdżone w obiekcie JSON.

Oryginalny kod JSON:

{
  "ColorProperties": {
    "Hue": "blue",
    "Saturation": "90%",
    "Brightness": "50%",
    "Opacity": "0.8"
  },
  "TextureProperties": {
    "type": "fabric",
    "SurfaceFeel": "soft",
    "SurfaceAppearance": "matte",
    "Pattern": "knitted"
  }
}

Konfiguracja mapowania używająca symboli wieloznacznych:

{
  inputs: [
    'ColorProperties.*'
  ]
  output: '*'
}
{
  inputs: [
    'TextureProperties.*'
  ]
  output: '*'
}

Wynikowy kod JSON:

{
  "Hue": "blue",
  "Saturation": "90%",
  "Brightness": "50%",
  "Opacity": "0.8",
  "type": "fabric",
  "SurfaceFeel": "soft",
  "SurfaceAppearance": "matte",
  "Pattern": "knitted"
}

Umieszczanie symboli wieloznacznych

W przypadku umieszczenia symbolu wieloznakowego należy postępować zgodnie z następującymi regułami:

  • Pojedyncza gwiazdka na odwołanie do danych: tylko jedna gwiazdka (*) jest dozwolona w ramach pojedynczego odwołania do danych.
  • Dopasowanie pełnego segmentu: gwiazdka musi zawsze odpowiadać całej części ścieżki. Nie można jej użyć do dopasowania tylko do części segmentu, takiej jak path1.partial*.path3.
  • Pozycjonowanie: gwiazdka może być umieszczona w różnych częściach odwołania do danych:
    • Na początku: *.path2.path3 - Tutaj gwiazdka pasuje do każdego segmentu, który prowadzi do path2.path3.
    • W środku: path1.*.path3 — W tej konfiguracji gwiazdka pasuje do dowolnego segmentu między path1 i path3.
    • Na końcu: path1.path2.* - Gwiazdka na końcu pasuje do każdego segmentu, który następuje po path1.path2.
  • Ścieżka zawierająca gwiazdkę musi być ujęta w pojedynczy cudzysłów (').

Symbole wieloznaczne z wieloma danymi wejściowymi

Oryginalny kod JSON:

{
  "Saturation": {
    "Max": 0.42,
    "Min": 0.67,
  },
  "Brightness": {
    "Max": 0.78,
    "Min": 0.93,
  },
  "Opacity": {
    "Max": 0.88,
    "Min": 0.91,
  }
}

Konfiguracja mapowania używająca symboli wieloznacznych:

inputs: [
  '*.Max'  // - $1
  '*.Min'  // - $2
]
output: 'ColorProperties.*'
expression: '($1 + $2) / 2'

Wynikowy kod JSON:

{
  "ColorProperties" : {
    "Saturation": 0.54,
    "Brightness": 0.85,
    "Opacity": 0.89 
  }    
}

Jeśli używasz wielo wejściowych symboli wieloznacznych, gwiazdka (*) musi spójnie reprezentować to samo Captured Segment we wszystkich danych wejściowych. Na przykład w przypadku * przechwytywania Saturation we wzorcu *.Maxalgorytm mapowania oczekuje dopasowania do Saturation.Min wzorca *.Min. * W tym miejscu element jest zastępowany przez element Captured Segment z pierwszego wejścia, prowadząc do dopasowywania dla kolejnych danych wejściowych.

Rozważmy ten szczegółowy przykład:

Oryginalny kod JSON:

{
  "Saturation": {
    "Max": 0.42,
    "Min": 0.67,
    "Mid": {
      "Avg": 0.51,
      "Mean": 0.56
    }
  },
  "Brightness": {
    "Max": 0.78,
    "Min": 0.93,
    "Mid": {
      "Avg": 0.81,
      "Mean": 0.82
    }
  },
  "Opacity": {
    "Max": 0.88,
    "Min": 0.91,
    "Mid": {
      "Avg": 0.89,
      "Mean": 0.89
    }
  }
}

Początkowa konfiguracja mapowania używająca symboli wieloznacznych:

inputs: [
  '*.Max'    // - $1
  '*.Min'    // - $2
  '*.Avg'    // - $3
  '*.Mean'   // - $4
]

To początkowe mapowanie próbuje skompilować tablicę (na przykład dla Opacity: [0.88, 0.91, 0.89, 0.89]). Ta konfiguracja kończy się niepowodzeniem, ponieważ:

  • Pierwsze dane wejściowe *.Max przechwytują segment, taki jak Saturation.
  • Mapowanie oczekuje, że kolejne dane wejściowe będą obecne na tym samym poziomie:
    • Saturation.Max
    • Saturation.Min
    • Saturation.Avg
    • Saturation.Mean

Ponieważ Avg i Mean są zagnieżdżone w obiekcie Mid, gwiazdka w początkowym mapowaniu nie przechwytuje poprawnie tych ścieżek.

Poprawiona konfiguracja mapowania:

inputs: [
  '*.Max'        // - $1
  '*.Min'        // - $2
  '*.Mid.Avg'    // - $3
  '*.Mid.Mean'   // - $4
]

To poprawione mapowanie dokładnie przechwytuje niezbędne pola. Poprawnie określa ścieżki do uwzględnienia zagnieżdżonego Mid obiektu, co gwarantuje, że gwiazdki działają skutecznie na różnych poziomach struktury JSON.

Druga reguła a specjalizacja

W przypadku użycia poprzedniego przykładu z wielo wejściowych symboli wieloznacznych należy wziąć pod uwagę następujące mapowania, które generują dwie wartości pochodne dla każdej właściwości:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*.Avg'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*.Diff'
  expression: '$1 - $2'
}

To mapowanie ma na celu utworzenie dwóch oddzielnych obliczeń (Avg i Diff) dla każdej właściwości w obszarze ColorProperties. W tym przykładzie przedstawiono wynik:

{
  "ColorProperties": {
    "Saturation": {
      "Avg": 0.54,
      "Diff": 0.25
    },
    "Brightness": {
      "Avg": 0.85,
      "Diff": 0.15
    },
    "Opacity": {
      "Avg": 0.89,
      "Diff": 0.03
    }
  }
}

W tym miejscu druga definicja mapowania na tych samych danych wejściowych działa jako druga reguła mapowania.

Teraz rozważmy scenariusz, w którym określone pole wymaga innego obliczenia:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    'Opacity.Max'   // - $1
    'Opacity.Min'   // - $2
  ]
  output: 'ColorProperties.OpacityAdjusted'
  expression: '($1 + $2 + 1.32) / 2'
}

W tym przypadku Opacity pole ma unikatowe obliczenie. Istnieją dwie opcje obsługi tego nakładających się scenariuszy:

  • Uwzględnij oba mapowania dla elementu Opacity. Ponieważ pola wyjściowe różnią się w tym przykładzie, nie przesłoniłyby się nawzajem.
  • Użyj bardziej konkretnej reguły dla Opacity i usuń więcej ogólnych.

Rozważmy specjalny przypadek dla tych samych pól, aby ułatwić podjęcie decyzji o odpowiedniej akcji:

{
  inputs: [
    '*.Max'   // - $1
    '*.Min'   // - $2
  ]
  output: 'ColorProperties.*'
  expression: '($1 + $2) / 2'
}
{
  inputs: [
    'Opacity.Max'   // - $1
    'Opacity.Min'   // - $2
  ]
  output: ''
}

output Puste pole w drugiej definicji oznacza, że nie zapisuje pól w rekordzie wyjściowym (skutecznie usuwając Opacity). Ta konfiguracja jest bardziej niż Specialization Second Rule.

Rozwiązywanie nakładających się mapowań według przepływów danych:

  • Ocena postępuje z góry reguły w definicji mapowania.
  • Jeśli nowe mapowanie zostanie rozpoznane w tych samych polach co poprzednia reguła, obowiązują następujące warunki:
    • Wartość A Rank jest obliczana dla każdego rozpoznanego danych wejściowych na podstawie liczby segmentów przechwyconych symboli wieloznacznych. Na przykład, jeśli Captured Segments parametr to Properties.Opacity, parametr Rank ma wartość 2. Jeśli jest to tylko Opacitywartość , parametr Rank ma wartość 1. Mapowanie bez symboli wieloznacznych ma Rank wartość 0.
    • Jeśli druga Rank reguła jest równa lub wyższa niż poprzednia reguła, przepływ danych traktuje go jako Second Rule.
    • W przeciwnym razie przepływ danych traktuje konfigurację Specializationjako .

Na przykład mapowanie, które kieruje element i Opacity.Min do pustych danych wyjściowychOpacity.Max, ma Rank wartość 0. Ponieważ druga reguła ma wartość niższą Rank niż poprzednia, jest uważana za specjalizację i zastępuje poprzednią regułę, która oblicza wartość dla Opacityelementu .

Symbole wieloznaczne w zestawach danych kontekstowych

Teraz zobaczmy, jak zestawy danych kontekstowych mogą być używane z symbolami wieloznacznymi za pomocą przykładu. Rozważmy zestaw danych o nazwie position zawierający następujący rekord:

{
  "Position": "Analyst",
  "BaseSalary": 70000,
  "WorkingHours": "Regular"
}

We wcześniejszym przykładzie użyliśmy określonego pola z tego zestawu danych:

inputs: [
  '$context(position).BaseSalary'
]
output: 'Employment.BaseSalary'

To mapowanie kopiuje BaseSalary z zestawu danych kontekstowych bezpośrednio do Employment sekcji rekordu wyjściowego. Jeśli chcesz zautomatyzować proces i dołączyć wszystkie pola z position zestawu danych do Employment sekcji, możesz użyć symboli wieloznacznych:

inputs: [
  '$context(position).*'
]
output: 'Employment.*'

Ta konfiguracja umożliwia dynamiczne mapowanie, w którym każde pole w zestawie position danych jest kopiowane do Employment sekcji rekordu wyjściowego:

{
    "Employment": {      
      "Position": "Analyst",
      "BaseSalary": 70000,
      "WorkingHours": "Regular"
    }
}

Ostatnia znana wartość

Możesz śledzić ostatnią znaną wartość właściwości. Sufiks pola wejściowego z elementem ? $last , aby przechwycić ostatnią znaną wartość pola. Jeśli w kolejnym ładunku wejściowym brakuje właściwości, ostatnia znana wartość jest mapowana na ładunek wyjściowy.

Rozważmy na przykład następujące mapowanie:

inputs: [
  'Temperature ? $last'
]
output: 'Thermostat.Temperature'

W tym przykładzie jest śledzona ostatnia znana wartość Temperature . Jeśli kolejny ładunek wejściowy nie zawiera Temperature wartości, ostatnia znana wartość jest używana w danych wyjściowych.