Search code examples
sqlvisual-studiossissql-server-data-tools

How do I write an SSIS Expression to extract one folder name from a fully qualified file name


I have an SSIS package with an ForEach File Enumerate loop (Fully Qualified Name) with an FTP task within in.

The package when executed will go through the files in the subfolders within the following directory

C:\Test\Test2\ABC\

*.txt

And it will post the files to an FTP site.

I have a defined variable called @[User::Filename] within the foreach loop.

But there are folders within the FTP and I want the files to go to based on the Folder they are taken from on the C drive.

C:\Test\Test2\ABC\A\1.txt

C:\Test\Test2\ABC\B\2.txt

C:\Test\Test2\ABC\C\3.txt

File 1.txt should go to the FTP folder Called \FTP\A

File 2.txt should go to the FTP folder Called \FTP\B

File 3.txt should go to the FTP folder Called \FTP\C

My original thought was to make the remote path a variable and piggy back off the the foreach loop variable Fully qualified name.

To do this I created a variable called @[User::FilenameFTP] and inputted the following into the expression

"//FTP//" + 

 RIGHT(
       (LEFT(@[User::Filename], ABS((FINDSTRING(@[User::Filename], "//", 5)))), 

       ABS((FINDSTRING(@[User::Filename], "//", 5)-1)) - ABS((FINDSTRING(@[User::Filename], "//",4)+1))
      )

I thought this formula would give me the filename in the C drive which the file is coming from and I used this as the Remote Path variable within the FTP task. But when I run it the files still go into \FTP\ and not into the subfolders.

I ran a script task on this and the output isnt showing what I want either. What am I doing wrong? Can this not be done this way editing the variable within the foreach loop?


Solution

  • If your drive names are coming in (more or less) as you have them shown, then those should be backslashes ("\\") instead of forward slashes in your expression. Might not be the issue, but I changed them to play around with this.

    Using the C folder string, in the expression as written, ABS((FINDSTRING(@[User::Filename], "\\", 5)-1)) and ABS((FINDSTRING(@[User::Filename], "\\",4)+1)) both evaluate to 19, so the expression comes down to RIGHT(<<String>>,0), and, from the documentation, If integer_expression is zero, the function returns a zero-length string.. So you're not appending anything to the end of the FTP base folder name.

    Down and Dirty Fix

    We could probably mess around with all that LEFT and RIGHT and FINDSTRING, but if you know that the folder name you're after will always be the fifth element in your fully qualified name (which your expression is already dependent on) you can get there faster just using TOKEN, and specifying the fifth element of your slash-delimited string:

    "//FTP//" + TOKEN( @[User::Filename],"\\",5) +"//"
    

    Which evaluates to //FTP//C//.

    More Sustainable Fix

    On the other hand, if you want to future-proof your code a little, in anticipation of the day that you add or eliminate a level of folder hierarchy, I would suggest extracting the last folder name, without regard to how many levels of folder come first.

    We can do that using SUBSTRING and some clever REVERSE work, with due credit to KeithL for this answer, that got me rolling.

    SUBSTRING takes three arguments. We have our string @[User::Filename], so that's one. The second is the starting position from the left end of the string, and the third is the number of characters to extract.

    To get the starting position, we'll find the position of the second to last slash using REVERSE to count characters from the right hand end of the string:

    FINDSTRING(REVERSE( @[User::Filename]),"\\",2) (evaluates to 8 here)
    

    So our starting position is the total length of the string, minus the number of characters back to the second to last slash.

    LEN( @[User::Filename]) - FINDSTRING(REVERSE( @[User::Filename]),"\\",2) (=17)
    

    We can get the number of characters to pull by subtracting the reversed position of the last slash from the reversed position of the second to last slash, then subtracting one more, since we don't want that trailing slash in our string yet.

    FINDSTRING(REVERSE( @[User::Filename]),"\\",2) 
    - FINDSTRING(REVERSE( @[User::Filename]),"\\",1) - 1 (= 1 in our example)
    

    And there are our three arguments. Putting those all together with your base folder name (and I added a trailing slash. If that doesn't work for you, take it out of there!):

    "//FTP//" 
    + SUBSTRING( 
          @[User::Filename] ,
          LEN( @[User::Filename]) - FINDSTRING(REVERSE( @[User::Filename]),"\\",2),
          FINDSTRING(REVERSE( @[User::Filename]),"\\",2)
            -FINDSTRING(REVERSE( @[User::Filename]),"\\",1)-1 )
    + "//"
    

    Evaluates to //FTP//C//.

    Now, when the powers that be decide to "clean up" that source server, and the Test2 layer disappears all of a sudden, or things get crazy, and you bury this all one layer deeper, your code will still work.

    Side Note

    If you're really using drive letters in your file path names, like C:\, be aware that when you're running your packages locally, that's your C:\ drive the package is using, but when you deploy this, it'll be looking around on the server's C:\ drive, and it just might not like what it finds, or more likely, doesn't find there.