SQL Server FileStream - How to acquire files path - sql

I'm working on this application where I need to upload large data files to my SQL Server DB, and I've been using FileStream to do it more efficiently.
I understand the files are stored directly into my system's folders (C:\CryptoDB).
The thing is, I need to manipulate these files (decrypt them) but I haven't been able to recover their filepath.
Doing so, I would be able to manipulate them directly, not having to re-download them via SQL, which is a real waste.
What I've been able to do so far:
My table:
CREATE TABLE [arquivo] (
[idUsuario] INT NOT NULL,
[fileState] INT NOT NULL,
[fileContent] varbinary(max) FILESTREAM,
[fileName] VARCHAR (150) NULL,
[fileSize] VARCHAR (50) NULL,
id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE,
CONSTRAINT [FK_arquivo_usuario] FOREIGN KEY ([idUsuario]) REFERENCES usuario(id)
);
Insert:
Insert into arquivo(id, idUsuario, fileState, fileContent, fileName, fileSize) Values(
newId(),
1,
5,
(SELECT * FROM OPENROWSET(BULK 'c:\medio.jpeg', SINGLE_BLOB) AS varbinary(max)) ,
'medio.jpeg',
'123'
)
And when I try to recover the filepath:
DECLARE #filePath varchar(max)
SELECT #filePath = fileContent.PathName()
FROM arquivo
PRINT #filepath
The result I get:
\\TEHORT-PC\MSSQLSERVER\v02-A60EC2F8-2B24-11DF-9CC3-AF2E56D89593\CryptoDB\dbo\arquivo\fileContent\31E3697E-0576-4B0F-B0AA-6E046F4116A1\VolumeHint-HarddiskVolume2
Where the file actually is:
C:\CryptoDB\DATA\902a7d8d-c8c1-43b0-8c94-b12319293f42\7febdbd1-02c6-4b00-aa3c-a72bee80ef9c\

SQL query for get physical location of all FILESTREAM data (source)
SELECT t.name AS 'table',
c.name AS 'column',
fg.name AS 'filegroup_name',
dbf.type_desc AS 'type_description',
dbf.physical_name AS 'physical_location'
FROM sys.filegroups fg
INNER JOIN sys.database_files dbf
ON fg.data_space_id = dbf.data_space_id
INNER JOIN sys.tables t
ON fg.data_space_id = t.filestream_data_space_id
INNER JOIN sys.columns c
ON t.object_id = c.object_id
AND c.is_filestream = 1
All FILESTREAM BLOB-fields query result sample
SQL query for get subfolders for FILESTREAM data on server:
(These tables only used within dedicated Administrator connection (DAC)).
SELECT o.name AS [Table], cp.name AS [Column], r.rsguid AS [Rowset GUID], rs.colguid AS [Column GUID] FROM SYS.SYSROWSETS r CROSS APPLY sys.sysrscols rs
JOIN sys.partitions p ON rs.rsid = p.partition_id
JOIN sys.objects o ON o.object_id = p.object_id
JOIN sys.syscolpars cp ON cp.colid = rs.rscolid
WHERE rs.colguid IS NOT NULL AND o.object_id = cp.id AND r.rsguid IS NOT NULL AND r.rowsetid = rs.rsid AND o.name = 'DOCUMENT' and cp.name = 'DIGITAL_FILE';
2.1. Query result:
Table: DOCUMENT
Column: DIGITAL_FILE
Rowset GUID: 0x6AA5E6045794D34D8B1FAC0F49A49B0A
Column GUID: 0xD756E638FB2CC843AE98F489B57F6D7D
Calculating Sub-Path from this guids:
0x6AA5E6045794D34D8B1FAC0F49A49B0A equals this path:
04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a
[reversed 6AA5E604]-[reversed 5794]-[reversed D34D]-[reversed 8B1F]-[original AC0F49A49B0A]
0xD756E638FB2CC843AE98F489B57F6D7D equals this path:
38e656d7-2cfb-43c8-ae98-f489b57f6d7d (rules in previus guid parsing)
2.2 Result calculated full path for FILESTREAM storage:
i:\SQL Base posc_astrachan FileStreams\GTMK\GTM_FILE_STREAM\04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a\38e656d7-2cfb-43c8-ae98-f489b57f6d7d
Get original filename for BLOB-value in NTFS-folder.
3.1. Stored procedure for query advanced SQL Server Page info
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[procDBCC_PAGE]
#db_name varchar (500),
#filenum INT,
#pagenum INT
AS
BEGIN
SET NOCOUNT ON
DBCC TRACEON (3604);
DBCC PAGE (#db_name, #filenum, #pagenum, 3) WITH TABLERESULTS;
SET NOCOUNT OFF
END
3.2. Stored procedure for query original filename for FILESTREAM'ed BLOB-field of table
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[procFindLogSequenceNumber]
-- #TableName varchar (500),
#instanceS varchar (19), -- key value for filed INSTANCE_S
#tableName varchar(500), -- DOCUMENT
#keyFieldName varchar(500), -- INSTANCE_S
#LogSequenceNumber varchar (500) OUTPUT
AS
SET NOCOUNT ON
DECLARE #db_name varchar (500)
DECLARE #filenum INT
DECLARE #pagenum INT
DECLARE #slotnum INT
DECLARE #rid varchar (100)
DECLARE #ridDotted varchar (100)
DECLARE #parent_object varchar (500)
DECLARE #sql nvarchar(2000)
DECLARE #sqlTable Table(physloc varchar(100))
DECLARE #DBCC_PAGE_Output Table ([ParentObject] varchar (MAX), [Object] varchar (MAX), [Field] varchar (MAX), [VALUE] varchar (MAX))
SET #db_name = db_name()
SET #sql = 'SELECT top 1 sys.fn_PhysLocFormatter (%%physloc%%) AS [PhysicalRID] FROM '+#tableName+' WHERE '
+#keyFieldName+' = '''+#instanceS+''''
INSERT #sqlTable (physloc)
EXECUTE sp_executesql #sql
SET #rid = (select top 1 physloc from #sqlTable)
if #rid is NULL
BEGIN
RETURN -1;
END
-- parse (#rid): (1:1172779:6) 1-#filenum, 2- #pagenum, 3- #slotnum
SET #ridDotted = Replace(#rid, ':', '.');
SET #ridDotted = Replace(#ridDotted, '(', '');
SET #ridDotted = Replace(#ridDotted, ')', '');
SET #filenum = (SELECT Parsename(#ridDotted, 3))
SET #pagenum = (SELECT Parsename(#ridDotted, 2))
SET #slotnum = (SELECT Parsename(#ridDotted, 1))
INSERT #DBCC_PAGE_Output ([ParentObject], [Object], [Field], [VALUE])
EXECUTE procDBCC_PAGE #db_name, #filenum , #pagenum
SET #parent_object = (SELECT TOP 1 [ParentObject] FROM #DBCC_PAGE_Output WHERE [Field] = 'INSTANCE_S'
AND [VALUE] = #instanceS)
--CreateLSN field Only
SET #LogSequenceNumber = (SELECT [VALUE] FROM #DBCC_PAGE_Output WHERE
[ParentObject] = #parent_object AND
[Field] = 'CreateLSN'
)
if #LogSequenceNumber is NULL
BEGIN
RETURN -1;
END
-- result 0006c050:00000120:0090 (442448:288:144)
-- clear (...)
SET #LogSequenceNumber = Replace(#LogSequenceNumber, ' ', '.');
SET #LogSequenceNumber = (SELECT Parsename(#LogSequenceNumber, 2))
--replace ":" to "-"
SET #LogSequenceNumber = Replace(#LogSequenceNumber, ':', '-');
SET NOCOUNT OFF
3.3. Sample query for stored procedure for get filename on NTFS folder for BLOB:
declare #filestreamFileName varchar(500);
exec procFindLogSequenceNumber 'ZW_NU9hGZ0CKoSXYAoc', 'DOCUMENT', 'INSTANCE_S', #filestreamFileName OUTPUT
select #filestreamFileName
3.4. Result (original file name in NTFS folder):
0003137a-00001244-00d0
3.5. Result full path:
i:\SQL Base posc_astrachan FileStreams\GTMK\GTM_FILE_STREAM\04e6a56a-9457-4dd3-8b1f-ac0f49a49b0a\38e656d7-2cfb-43c8-ae98-f489b57f6d7d\0003137a-00001244-00d0

The path you get is correct, you are supposed to get the network share path not the local path and use SqlFileStream to open a stream.
https://msdn.microsoft.com/en-us/library/system.data.sqltypes.sqlfilestream%28v=vs.110%29.aspx
You could also get a file handle using OpenSqlFilestream method, i.e. to use it in Windows API.
https://msdn.microsoft.com/en-us/library/bb933972.aspx

Alexander's answer is great, saved me a LOT of trouble correlating page/slot numbers to actual LSNs. In my case using SQL Server 2008 R2 I had to make some adjustments to his SP to get it working, those were:
The SPs first parameter:
#instanceS varchar (100), -- key value for filed INSTANCE_S
In my case this is a uniqueidentifier so I needed a bigger varchar, original value was 19.
When querying DBCC PAGE Output:
SET #parent_object = (SELECT TOP 1 [ParentObject] FROM
#DBCC_PAGE_Output WHERE [Field] = #keyFieldName AND [VALUE] =
#instanceS)
It originaly stated "[Field] = 'INSTANCE_S'" which apparently hardcoded a value that worked for OP but not for me. It needed to match the name of the FILESTREAM table's key field.
Also to clarify the SPs input params a bit:
#instanceS = The actual column value which identifies the row. Will
this always match the column set as the table's "RowGuid"?
#tableName = Pretty clear. The FILESTREAM table's name.
#keyFieldName = The name of the table's key column. Should be the
source column from where #instanceS was taken.

Related

SQL script for updating SQL Extended Properties with a Cursor

I have a task of updating the tables in a database which are missing Extended Properties for 'Category' and 'Description'. The script below is what I am currently trying to implement, but it doesn't function as I'd hoped it would.
The first part of the script successfully returns the tables which have NULL or missing values for 'Category' and 'Description'. These results are used by a cursor to iterate through each table so I can perform an operation on them. For each table in the cursor, I want to retrieve the correct values from TemporaryTable which contains the correct 'Category' and 'Description'. Then, pass those correct values to the EXEC statements to update or create the Extended Property. The only thing that doesn't seem to work is that the variables 'categ' and 'descr' don't get assigned the values in the second SELECT statement where I expected them to.
I've tried as many combinations as I can think of to get these values into the variables, but none seem to work for me. Hopefully somebody can point out where I am going wrong.
Thanks in advance for your suggestions.
Few variables declared for use in this script
*/
DECLARE #tablename VARCHAR(100)
DECLARE #categ VARCHAR(50)
DECLARE #descr VARCHAR(MAX)
DECLARE myCursor CURSOR FOR
/*
This SELECT/FROM returns all table names which have
a NULL value for either category or description. This statement correctly
returns the subset of tables.
*/
SELECT
t.name AS tablename
FROM sys.tables t
LEFT JOIN sys.extended_properties AS description
ON t.object_id = description.major_id
AND description.minor_id = 0
AND description.name = 'MS_Description'
LEFT JOIN sys.extended_properties AS category
ON t.object_id = category.major_id
AND category.minor_id = 0
AND category.name = 'Table_Category'
WHERE type = 'U'
AND (category.value IS NULL OR description.value IS NULL)
AND t.name NOT LIKE 'temp%'
AND t.name NOT LIKE 'tmp%'
AND t.name NOT LIKE '%_BAK'
OPEN myCursor
FETCH NEXT FROM myCursor INTO #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
/*
This SELECT/FROM/WHERE takes information (correct extended properties) about all
cursor tables from a temporary table.
The purpose is to update the NULL (or not present) extended properties in the subset of tables
of the cursor.
The [Table Name], tp.Category, and tp.Description are correctly output in the results, but the assignment
to the variables categ, and descr (which I want to use in the EXEC statement for updating properties)
doesn't seem to happen.
*/
SELECT
tp.[Table Name],
tp.Category AS categ,
tp.Description AS descr
FROM TemporaryTable tp
WHERE #tablename = tp.[Table Name]
--If category ext_prop doesn't exist, add it
IF NOT EXISTS (SELECT NULL FROM SYS.EXTENDED_PROPERTIES WHERE [major_id] = OBJECT_ID(#tablename) AND [name] = N'Table_Category' AND [minor_id] = 0)
EXEC sys.sp_addextendedproperty #name=N'Table_Category', #value=N#categ , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N#tablename
--Else, update extended property
ELSE
EXEC sys.sp_updateextendedproperty #name=N'Table_Category', #value=N#categ , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N#tablename
--If description ext_prop doesn't exist, add extended property
IF NOT EXISTS (SELECT NULL FROM SYS.EXTENDED_PROPERTIES WHERE [major_id] = OBJECT_ID(#tablename) AND [name] = N'MS_Description' AND [minor_id] = 0)
EXEC sys.sp_updateextendedproperty #name=N'MS_Description', #value=N#desc , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N#tablename
--If Not Exists, add extended property
ELSE
EXEC sys.sp_addextendedproperty #name=N'MS_Description', #value=N#desc , #level0type=N'SCHEMA',#level0name=N'dbo', #level1type=N'TABLE',#level1name=N#tablename
PRINT #categ
FETCH NEXT FROM myCursor INTO #tablename
END
CLOSE myCursor
DEALLOCATE myCursor
I don't see any trace of variable assignments, just aliases.
To assign a value to a variable you must either use
SET #varname = value or query
or you can do it using a select statement like
SELECT
#categ = tp.Category,
#descr = tp.Description
FROM TemporaryTable tp
WHERE #tablename = tp.[Table Name]

How to execute exceeded length query

The character string that starts with '"0111" OR "0112" OR "0115" OR ' is too long. Maximum length is 4000.
declare #stmt1 varchar(max)
set #stmt1='insert into dbo.abcd SELECT [company],[address],
[address2],[zip],[zip4],[city],[state],
[telephone_number],LOWER([email]),[name],[fname],[mname],[lname],
[title_full] FROM DBO.filter WHERE
[id] in (''7'',''8'',''11'',''15'') and contains
(code,'+''''+'"0111" OR "4142"......................OR
"5999"'+''''+')'
exec (#stmt1) AT [server]
Try this:
declare #stmt1 varchar(max)
set #stmt1=CAST('insert into dbo.abcd SELECT [company],[address],
[address2],[zip],[zip4],[city],[state],
[telephone_number],LOWER([email]),[name],[fname],[mname],[lname],
[title_full] FROM DBO.filter WHERE
[id] in (''7'',''8'',''11'',''15'') and contains
(code,' AS VARCHAR(MAX))+''''+'"0111" OR "4142"......................OR
"5999"'+''''+')'
exec (#stmt1) AT [server]
Assuming that your codes list are stored in a table, say 'dbo.codes', with a field 'code' containing the value, you can avoid the CONTAINS predicate and use an INNER JOIN between the two tables using LIKE.
DECLARE #stmt1 VARCHAR(MAX)
SET #stmt1 = 'INSERT INTO dbo.abcd
SELECT DISTINCT [F].[company], [F].[address], [F].[address2], [F].[zip], [F].[zip4],
[F].[city], [F].[state], [F].[telephone_number], LOWER([F].[email]),
[F].[name], [F].[fname], [F].[mname], [F].[lname], [F].[title_full]
FROM dbo.filter AS F INNER JOIN dbo.codes AS C ON F.code LIKE ''%'' + C.code + ''%''
WHERE (F.id in (''7'',''8'',''11'',''15''))';
EXEC (#stmt1) AT [server]

SQL Server replace function inside of merge operation

I want to match the temp variable to my table column to merge its contents, but my temp variable contains a letter and I need to remove it to match the two columns from my table. I dont know what is the right code for this.
This is the content of the temp table
F20151103-1010|FSI|FRANCISCA MANALO BISCOCHO|DELIVERED|42317
F20151103-1019|FSI|NINA MARTINNE HAMOY CAJAYON|DELIVERED|42314
CREATE TABLE #records(
[index] int PRIMARY KEY IDENTITY
,refnum varchar(200)
,stat varchar(200)
,statdate varchar(200)
)
insert into #records (refnum, stat, statdate)
select
dbo.fn_Parsename(WHOLEROW,'|',0),
dbo.fn_Parsename(WHOLEROW,'|',3),
dbo.fn_Parsename(WHOLEROW,'|',4)
from #temp1
declare #refnum varchar(100)
declare #stat varchar(100)
declare #statdate varchar(100)
BEGIN
WHILE (#index <= (SELECT MAX([index]) FROM #records))
set #sql = '
MERGE gen_048_MAR2016 target
USING #records source
ON target.refdate'+'-'+'target.refcount = source.'+replace(refnum, 'F', '')+'
WHEN MATCHED THEN
UPDATE
SET
target.stat = source.stat
WHEN NOT MATCHED BY TARGET THEN
INSERT (stat, statdate)
VALUES (source.stat, source.statdate)
;'
select #refnum, #stat, #statdate
print #sql
exec (#sql)
SELECT 'File has been successfully uploaded', #fileDate,'success' as msg
set #index = #index + 1
END
ERROR:
Conversion failed when converting the varchar value 'F20160323-1000' to data type int.
Could you try replacing
'+'-'+' with +''-''+
set #sql = '
MERGE gen_048_MAR2016 target
USING #records source
ON target.refdate+''-''+target.refcount = source.'+replace(refnum, 'F', '')+'
WHEN MATCHED THEN
UPDATE
SET
target.stat = source.stat
WHEN NOT MATCHED BY TARGET THEN
INSERT (stat, statdate)
VALUES (source.stat, source.statdate)
;'
EDIT:
target.refdate'+'-'+'target.refcount is not contactination.
You are subtracting the 2 columns.
Why not just add the 'F' to the other side?
set #sql = '
MERGE gen_048_MAR2016 target
USING #records source
ON ''F''+target.refdate+''-''+target.refcount = source.refnum
WHEN MATCHED THEN
UPDATE
SET
target.stat = source.stat
WHEN NOT MATCHED BY TARGET THEN
INSERT (stat, statdate)
VALUES (source.stat, source.statdate)
;'

Validation if you're using the correct DB

Is there a way to have a validation script before continuing a script?
So
DECLARE #strServernameToClear as VarChar(100)
DECLARE #strCurrentServer as VarChar(100)
DECLARE #strDatabaseToClear as VarChar (100)
DECLARE #strDatabase as VarChar (100)
SET #strDatabase = (SELECT ##database) << THIS IS WHERE IM HAVING TROUBLE. How to indicate which database it is running against
IF #strCurrentServer <> #strServernameToClear
BEGIN
PRINT '*****************WARNING *****************'
PRINT 'WRONG DB NAME: This server is: ' + #strCurrentServer
PRINT 'If this is the correct server, set #strServernameToClear to ' + #strCurrentServer
RETURN
END
ELSE
EXECUTE ORDER 66
I think this'll work for you:
select #strDatabase = d.name
from sys.sysprocesses p
join sys.databases d on d.database_id = p.dbid
where p.spid = ##SPID

How to replace text in a string with values from a column in sql [duplicate]

This question already has an answer here:
How to replace a string with values from columns in a table in SQL
(1 answer)
Closed 9 years ago.
Exp Major Start
__________________________________________________________
| |
'My names are W.Major and W.Start' | Hal | Bark
___________________________________|________|_________________
'W.Major is a doctor' | Mark | Slope
___________________________________|________|_______________
Hi All suppose I have the table above in SQL server management studio
and for any text in the Exp column I want to replace W.Major with the value in the Major column and wherever there is a W.Start I want to replace it with the value in the Start column.
Do you know what type of SP I have to write to get this accomplished?
Well you can use a Dynamic SQL and UNPIVOT to get table with all unit replacements and then you run a while loop to replace expressions one by one till you get your result.
I am sure there can be better techniques but here is my solution.
Please mark it as answer for my effort :)
IF OBJECT_ID ('tempdb.dbo.#temptable') IS NOT NULL DROP TABLE #temptable
CREATE TABLE #tempTable ( ID INT IDENTITY(1, 1), [Exp] nvarchar(4000) not NULL, replacementWord nvarchar(50) not null, Wordvalue nvarchar(50) not null, flag bit null, ReplacedExp nvarchar(4000) null)
DECLARE #query VARCHAR(4000)
DECLARE #queryRWords VARCHAR(2000)
SELECT #queryRWords =
STUFF((select DISTINCT '],['+ LTRIM(C.name) from sys.Columns C INNER JOIN sys.objects O on O.object_id=C.object_id where O.name='yourtable' and O.type_desc='USER_TABLE' and C.name not like 'Exp' ORDER BY '],['+LTRIM(C.name) FOR XML PATH('') ),1,2,'') + ']'
SET #query='INSERT INTO #tempTable(Exp, replacementWord, WordValue) select [Exp], [replacementWord],[WordValue] FROM (SELECT [Exp], [major],[start] FROM [yourtable]) p
UNPIVOT([Wordvalue] FOR [replacementWord] IN ('+#queryRWords+'))AS unpvt'
EXECUTE(#query)
UPDATE #tempTable SET ReplacedExp=[Exp]
DECLARE #ExpCount INT
SELECT #ExpCount= COUNT(*) FROM #tempTable
WHILE #ExpCount >0
BEGIN
IF((SELECT [Exp] From #tempTable where Id=#ExpCount)<>(SELECT [Exp] from #tempTable where Id=(#ExpCount-1)))
BEGIN
UPDATE #tempTable SET FLAG=1, ReplacedExp= REPLACE(ReplacedExp, CAST('W.'+replacementWord AS VARCHAR), WordValue) FROM #tempTable WHERE Id=#ExpCount
END
ELSE
BEGIN
UPDATE #tempTable SET ReplacedExp= REPLACE(ReplacedExp, CAST('W.'+replacementWord AS VARCHAR), WordValue) FROM #tempTable WHERE Id=#ExpCount
UPDATE #tempTable SET ReplacedExp= (SELECT ReplacedExp FROM #temptable where Id=#ExpCount) WHERE Id=(#ExpCount-1)
END
SET #ExpCount=#ExpCount-1
END
UPDATE #tempTable
SET flag=1 where Id=1
SELECT ReplacedExp FROM #tempTable where flag=1
Here is the sample TSQL where you can have a good start
SELECT Exp, Major, Start
, Replace(Replace(Exp, 'W.Major', Major), 'W.Start', Start) As Result
FROM [Your Table Name]