Equivalent of SQL Left Join using Tuples

I am trying to do the equivalent of a SQL Left Join in Relevance using Tuples. Here is what I have so far, any help would be appreciated. Let me know if you need more details.

/* Data Set 1 which I would like to list all rows (Left Side) */
(item 0 of it, item 1 of it, item 2 of it, item 3 of it) of (“1”,“Property1”,“Property2”,“Property3”;“2”,“Property1”,“Property2”,“Property3”;“3”,“Property1”,“Property2”,“Property3”;“4”,“Property1”,“Property2”,“Property3”)

/* Data Set 2 */
(item 0 of it, item 1 of it, item 2 of it, item 3 of it) of (“2”,“Property4”,“Property5”,“Property6”;“4”,“Property4”,“Property5”,“Property6”)

I have this relevance which gives me the Intersection of these two data sets.

(item 0 of item 0 of it, item 1 of item 0 of it, item 1 of item 1 of it) of
((item 0 of it, item 1 of it, item 2 of it, item 3 of it) of (“1”,“Property1”,“Property2”,“Property3”;“2”,“Property1”,“Property2”,“Property3”;“3”,“Property1”,“Property2”,“Property3”;“4”,“Property1”,“Property2”,“Property3”),
(item 0 of it, item 1 of it, item 2 of it, item 3 of it) of (“2”,“Property4”,“Property5”,“Property6”;“4”,“Property4”,“Property5”,“Property6”))
whose (item 0 of item 0 of it = item 0 of item 1 of it)

Output:
2, Property1, Property4
4, Property1, Property4

The desired output would look something like this:
1, Property1,
2, Property1, Property4
3, Property1,
4, Property1, Property4

Thank you,
Jeff

@jpboisen
Interesting one.

I have been playing around with it and have a step towards a solution. I suspect sets will work better but have not yet solved.

 (if (item 0 of item 0 of it = item 0 of item 1 of it) then (item 0 of it,item 1 of it) else (item 0 of it,("","","",""))) of (items 0 of it,items 1 of it) of (("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3")
 ,("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
1 Like

I got closer this morning after a cup of :coffee:

q:  elements of (item 0 of it +  (set of (it as string) of (item 1 of it) of  ((elements of item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it )))) of (set of (it as string) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"),set of (it as string) of("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
A: 1, Property1, Property2, Property3
A: 2, Property1, Property2, Property3
A: 2, Property4, Property5, Property6
A: 3, Property1, Property2, Property3
A: 4, Property1, Property2, Property3
A: 4, Property4, Property5, Property6 

I should have it completed before end of day :wink:

1 Like

@jpboisen

Is this what you are looking for:
(item 0 of it & ", " & concatenation ", " of tuple string items 1 of items 1 of (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it) )
of
(elements of item 0 of it, item 1 of it)
of
(set of (item 0 of it & ", " & item 1 of it) of (“1”,“Property1”,“Property2”,“Property3”;“2”,“Property1”,“Property2”,“Property3”;“3”,“Property1”,“Property2”,“Property3”;“4”,“Property1”,“Property2”,“Property3”)
,
set of
(item 0 of it & ", " & item 1 of it) of (“2”,“Property4”,“Property5”,“Property6”;“4”,“Property4”,“Property5”,“Property6”; “4”,“Property11”,“Property5”,“Property6”)
)

I am just summarizing over the first two properties here. Are 1,2,3,4 unique ids in a query ?

2 Likes

Interesting, so you took care of trimming out the extra Property2, Property3 in the bottom tuple when you create the string sets and you expand SetA out in the second layer of tuple, but leave SetB as a string set object.

q: (item 0 of it) of  (elements of item 0 of it, item 1 of it) of (set of (item 0 of it & ", " & item 1 of it) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"),set of (item 0 of it & ", " & item 1 of it) of ("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
A: 1, Property1
A: 2, Property1
A: 3, Property1
A: 4, Property1


q: (item 1 of it) of  (elements of item 0 of it, item 1 of it) of (set of (item 0 of it & ", " & item 1 of it) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"),set of (item 0 of it & ", " & item 1 of it) of ("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
E: This expression evaluates to an unrepresentable object of type "string set"

Finally you keep item 0 and concatenate with any item 1 elements that match for the second set.

The real trick there is what you do when you expand the elements of SetB and keep it tupled with item 0, to allow you to add the whose filter inside of the topmost it clause.

q: (item 0 of it & " | " &  concatenation ", " of (it as string) of (item 0 of it, elements of item 1 of it) of it) of  (elements of item 0 of it, item 1 of it) of (set of (item 0 of it & ", " & item 1 of it) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"),set of (item 0 of it & ", " & item 1 of it) of ("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
A: 1, Property1 | ( 1, Property1 ), ( 2, Property4 ), ( 1, Property1 ), ( 4, Property4 )
A: 2, Property1 | ( 2, Property1 ), ( 2, Property4 ), ( 2, Property1 ), ( 4, Property4 )
A: 3, Property1 | ( 3, Property1 ), ( 2, Property4 ), ( 3, Property1 ), ( 4, Property4 )
A: 4, Property1 | ( 4, Property1 ), ( 2, Property4 ), ( 4, Property1 ), ( 4, Property4 )
T: 0.649 ms
I: plural string

q: (item 0 of it & " | " &  concatenation ", " of (it as string) of (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it) of it) of  (elements of item 0 of it, item 1 of it) of (set of (item 0 of it & ", " & item 1 of it) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"),set of (item 0 of it & ", " & item 1 of it) of ("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"))
A: 1, Property1 | 
A: 2, Property1 | ( 2, Property1 ), ( 2, Property4 )
A: 3, Property1 | 
A: 4, Property1 | ( 4, Property1 ), ( 4, Property4 )
T: 0.313 ms
I: plural string

keeping the Whose inside of the Clause lets you filter out the non-matching.

For a final query of

q: (item 0 of it & ", " & concatenation ", " of tuple string items 1 of items 1 of (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it) ) of (elements of item 0 of it, item 1 of it) of (set of (item 0 of it & ", " & item 1 of it) of ("1","Property1","Property2","Property3";"2","Property1","Property2","Property3";"3","Property1","Property2","Property3";"4","Property1","Property2","Property3"), set of (item 0 of it & ", " & item 1 of it) of ("2","Property4","Property5","Property6";"4","Property4","Property5","Property6"; "4","Property1","Property5","Property6"))
A: 1, Property1, 
A: 2, Property1, Property4
A: 3, Property1, 
A: 4, Property1, Property1, Property4
3 Likes

Perhaps I’m misreading the expectation here, but I would think that a “Left Join” would return something like this:

Data set 1:

 "1","Property1","Property2","Property3"
; "2","Property1","Property2","Property3"
; "3","Property1","Property2","Property3"
; "4","Property1","Property2","Property3"

Data Set 2:

 "2","Property4","Property5","Property6"
;"4","Property4","Property5","Property6"

Left Join Result:

 "1","Property1","Property2","Property3"
; "2","Property1","Property2", "Property3", "Property4", "Property5", "Property6"
; "3","Property1","Property2","Property3"
; "4","Property1","Property2","Property3", "Property4", "Property5", "Property6"

I’m just starting to look at it now, but I think I’ll have something similar to what’s already posted…

1 Like

So I managed to come up with a solution, but … I’m not sure I can explain it’s behavior. I think I may have possibly hit an edge case bug in the ‘tuple item’ inspector, I’m not sure whether to rely on this behavior. Maybe @brolly33 or @alanm could comment…

I start off by casting each data to a string, so this only works with properties that can be represented as simple strings. I start by building a set of data1 and a set of data2:

(
  set of (it as string) 
 of (
     "1","Property1","Property2","Property3"
    ;"2","Property1","Property2","Property3"
    ;"3","Property1","Property2","Property3"
    ;"4","Property1","Property2","Property3"
    )
  , set of (it as string)
  of (
     "2","Property4","Property5","Property6"
    ;"4","Property4","Property5","Property6"
    )
)

From there, I plan to unwind the first set, so everything in the “left side” of the left join has its elements expanded. I preserve the second piece of data as a set that I would iterate in the next step…

(
 elements of item 0 of it
 , item 1 of it
) 
of
(
  set of (it as string) 
 of (
     "1","Property1","Property2","Property3"
    ;"2","Property1","Property2","Property3"
    ;"3","Property1","Property2","Property3"
    ;"4","Property1","Property2","Property3"
    )
  , set of (it as string)
  of (
     "2","Property4","Property5","Property6"
    ;"4","Property4","Property5","Property6"
    )
)

Now, here’s where it gets a bit odd. Starting with the query above, I’m going to take the current “left side” element and compare it with all of the “right side” elements. This step will discard the entire row where there is no match in the right side. I expected that, and that I would handle cases where the right side doesn’t exist later. This part behaves as expected -

(
 item 0 of it
 ,  (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it)
)  /* of everything else so far */

Result:

( 2, Property1, Property2, Property3 ), ( ( 2, Property1, Property2, Property3 ), ( 2, Property4, Property5, Property6 ) )
( 4, Property1, Property2, Property3 ), ( ( 4, Property1, Property2, Property3 ), ( 4, Property4, Property5, Property6 ) )

This is as expected - the “1” and “3” have been discarded, because there was no 1 or 3 to match in the second set. However, I went ahead and started building the string tuple representations for the elements that are present…only to find 1 and 3 return as a result of my text conversions…

(
 item 0 of it
 ,  concatenation ", " of (it as string) 
     of tuple items whose (index of it > 0) of  items 1 of 
    (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it)
)
of
(
 elements of item 0 of it
 , item 1 of it
) 
of
(
  set of (it as string) 
 of (
     "1","Property1","Property2","Property3"
    ;"2","Property1","Property2","Property3"
    ;"3","Property1","Property2","Property3"
    ;"4","Property1","Property2","Property3"
    )
  , set of (it as string)
  of (
     "2","Property4","Property5","Property6"
    ;"4","Property4","Property5","Property6"
    )
)

The result for this, which brings back the 1 and 3 items from the first set, is

( 1, Property1, Property2, Property3 ), 
( 2, Property1, Property2, Property3 ), ( Property4, Property5, Property6 )
( 3, Property1, Property2, Property3 ), 
( 4, Property1, Property2, Property3 ), ( Property4, Property5, Property6 )

I did not expect this. I haven’t been able to explan how the “1” and “3” rows have returned, where item 1 still appears to be Nothing.

Finally, to combine them into a single string for each row, I’m doing

 /* combine the "left-side" and "right-side" columns into a merged row */

(concatenation ", " of (item 0 of it ; item 1 of it)) of
(
      /* the "left-side columns" of this emulated left-join */
 item 0 of it
       /* Find the "right-side columns" of this emulated left-join */
 ,  concatenation ", " of (it as string) of 
            /* from the "right side of the left join", keep only the elements with index greater than 0; here tuple item 0 is our "primary key" column, already present above, and not to be repeated in the final */
      tuple items whose (index of it > 0) of  
            /* from the match between item 0 ("left set") and matching elements from item 1 ("right set"), keep only the element from the right set */
      items 1 of 
           /* find the elements from the second set, where the first item, ie "column", matches this item */
      (item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it)
)
of
(
 elements of item 0 of it
 , item 1 of it
) 
of
(
  /* build the "left set" of the left join */
  set of (it as string) 
 of (
     "1","Property1","Property2","Property3"
    ;"2","Property1","Property2","Property3"
    ;"3","Property1","Property2","Property3"
    ;"4","Property1","Property2","Property3"
    )
   /* build the "right set" of the left join */
  , set of (it as string)
  of (
     "2","Property4","Property5","Property6"
    ;"4","Property4","Property5","Property6"
    )
)

which results in

1, Property1, Property2, Property3, 
2, Property1, Property2, Property3, Property4, Property5, Property6
3, Property1, Property2, Property3, 
4, Property1, Property2, Property3, Property4, Property5, Property6

** by the way, I’m running this in a 9.5.8 debugger because that’s what I had handy at the time. I haven’t upgraded the debugger on my home PC in quite a while :slight_smile:

1 Like

Very nice work guys. I will take this and try it with data from my actual use case. If get this working I will post my solution in another thread as it may have benefits to other users.

I think it is the Concatenation that is giving you that effect.

q: concatenation of (nothing as string)
A:

q: concatenation "|" of (nothing as string; "bob")
A: bob
T: 0.238 ms
I: singular string


q: concatenation "|" of (nothing as string; "bob";"bar")
A: bob|bar
T: 0.172 ms
I: singular string

q: exists (nothing as string)
A: False
T: 0.115 ms
I: singular boolean

q: exists concatenation of (nothing as string)
A: True
T: 0.066 ms
I: singular boolean

@JasonWalker, the solution you provided is so close to what I am needing. I am needing to access the individual items in Dataset 1 and 2 and reorder them. I was thinking I could do something like the following, but get an error stating “item of” is not a tuple. How can I reference the individual items?

(item 0 of item 0 of it, item 0 of item 1 of it)
of
(
item 0 of it
, concatenation ", " of (it as string)
of tuple items whose (index of it > 0) of items 1 of
(item 0 of it, elements of item 1 of it) whose (tuple string item 0 of item 0 of it = tuple string item 0 of item 1 of it)
)
of
(
elements of item 0 of it
, item 1 of it
)
of
(
set of (it as string)
of (
“1”,“Property1”,“Property2”,“Property3”
;“2”,“Property1”,“Property2”,“Property3”
;“3”,“Property1”,“Property2”,“Property3”
;“4”,“Property1”,“Property2”,“Property3”
)
, set of (it as string)
of (
“2”,“Property4”,“Property5”,“Property6”
;“4”,“Property4”,“Property5”,“Property6”
)
)

I’ll need to get back to my computer to test, but what we’ve done up to that point has converted our tuples into strings, with embedded ", " separating each item. With that comma-space combination we have a specially-formatted string, that we can reference with “tuple string items”.

Try

(tuple string item 0 of item 0 of it, tuple string item 0 of item 1 of it)

I tried that before and received an “Error: Singular expression refers to nonexistent object.” error. I just figured it out that I get that error when DataSet2 values are not present. I will wrap it in an if/then/else and that should resolve it. Thanks again.

1 Like

Sounds good. if/then/else should work, or you could also use the pipe operator for error handling…

(tuple string item 0 of item 0 of it | "First thing not found", tuple string item 0 of item 1 of it | "Second thing not found")

to substitute in an arbitrary value when not found, or maybe

(tuple string item 0 of item 0 of it | Nothing, tuple string item 0 of item 1 of it | Nothing)

to make it remove the result altogether

Or plurals to exclude the missing results

    (tuple string items 0 of items 0 of it, tuple string items 0 of items 1 of it)