class_docstr: Generates an SQL query plan from a node in the a metric dataflow plan.
class_docstr_tok: ["Generates","an","SQL","query","plan","from","a","node","in","the","a","metric","dataflow","plan","."]
docstr: Join aggregated output datasets together to return a single dataset containing all metrics/measures.
  
  This node may exist in one of two situations: when metrics/measures need to be combined in order to produce a single
  dataset with all required inputs for a metric (ie., derived metric), or when metrics need to be combined in order to
  produce a single dataset of output for downstream consumption by the end user.
  
  The join key will be a coalesced set of all previously seen dimension values. For example:
      FROM (
        ...
      ) subq_9
      FULL OUTER JOIN (
        ...
      ) subq_10
      ON
        subq_9.is_instant = subq_10.is_instant
        AND subq_9.ds = subq_10.ds
      FULL OUTER JOIN (
        ...
      ) subq_11
      ON
        COALESCE(subq_9.is_instant, subq_10.is_instant) = subq_11.is_instant
        AND COALESCE(subq_9.ds, subq_10.ds) = subq_11.ds
  
  Whenever these nodes are joined using a FULL OUTER JOIN, we must also do a subsequent re-aggregation pass to
  deduplicate the dimension value outputs across different metrics. This can happen if one or more of the
  dimensions contains a NULL value. In that case, the FULL OUTER JOIN condition will fail, because NULL = NULL
  returns NULL. Unfortunately, there's no way to do a robust NULL-safe comparison across engines in a FULL
  OUTER JOIN context, because many engines do not support complex ON conditions or other techniques we might
  use to apply a sentinel value for NULL to NULL comparisons.
docstr_tok: ["Join","aggregated","output","datasets","together","to","return","a","single","dataset","containing","all","metrics\/measures",".","This","node","may","exist","in","one","of","two","situations",":","when","metrics\/measures","need","to","be","combined","in","order","to","produce","a","single","dataset","with","all","required","inputs","for","a","metric","(","ie.",",","derived","metric",")",",","or","when","metrics","need","to","be","combined","in","order","to","produce","a","single","dataset","of","output","for","downstream","consumption","by","the","end","user",".","The","join","key","will","be","a","coalesced","set","of","all","previously","seen","dimension","values",".","For","example",":","FROM","(","...",")","subq_9","FULL","OUTER","JOIN","(","...",")","subq_10","ON","subq_9.is_instant","=","subq_10.is_instant","AND","subq_9.ds","=","subq_10.ds","FULL","OUTER","JOIN","(","...",")","subq_11","ON","COALESCE","(","subq_9.is_instant",",","subq_10.is_instant",")","=","subq_11.is_instant","AND","COALESCE","(","subq_9.ds",",","subq_10.ds",")","=","subq_11.ds","Whenever","these","nodes","are","joined","using","a","FULL","OUTER","JOIN",",","we","must","also","do","a","subsequent","re-aggregation","pass","to","deduplicate","the","dimension","value","outputs","across","different","metrics",".","This","can","happen","if","one","or","more","of","the","dimensions","contains","a","NULL","value",".","In","that","case",",","the","FULL","OUTER","JOIN","condition","will","fail",",","because","NULL","=","NULL","returns","NULL",".","Unfortunately",",","there","'s","no","way","to","do","a","robust","NULL-safe","comparison","across","engines","in","a","FULL","OUTER","JOIN","context",",","because","many","engines","do","not","support","complex","ON","conditions","or","other","techniques","we","might","use","to","apply","a","sentinel","value","for","NULL","to","NULL","comparisons","."]
code: def visit_combine_aggregated_outputs_node(
      self, node: CombineAggregatedOutputsNode
  ) -> SqlDataSet:
      """Join aggregated output datasets together to return a single dataset containing all metrics/measures.
  
      This node may exist in one of two situations: when metrics/measures need to be combined in order to produce a single
      dataset with all required inputs for a metric (ie., derived metric), or when metrics need to be combined in order to
      produce a single dataset of output for downstream consumption by the end user.
  
      The join key will be a coalesced set of all previously seen dimension values. For example:
          FROM (
            ...
          ) subq_9
          FULL OUTER JOIN (
            ...
          ) subq_10
          ON
            subq_9.is_instant = subq_10.is_instant
            AND subq_9.ds = subq_10.ds
          FULL OUTER JOIN (
            ...
          ) subq_11
          ON
            COALESCE(subq_9.is_instant, subq_10.is_instant) = subq_11.is_instant
            AND COALESCE(subq_9.ds, subq_10.ds) = subq_11.ds
  
      Whenever these nodes are joined using a FULL OUTER JOIN, we must also do a subsequent re-aggregation pass to
      deduplicate the dimension value outputs across different metrics. This can happen if one or more of the
      dimensions contains a NULL value. In that case, the FULL OUTER JOIN condition will fail, because NULL = NULL
      returns NULL. Unfortunately, there's no way to do a robust NULL-safe comparison across engines in a FULL
      OUTER JOIN context, because many engines do not support complex ON conditions or other techniques we might
      use to apply a sentinel value for NULL to NULL comparisons.
      """
      assert (
          len(node.parent_nodes) > 1
      ), "Shouldn't have a CombineAggregatedOutputsNode in the dataflow plan if there's only 1 parent."
  
      parent_data_sets: List[AnnotatedSqlDataSet] = []
      table_alias_to_instance_set: OrderedDict[
          str, InstanceSet
      ] = OrderedDict()
  
      for parent_node in node.parent_nodes:
          parent_sql_data_set = parent_node.accept(self)
          table_alias = self._next_unique_table_alias()
          parent_data_sets.append(
              AnnotatedSqlDataSet(
                  data_set=parent_sql_data_set, alias=table_alias
              )
          )
          table_alias_to_instance_set[
              table_alias
          ] = parent_sql_data_set.instance_set
  
      # When we create the components of the join that combines metrics it will be one of INNER, FULL OUTER,
      # or CROSS JOIN. Order doesn't matter for these join types, so we will use the first element in the FROM
      # clause and create join descriptions from the rest.
      from_data_set = parent_data_sets[0]
      join_data_sets = parent_data_sets[1:]
  
      # Sanity check that all parents have the same linkable specs before building the join descriptions.
      linkable_specs = (
          from_data_set.data_set.instance_set.spec_set.linkable_specs
      )
      assert all(
          [
              set(x.data_set.instance_set.spec_set.linkable_specs)
              == set(linkable_specs)
              for x in join_data_sets
          ]
      ), "All parent nodes should have the same set of linkable instances since all values are coalesced."
  
      linkable_spec_set = (
          from_data_set.data_set.instance_set.spec_set.transform(
              SelectOnlyLinkableSpecs()
          )
      )
      join_type = (
          SqlJoinType.CROSS_JOIN
          if len(linkable_spec_set.all_specs) == 0
          else SqlJoinType.FULL_OUTER
      )
  
      joins_descriptions: List[SqlJoinDescription] = []
      # TODO: refactor this loop into SqlQueryPlanJoinBuilder
      column_associations = tuple(
          self._column_association_resolver.resolve_spec(spec)
          for spec in linkable_spec_set.all_specs
      )
      column_names = tuple(
          association.column_name for association in column_associations
      )
      aliases_seen = [from_data_set.alias]
      for join_data_set in join_data_sets:
          joins_descriptions.append(
              SqlQueryPlanJoinBuilder.make_join_description_for_combining_datasets(
                  from_data_set=from_data_set,
                  join_data_set=join_data_set,
                  join_type=join_type,
                  column_names=column_names,
                  table_aliases_for_coalesce=aliases_seen,
              )
          )
          aliases_seen.append(join_data_set.alias)
  
      # We can merge all parent instances since the common linkable instances will be de-duped.
      output_instance_set = InstanceSet.merge(
          [x.data_set.instance_set for x in parent_data_sets]
      )
      output_instance_set = output_instance_set.transform(
          ChangeAssociatedColumns(self._column_association_resolver)
      )
  
      aggregated_select_columns = SelectColumnSet()
      for (
          table_alias,
          instance_set,
      ) in table_alias_to_instance_set.items():
          aggregated_select_columns = aggregated_select_columns.merge(
              instance_set.transform(
                  CreateSelectColumnForCombineOutputNode(
                      table_alias=table_alias,
                      column_resolver=self._column_association_resolver,
                      metric_lookup=self._metric_lookup,
                  )
              )
          )
      linkable_select_column_set = linkable_spec_set.transform(
          CreateSelectCoalescedColumnsForLinkableSpecs(
              column_association_resolver=self._column_association_resolver,
              table_aliases=[x.alias for x in parent_data_sets],
          )
      )
      combined_select_column_set = linkable_select_column_set.merge(
          aggregated_select_columns
      )
  
      return SqlDataSet(
          instance_set=output_instance_set,
          sql_select_node=SqlSelectStatementNode(
              description=node.description,
              select_columns=combined_select_column_set.as_tuple(),
              from_source=from_data_set.data_set.sql_select_node,
              from_source_alias=from_data_set.alias,
              joins_descs=tuple(joins_descriptions),
              group_bys=linkable_select_column_set.as_tuple(),
              where=None,
              order_bys=(),
          ),
      )
  
imports: from __future__ import annotations
  import logging
  from collections import OrderedDict
  from typing import List, Optional, Sequence, Tuple, Union
  from dbt_semantic_interfaces.enum_extension import assert_values_exhausted
  from dbt_semantic_interfaces.protocols.metric import MetricInputMeasure, MetricType
  from dbt_semantic_interfaces.references import MetricModelReference
  from dbt_semantic_interfaces.type_enums.aggregation_type import AggregationType
  from dbt_semantic_interfaces.type_enums.conversion_calculation_type import ConversionCalculationType
  from dbt_semantic_interfaces.validations.unique_valid_name import MetricFlowReservedKeywords
  from metricflow.aggregation_properties import AggregationState
  from metricflow.dag.id_generation import IdGeneratorRegistry
  from metricflow.dataflow.dataflow_plan import AddGeneratedUuidColumnNode, AggregateMeasuresNode, BaseOutput, CombineAggregatedOutputsNode, ComputedMetricsOutput, ComputeMetricsNode, ConstrainTimeRangeNode, DataflowPlanNodeVisitor, FilterElementsNode, JoinConversionEventsNode, JoinOverTimeRangeNode, JoinToBaseOutputNode, JoinToTimeSpineNode, MetricTimeDimensionTransformNode, MinMaxNode, OrderByLimitNode, ReadSqlSourceNode, SemiAdditiveJoinNode, WhereConstraintNode, WriteToResultDataframeNode, WriteToResultTableNode
  from metricflow.dataset.dataset import DataSet
  from metricflow.dataset.sql_dataset import SqlDataSet
  from metricflow.filters.time_constraint import TimeRangeConstraint
  from metricflow.instances import InstanceSet, MetadataInstance, MetricInstance, TimeDimensionInstance
  from metricflow.model.semantic_manifest_lookup import SemanticManifestLookup
  from metricflow.plan_conversion.instance_converters import AddLinkToLinkableElements, AddMetadata, AddMetrics, AliasAggregatedMeasures, ChangeAssociatedColumns, ChangeMeasureAggregationState, ConvertToMetadata, CreateSelectColumnForCombineOutputNode, CreateSelectColumnsForInstances, CreateSelectColumnsWithMeasuresAggregated, CreateSqlColumnReferencesForInstances, FilterElements, FilterLinkableInstancesWithLeadingLink, RemoveMeasures, RemoveMetrics, UpdateMeasureFillNullsWith, create_select_columns_for_instance_sets
  from metricflow.plan_conversion.select_column_gen import SelectColumnSet
  from metricflow.plan_conversion.spec_transforms import CreateColumnAssociations, CreateSelectCoalescedColumnsForLinkableSpecs, SelectOnlyLinkableSpecs
  from metricflow.plan_conversion.sql_join_builder import AnnotatedSqlDataSet, ColumnEqualityDescription, SqlQueryPlanJoinBuilder
  from metricflow.plan_conversion.time_spine import TimeSpineSource
  from metricflow.protocols.sql_client import SqlEngine
  from metricflow.specs.column_assoc import ColumnAssociation, ColumnAssociationResolver, SingleColumnCorrelationKey
  from metricflow.specs.specs import InstanceSpecSet, MeasureSpec, MetadataSpec, MetricSpec, TimeDimensionSpec
  from metricflow.sql.optimizer.optimization_levels import SqlQueryOptimizationLevel, SqlQueryOptimizerConfiguration
  from metricflow.sql.sql_exprs import SqlAggregateFunctionExpression, SqlBetweenExpression, SqlColumnReference, SqlColumnReferenceExpression, SqlComparison, SqlComparisonExpression, SqlDateTruncExpression, SqlExpressionNode, SqlExtractExpression, SqlFunction, SqlFunctionExpression, SqlGenerateUuidExpression, SqlLogicalExpression, SqlLogicalOperator, SqlRatioComputationExpression, SqlStringExpression, SqlStringLiteralExpression, SqlWindowFunction, SqlWindowFunctionExpression, SqlWindowOrderByArgument
  from metricflow.sql.sql_plan import SqlJoinDescription, SqlJoinType, SqlOrderByDescription, SqlQueryPlan, SqlQueryPlanNode, SqlSelectColumn, SqlSelectStatementNode, SqlTableFromClauseNode
  from metricflow.time.time_constants import ISO8601_PYTHON_FORMAT
label_desc: Use image node_id 16 for calling the DataflowToSqlQueryPlanConverter obj's underlying member method code with example usage: obj.visit_combine_aggregated_outputs_node(node) and returns: SqlDataSet