views:

56

answers:

2

Hi,

I'm using custom objects to hold the name and schema from a set of SQL Server objects. I put the objects into an array, then I get another set of objects and put those into another array. What I'd like to do now is find all exact matches between the two arrays.

I'm currently using this:

$filteredSQLObjects = @()

foreach ($SQLObject1 in $SQLObjects1)
{
    foreach ($SQLObject2 in $SQLObjects2)
    {
        if ($SQLObject1.Name   -eq $SQLObject2.Name -and
            $SQLObject1.Schema -eq $SQLObject2.Schema)
        {
            $filteredSQLObjects += $SQLObject1
        }
    }
}

Is there a better/faster/cleaner way to do this? Originally when I was just working with arrays of strings I could just loop through one of the arrays and use -contains on the second, but with objects that doesn't seem possible.

Thanks!

+1  A: 

You could condense this to a one-liner which would be appropriate if you were writing this at the console:

$filtered = $SQLObjects1 | ? {$o1=$_; $SQLObjects2 | ? {$_.Name -eq $o1.Schema `
                                                   -and $_.Name -eq $o1.Schema}}

But in a script, I would expand it out like you have it. It is more readable that way.

Keith Hill
Thanks for the suggestion. This is in a script, and as you said it's more readable expanded out. It's always nice to see other ways to do things while I'm learning this language.
Tom H.
+1  A: 

I think its better if you define the equality condition in an IsEqualTo method on your custom object. So something like this:

$myObject = New-Object PSObject
$myObject | Add-Member -MemberType NoteProperty -Name Name -Value $name
$myObject | Add-Member -MemberType NoteProperty -Name Schema -Value $schema
$myObject | Add-Member -MemberType ScriptMethod -Name IsEqualTo -Value {
    param (
        [PSObject]$Object
    )

    return (($this.Name -eq $Object.Name) -and ($this.Schema -eq $Object.Schema))
}

Then you can either do a one-liner like Keith showed us, or just do your double foreach iteration. Whichever you think is more readable:

$filteredSQLObjects = $SQLObjects1 | Where-Object { $SQLObject1 = $_; $SQLObjects2 | Where-Object { $_.IsEqualTo($SQLOBject1) } }

foreach ($SQLObject1 in $SQLObjects1)
{
    foreach ($SQLObject2 in $SQLObjects2)
    {
        if ($SQLObject1.IsEqualTo($SQLObject2))
        {
            $filteredSQLObjects += $SQLObject1
        }
    }
}

EDIT

OK, for a start, you can't add an Equals member because it already exists on System.Object (doh!). So I guess IsEqualTo will have to do instead.

What you can do is define your own function called Intersect-Object (the equivalent of .NET's Enumerable.Intersect method) which accepts pipeline input and returns the set intersection of two sequences (the ones that appear in both sequences). Be aware that I haven't fully-implemented this function (assumes each item in the collection specified by Sequence has an IsEqualTo method, doesn't check for duplicates before adding to $filteredSequence etc), but I hope you get the idea.

function Intersect-Object
{
    param (
        [Parameter(ValueFromPipeline = $true)]
        [PSObject]$Object,
        [Parameter(Mandatory = $true)]
        [PSObject[]]$Sequence
    )

    begin
    {
        $filteredSequence = @()
    }

    process
    {
        $Sequence | Where-Object { $_.IsEqualTo($Object) } | ForEach-Object { $filteredSequence += $_ }
    }

    end
    {
        return $filteredSequence
    }
}

Then your double foreach loop turns into this:

$filteredSQLObjects = $SQLObjects1 | Intersect-Object -Sequence $SQLObjects2
George Howarth
That doesn't get rid of the double loop, but it's much cleaner. Thanks! Going to leave the question opened for a bit longer to see if there are any other solutions.
Tom H.
I edited my post to show another solution.
George Howarth
You can add an Equals. You just need to use the -force switch to overwrite the existing method. I found this out because that's what I did :)
Tom H.