Skip to main content
cancel
Showing results for 
Search instead for 
Did you mean: 

The Power BI Data Visualization World Championships is back! It's time to submit your entry. Live now!

Reply
smpa01
Community Champion
Community Champion

Full Anti Join (M not DAX)

Hello experts,

 

I want to perform a full anti join. this option is not there in the user interface.

 

My sample data set is following

 

DataValue
D1301
D2305
D3330

 Table1

 

DataValue
D2305
D3330
D5791

Table2

 

 I want to achieve the following output

 

DataValue
D1301
D5791

 

I am currently doing it by doing LeftAnti+RightAnti. I was wondering if I can achieve it in one step to save memory consumption.

let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Data", type text}, {"Value", Int64.Type}}),
    #"Merged Queries" = Table.NestedJoin(#"Changed Type",{"Data"},Table2,{"Data"},"Table2",JoinKind.LeftAnti),
    #"Removed Columns2" = Table.RemoveColumns(#"Merged Queries",{"Table2"}),
    Custom1 = Table.NestedJoin(#"Changed Type",{"Data"},Table2,{"Data"},"Table2",JoinKind.RightAnti),
    #"Removed Other Columns" = Table.SelectColumns(Custom1,{"Table2"}),
    #"Expanded Table2" = Table.ExpandTableColumn(#"Removed Other Columns", "Table2", {"Data", "Value"}, {"Data", "Value"}),
    Custom2 = #"Removed Columns2"&#"Expanded Table2"
in
    Custom2

Table1

let
    Source = Excel.CurrentWorkbook(){[Name="Table2"]}[Content],
    #"Changed Type" = Table.TransformColumnTypes(Source,{{"Data", type text}, {"Value", Int64.Type}})
in
    #"Changed Type"

Table2

 

Thank you in advance.

Did I answer your question? Mark my post as a solution!
Proud to be a Super User!
My custom visualization projects
Plotting Live Sound: Viz1
Beautiful News:Viz1, Viz2, Viz3
Visual Capitalist: Working Hrs
2 ACCEPTED SOLUTIONS
PattemManohar
Community Champion
Community Champion

@smpa01 I've tried this way... 

 

let
    Source = Table.NestedJoin(Test296FullAntiJoinT1,{"Data"},Test296FullAntiJoinT2,{"Data"},"Test296FullAntiJoinT2",JoinKind.FullOuter),
    #"Expanded Test296FullAntiJoinT2" = Table.ExpandTableColumn(Source, "Test296FullAntiJoinT2", {"Data", "Value"}, {"Test296FullAntiJoinT2.Data", "Test296FullAntiJoinT2.Value"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Test296FullAntiJoinT2", "DataNew", each if [Data] = null then [Test296FullAntiJoinT2.Data] else if [Test296FullAntiJoinT2.Data] = null then [Data] else null),
    #"Added Conditional Column" = Table.AddColumn(#"Added Custom", "ValueNew", each if [Value] = null then [Test296FullAntiJoinT2.Value] else if [Test296FullAntiJoinT2.Value] = null then [Value] else null),
    #"Filtered Rows" = Table.SelectRows(#"Added Conditional Column", each ([DataNew] <> null)),
    #"Removed Columns" = Table.RemoveColumns(#"Filtered Rows",{"Data", "Value", "Test296FullAntiJoinT2.Data", "Test296FullAntiJoinT2.Value"})
in
    #"Removed Columns"

image.png





Did I answer your question? Mark my post as a solution!

Proud to be a PBI Community Champion




View solution in original post

Anonymous
Not applicable

Maybe something like this could work?

  • Append the two tables
  • Groupby Data, and aggregating by Count Rows as well as all rows
  • Filter the count to equal 1
  • Expand the All Rows 
  • Remove misc columns and set data types
let
    Source = Table.Combine({Table1, Table2}),
    #"Grouped Rows" = Table.Group(Source, {"Data"}, {{"Count", each Table.RowCount(_), type number}, {"Data.1", each _, type table}}),
    #"Filtered Rows" = Table.SelectRows(#"Grouped Rows", each ([Count] = 1)),
    #"Expanded Data.1" = Table.ExpandTableColumn(#"Filtered Rows", "Data.1", {"Value"}, {"Value"}),
    #"Removed Columns" = Table.RemoveColumns(#"Expanded Data.1",{"Count"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"Value", Int64.Type}})
in
    #"Changed Type"

Full Anti.png

View solution in original post

3 REPLIES 3
Rickmaurinus
Helper V
Helper V

Hi, 

 

It seems you're looking for a 'Full Anti-Join'. And even though this option is unavailable in the user-interface, there are 4 great methods you can use to achieve this.

 

1. Perform two left-anti joins (just swap around the table names), and then join them back together. 

 

 

let
  LeftAntiT1 = Table.NestedJoin(
                  Table1,    {"Key"},         
                  Table2,    {"Key"},      
                  "Table2",  JoinKind.LeftAnti // Join type
               ),
  LeftAntiT2 = Table.NestedJoin(
                  Table2,    {"Key"},         
                  Table1,    {"Key"},      
                  "Table2",  JoinKind.LeftAnti // Join type
               ),
  CombinedTable =     Table.Combine( { LeftAntiT1 , LeftAntiT2 } ),
  RemoveMergeColumn = Table.RemoveColumns( CombinedTable, {"Table2"} )
in
   RemoveMergeColumn

 

 

Another way would be to do a 'Full Outer Join' and filter the Table1 columns to be null or Table2 columns to be null

 

 

let
  FullOuterJoin =           // Perform a full outer join
    Table.NestedJoin(
        Table1,    {"Key"},
        Table2,    {"Key"},
        "Table2", JoinKind.FullOuter
    ),
  ExpandColumns =           // Retrieve all values from both tables
     Table.ExpandTableColumn(
         FullOuterJoin, "Table2",
         {"Key", "T2"}, {"Key.1", "T2"}
     ),
  T1orT2isNull =            // Keep rows where T1 or T2 = null
     Table.SelectRows( ExpandColumns, each [T1] = null or [T2] = null),
  MergeKeys =
    Table.CombineColumns(   // Combine Key Column values
        T1orT2isNull,
        {"Key.1", "Key"},
        Combiner.CombineTextByDelimiter("", QuoteStyle.None),
        "Key"
    )
in
  MergeKeys

 

 

A third way (and the cleanest one, code wise) is by using Table.RemoveMatchingRows: 

 

 

Table.RemoveMatchingRows(
      Table1,
      Table.ToRecords(Table2[[Key]]), {"Key"}
   )
  & Table.RemoveMatchingRows(
      Table2,
      Table.ToRecords(Table1[[Key]]), {"Key"}
   )

 

 

The 4th method makes use of grouping your data. You can find it in my blogpost: Understanding Join Types in Power Query - BI Gorilla

 

Hope that helped. If you have any more question, let me know!

 

Cheers, Rick 

--------------------------------------------------

@ me in replies or I'll lose your thread

 

Master Power Query M? -> https://powerquery.how

Read in-depth articles? -> BI Gorilla

Youtube Channel: BI Gorilla

 

If this post helps, then please consider accepting it as the solution to help other members find it more quickly.

 

Anonymous
Not applicable

Maybe something like this could work?

  • Append the two tables
  • Groupby Data, and aggregating by Count Rows as well as all rows
  • Filter the count to equal 1
  • Expand the All Rows 
  • Remove misc columns and set data types
let
    Source = Table.Combine({Table1, Table2}),
    #"Grouped Rows" = Table.Group(Source, {"Data"}, {{"Count", each Table.RowCount(_), type number}, {"Data.1", each _, type table}}),
    #"Filtered Rows" = Table.SelectRows(#"Grouped Rows", each ([Count] = 1)),
    #"Expanded Data.1" = Table.ExpandTableColumn(#"Filtered Rows", "Data.1", {"Value"}, {"Value"}),
    #"Removed Columns" = Table.RemoveColumns(#"Expanded Data.1",{"Count"}),
    #"Changed Type" = Table.TransformColumnTypes(#"Removed Columns",{{"Value", Int64.Type}})
in
    #"Changed Type"

Full Anti.png

PattemManohar
Community Champion
Community Champion

@smpa01 I've tried this way... 

 

let
    Source = Table.NestedJoin(Test296FullAntiJoinT1,{"Data"},Test296FullAntiJoinT2,{"Data"},"Test296FullAntiJoinT2",JoinKind.FullOuter),
    #"Expanded Test296FullAntiJoinT2" = Table.ExpandTableColumn(Source, "Test296FullAntiJoinT2", {"Data", "Value"}, {"Test296FullAntiJoinT2.Data", "Test296FullAntiJoinT2.Value"}),
    #"Added Custom" = Table.AddColumn(#"Expanded Test296FullAntiJoinT2", "DataNew", each if [Data] = null then [Test296FullAntiJoinT2.Data] else if [Test296FullAntiJoinT2.Data] = null then [Data] else null),
    #"Added Conditional Column" = Table.AddColumn(#"Added Custom", "ValueNew", each if [Value] = null then [Test296FullAntiJoinT2.Value] else if [Test296FullAntiJoinT2.Value] = null then [Value] else null),
    #"Filtered Rows" = Table.SelectRows(#"Added Conditional Column", each ([DataNew] <> null)),
    #"Removed Columns" = Table.RemoveColumns(#"Filtered Rows",{"Data", "Value", "Test296FullAntiJoinT2.Data", "Test296FullAntiJoinT2.Value"})
in
    #"Removed Columns"

image.png





Did I answer your question? Mark my post as a solution!

Proud to be a PBI Community Champion




Helpful resources

Announcements
Power BI DataViz World Championships

Power BI Dataviz World Championships

The Power BI Data Visualization World Championships is back! It's time to submit your entry.

January Power BI Update Carousel

Power BI Monthly Update - January 2026

Check out the January 2026 Power BI update to learn about new features.

FabCon Atlanta 2026 carousel

FabCon Atlanta 2026

Join us at FabCon Atlanta, March 16-20, for the ultimate Fabric, Power BI, AI and SQL community-led event. Save $200 with code FABCOMM.