I have a table City; my Pic column datatype is varbinary(max):
I update my pic column with this code and work (for 1 pic):
UPDATE City
SET pic = 0xwed323422222222....
WHERE id = 4
but how can I update 3 images or more in Pic column? (add to existing image)
It my means I stored in table where id = 4 just 1 image binary. How can I save more than 1 image in a single column?
You could store multiple images in a "semi-structured" way, within a single column by leveraging xml (and its structural integrity). You could change the datatype of the "pic" column to xml or keep it varbinary (some type casting is required for the latter).
The following example assumes varbinary storage for "the image" column and a "c:\testnew" folder (on the sql instance) which contains 3 images (image[1..3].png).
First, the two images are loaded for "the image" column of cityid=1 and later on the third image is appended to the binary data(first 2 pics) with update .write().
Removing images or inserting images in specific place within the blob could be achieved by utilizing the xml.modify() method.
All this, if you really need/have to store multiple images in a single row&column.
create table dbo.TestImages
(
id int identity(1,1) constraint pkidTestImages primary key clustered(id),
CityId int,
ImagesBlobXml varbinary(max) --or xml
)
go
--insert two images from folder c:\testnew
insert into dbo.TestImages
(
CityId, ImagesBlobXml
)
values (
1,
cast((
select TheImage
from
(
select *
from openrowset(bulk N'C:\testnew\image1.png', single_blob) as i(TheImage)
union all
select *
from openrowset(bulk N'C:\testnew\image2.png', single_blob) as i(TheImage)
--union all
--select *
--from openrowset(bulk N'C:\testnew\stackoverflow.png', single_blob) as i(TheImage)
) as images
for xml path(''), type
) as varbinary(max))
);
select 'table content', *
from dbo.TestImages;
--retrieve images (2)
select 'images in blob, initial insert', t.id, t.CityId, i.bin.value('.', 'varbinary(max)') as TheImage
from
(
select *, cast(ImagesBlobXml as xml) as ImagesBlobXmlXML
from dbo.TestImages
) as t
cross apply t.ImagesBlobXmlXML.nodes('TheImage') as i(bin);
--append new image
update t
set ImagesBlobXml .WRITE( --note:write cannot be used on NULL values
cast((
select TheImage
from
(
select *
from openrowset(bulk N'C:\testnew\image3.png', single_blob) as i(TheImage)
) as images
for xml path(''), type
) as varbinary(max)) ,
null, 0 --write() append
)
from dbo.TestImages as t
where CityId = 1;
--retrieve the images (3)
select 'images in blob, after update_append', t.id, t.CityId, i.bin.value('.', 'varbinary(max)') as TheImage
from
(
select *, cast(ImagesBlobXml as xml) as ImagesBlobXmlXML
from dbo.TestImages
) as t
cross apply t.ImagesBlobXmlXML.nodes('TheImage') as i(bin);
--check for any diff
select i.bin.value('.', 'varbinary(max)') as TheImage
from
(
select *, cast(ImagesBlobXml as xml) as ImagesBlobXmlXML
from dbo.TestImages
) as t
cross apply t.ImagesBlobXmlXML.nodes('TheImage') as i(bin)
except
select TheImage
from
(
select *
from openrowset(bulk N'C:\testnew\image1.png', single_blob) as i(TheImage)
union all
select *
from openrowset(bulk N'C:\testnew\image2.png', single_blob) as i(TheImage)
union all
select *
from openrowset(bulk N'C:\testnew\image3.png', single_blob) as i(TheImage)
) as images;
go
--cleanup
drop table dbo.TestImages;
go
Related
I have a query that returns a row
SELECT *
FROM table
WHERE id = 1;
I want to save the result into a nvarchar sql variable. I have seen similar questions Convert SQL Server result set into string but they only use select with the name of the columns, never with *.
select *
from table
where id = 1
for xml path ('')
However the answer is <column1>value1</column1> <column2>value2</column2> and I just want it to be value1, value2
Is there a way to achieve this? thank you!
If open to a helper function.
This will convert virtually any row, table or query to a string (delimited or not).
In the following examples I selected a PIPE delimiter with a CRLF line terminator.
Please note the usage and placement of _RN when a line terminator is required. Also note the ,ELEMENTS XSINIL ... this will included null values as empty string. If you want to exclude null values, simply omit the ,ELEMENTS XSINIL
Example as Entire Table or dbFiddle
Declare #YourTable Table (id int,[col_1] varchar(50),[col_2] varchar(50),[col_3] varchar(50),[col_n] varchar(50)) Insert Into #YourTable Values
(1,'data1','data2','data3','data4')
,(2,'data5','data6','data7','data8')
-- Entire Table
Declare #XML xml = (Select *,_RN=Row_Number() over (Order By (Select null)) From #YourTable for XML RAW,ELEMENTS XSINIL )
Select [dbo].[svf-str-Data-To-Delimited]('|',char(13)+char(10),#XML)
Returns
1|data1|data2|data3|data4
2|data5|data6|data7|data8
Example as Row Based
Select A.ID
,AsAString = [dbo].[svf-str-Data-To-Delimited]('|',char(13)+char(10),B.XMLData)
From #YourTable A
Cross Apply ( values ( (select a.* for xml RAW,ELEMENTS XSINIL )) )B(XMLData)
Returns
ID AsAString
1 1|data1|data2|data3|data4
2 2|data5|data6|data7|data8
The Function if Interested
CREATE Function [dbo].[svf-str-Data-To-Delimited] (#Delim varchar(50),#EOL varchar(50),#XML xml)
Returns varchar(max)
Begin
Return(
Select convert(nvarchar(max),(
Select case when Item='_RN' then ''
else case when nullif(lead(Item,1) over (Order by Seq),'_RN') is not null
then concat(Value,#Delim)
else concat(Value,#EOL)
end
end
From (
Select Seq = row_number() over(order by (select null))
,Item = xAttr.value('local-name(.)', 'nvarchar(100)')
,Value = xAttr.value('.','nvarchar(max)')
From #XML.nodes('/row/*') xNode(xAttr)
) A
Order By Seq
For XML Path (''),TYPE).value('.', 'nvarchar(max)') )
)
End
You can easily store the result as an XML string:
select *
from (values (1, 'x', getdate())) v(id, a, b)
where id = 1
for xml path ('');
Or as a JSON string:
select *
from (values (1, 'x', getdate())) v(id, a, b)
where id = 1
for json auto;
If you don't mind Using dynamic SQL (and INFORMATION_SCHEMA dictionary), for example, for SQL Server this works:
DECLARE #sql nvarchar(max) = '',
#result nvarchar(max),
#id int = 1
SELECT #sql += '+'',''+convert(nvarchar,' + QUOTENAME(column_name) +')' from INFORMATION_SCHEMA.columns where table_name = 'Student'
SET #sql = 'select #result=' + stuff(#sql,1,5,'') + ' from student where id = ' + CAST(#id as nvarchar)
EXECUTE sp_executesql #sql, N'#result nvarchar(max) OUTPUT', #result=#result OUTPUT
SELECT #result as MyOutput
I have the following query setup, that works with no problem:
WITH XmlFile (xmlData) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'D:\Timewise\results.xml', SINGLE_BLOB) AS x
)
INSERT INTO [dbo].[Timewise] ([Title], [IP])
SELECT c.value('(Title/text())[1]', 'VARCHAR(25)') AS [NameSite]
, 'video' as [JournalUserName]
, '' as [JournalPassword]
, c.value('(IP/text())[1]', 'VARCHAR(25)') AS [IPAddress]
, '4910' as [Port]
, 'video' as [VideoUserName]
, '' as [VideoPassword]
, c.value('(IP/text())[1]', 'VARCHAR(25)') AS [VideoIP]
, '4910' as [VideoPort]
, '1' as [TotalCams]
, '1' as [TotalVideoWindows]
, 'true' as [IsRemote]
, 'false' as [IsDefaultSite]
,'' as [hasTwoDifferentUsers]
, '' as [DVRType]
, 'false' as [IsRemoteLiveDataRunning]
FROM XmlFile CROSS APPLY xmlData.nodes('DocumentElement/Server_x0020_List') AS t(c);
The next issue is that some of these nodes contain data from another xml file, located at D:\Timewise\JournalBrowserSiteSettings.xml
I tried to do some research on these, to get an example, and while I have the WITH statement covered:
WITH XmlFile (xmlData) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'D:\Timewise\results.xml', SINGLE_BLOB) AS x
)
,
XmlFile2 (xmlData) AS
(
SELECT CAST(BulkColumn AS XML)
FROM OPENROWSET(BULK 'D:\Timewise\JournalBrowserSiteSettings.xml', SINGLE_BLOB) AS y
)
I honestly have no idea how to pull this data (namely the TotalCams,TotalVideoWIndows,IsRemote, hasTwoDIfferentUsers, and DVRType) from the other file, and now almost 2 hours of searching has given me nothing to really go on.
It seems that you know how to get XML into SQL tables.
How about getting the XML files into two different tables and then joining the tables based on a common key, e.g. Title?
That will work. Won't it?
I have some Data stored as XML in SQL Server that looks as follows:
<FormSearchFilter>
.......
<IDs>
<int>1</int>
<int>2</int>
</IDs>
.......
</FormSearchFilter>
This XML is mapped to a DTO and the data type for IDs is changing from a List to a string. As a result I now need to updae all existing XML: data to look as follows:
<FormSearchFilter>
.......
<IDs>1,2</IDs>
.......
</FormSearchFilter>
Whats the best way to achieve this via an update query
Besides the hint, that this is a very bad idea! you might try something like this:
DECLARE #t TABLE(
Id INT NOT NULL IDENTITY(1,1),
xml XML)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
UPDATE #t
SET [xml]= (SELECT REPLACE([xml].query('data(/FormSearchFilter/IDs/int)').value('.','nvarchar(max)'),' ',',') AS IDs
FOR XML PATH('FormSearchFilter'));
SELECT * FROM #t
Explanation:
XQuery function data() will return alle text() nodes (in your case the int values) separated by a blank. This can be replaced with a comma to get the list needed.
UPDATE: Preserve other elements (be aware, that the order changes)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><test>x</test><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs><test>x</test></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
UPDATE #t
SET [xml]= (SELECT [xml].query('/FormSearchFilter/*[local-name()!="IDs"]') AS [*]
,REPLACE([xml].query('data(/FormSearchFilter/IDs/int)').value('.','nvarchar(max)'),' ',',') AS IDs
FOR XML PATH('FormSearchFilter'));
SELECT * FROM #t
A bit of a hack, and if you're open to a helper Table-Valued Function.
Example
Declare #XML xml = '
<FormSearchFilter>
<OtherContent>Some Content</OtherContent>
<IDs>
<int>1</int>
<int>2</int>
</IDs>
<IDs>
<int>11</int>
<int>12</int>
<int>13</int>
</IDs>
<IDs>
<int>99</int>
</IDs>
<MoreContent>Some MORE Content</MoreContent>
</FormSearchFilter>
'
Select #XML = replace(cast(#XML as varchar(max)),RetVal,NewVal)
From (
Select *
,NewVal = stuff(replace(replace(RetVal,'<int>',','),'</int>',''),1,1,'')
From [dbo].[tvf-Str-Extract](cast(#XML as varchar(max)),'<IDs>','</IDs>')
) A
Select #XML
Returns
<FormSearchFilter>
<OtherContent>Some Content</OtherContent>
<IDs>1,2</IDs>
<IDs>11,12,13</IDs>
<IDs>99</IDs>
<MoreContent>Some MORE Content</MoreContent>
</FormSearchFilter>
The TVF was created because I tired of extracting content (left,right,charindex,patindex,reverse,...). It is a modifed parse/split function which accepts two non-like delimiters. Just to illustrate, if you were to run:
Select * From [dbo].[tvf-Str-Extract](cast(#XML as varchar(max)),'<IDs>','</IDs>')
The results would be
RetSeq RetPos RetVal
1 65 <int>1</int><int>2</int>
2 100 <int>11</int><int>12</int><int>13</int>
3 150 <int>99</int>
The TVF if Interested
CREATE FUNCTION [dbo].[tvf-Str-Extract] (#String varchar(max),#Delimiter1 varchar(100),#Delimiter2 varchar(100))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 N1,cte1 N2,cte1 N3,cte1 N4,cte1 N5,cte1 N6) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter1) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter1)) = #Delimiter1),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter1,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By N)
,RetPos = N
,RetVal = left(RetVal,charindex(#Delimiter2,RetVal)-1)
From (
Select *,RetVal = Substring(#String, N, L)
From cte4
) A
Where charindex(#Delimiter2,RetVal)>1
)
/*
Max Length of String 1MM characters
Declare #String varchar(max) = 'Dear [[FirstName]] [[LastName]], ...'
Select * From [dbo].[tvf-Str-Extract] (#String,'[[',']]')
*/
Not particularly elegant but does end up with the required output:
DECLARE #t TABLE(
Id INT NOT NULL IDENTITY(1,1),
xml XML)
INSERT INTO #t(xml)
VALUES
('<FormSearchFilter><IDs><int>1</int><int>2</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int></IDs></FormSearchFilter>'),
('<FormSearchFilter><IDs><int>1</int><int>2</int><int>3</int><int>4</int></IDs></FormSearchFilter>');
DECLARE #updates TABLE(
Id INT,
UpdatedValue XML
)
INSERT INTO #updates
SELECT
Id,
(SELECT STUFF((
SELECT
',' + c.value('.', 'varchar')
FROM #t t1
CROSS APPLY t1.xml.nodes('//IDs/int') x(c)
WHERE t1.Id = t.Id
FOR XML PATH('')
), 1, 1, '') IDs
FOR XML PATH(''))
FROM #t t
-- remove existing IDs node
UPDATE #t
SET xml.modify('delete //IDs')
-- insert updated IDs node back in
UPDATE t
SET xml.modify('insert sql:column("u.UpdatedValue") into (/FormSearchFilter)[1]')
FROM #t t
JOIN #updates u ON t.Id = u.Id
I am using CTE to convert xml to csv so that it can be exported to a file, however if I have an empty xml tag, this currently gets ignored.
Here is my initial solution courtesy of this previous very helpful post:
https://stackoverflow.com/a/23785202/6260721
Here is my sql:
CREATE TABLE EXPORT_TEST
(
DATA varchar(max)
)
INSERT INTO EXPORT_TEST (DATA)
VALUES ('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
DECLARE #commaSeparatedValues NVARCHAR(MAX)
DECLARE #xml XML = (SELECT TOP 1 CONVERT(xml,DATA) FROM EXPORT_TEST)
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('//text()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
SELECT * FROM EXPORT_TEST
At the moment, it is returning:
'ABC123',5.6,8.1
But I don't want it to ignore PRICE_B, I want it to return an empty string:
'ABC123',5.6,,8.1 <--extra comma required where PRICE_B should be
How can I achieve this?
Besides the possibility to shredd the full XML and re-concatenate its values (there is an answer already), you might use FLWOR-XQuery:
DECLARE #xml XML=
'<EXPORT_DATA>
<ID>ABC123</ID>
<PRICE_A>5.6</PRICE_A>
<PRICE_B />
<PRICE_C>8.1</PRICE_C>
</EXPORT_DATA>';
EDIT better to read with a variable $txt instead of ($n/text())[1]
SELECT
STUFF
(
#xml.query('
let $r:=/EXPORT_DATA
for $n in $r/*
let $txt:=($n/text())[1]
return if(empty($txt) or not(empty(number($txt)))) then
concat(",",string($txt))
else concat(",''",string($txt),"''")
').value('.','nvarchar(max)'),1,1,'');
The result
'ABC123' ,5.6 , ,8.1
This code works on a mass of records using XQUERY.
I'm assuming char(10) (Line Feed) does not appear in your data.
I'm assuming the maximum length of the concatenated text is 1000 (I don't want to use varchar(max) for no good reason)
You can change both of these assumptions if you wish
declare #separator char(1) = char(10)
select substring
(
replace
(
cast
(
cast(DATA as xml).query
(
'for $i in //*
where not($i/*)
return concat
(
sql:variable("#separator")
,if(local-name($i) = "ID") then ('''''''') else ('''')
,($i/text())[1]
,if(local-name($i) = "ID") then ('''''''') else ('''')
)'
) as nvarchar(1000)
) ,' ' + #separator ,','
) ,2 ,1000
) as csv
from EXPORT_TEST
INSERT INTO EXPORT_TEST (DATA) VALUES
('<EXPORT_DATA><ID>ABC123</ID><PRICE_A>5.6</PRICE_A><PRICE_B></PRICE_B><PRICE_C>8.1</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>DEF456</ID><PRICE_A>6.7</PRICE_A><PRICE_B>66.77</PRICE_B><PRICE_C>7.2</PRICE_C></EXPORT_DATA>')
,('<EXPORT_DATA><ID>GHI789</ID><PRICE_A></PRICE_A><PRICE_B>88.99</PRICE_B><PRICE_C></PRICE_C></EXPORT_DATA>')
csv
'ABC123',5.6,,8.1
'DEF456',6.7,66.77,7.2
'GHI789',,88.99,
What about this:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
), cols AS (
SELECT
rownr,
Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
)
INSERT INTO EXPORT_TEST(DATA)
SELECT DISTINCT
STUFF((
SELECT ',' + IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')
FROM cols SSF WHERE SSF.rownr = S.rownr
FOR XML PATH(''),TYPE
).value('.','VARCHAR(MAX)'
), 1, 1, '') as DATA
FROM cols S
Using cte.xml.nodes('EXPORT_DATA/child::node()') in the second CTE will give as all nodes:
;WITH cte AS (
SELECT
rownr = ROW_NUMBER() OVER (ORDER BY #commaSeparatedValues),
Tbl.col.query('.') AS [xml]
FROM #xml.nodes('EXPORT_DATA') Tbl(col)
)
SELECT
rownr
,Tbl.Col.query('.')
,Tbl.Col.value('.', 'nvarchar(max)') AS Value
FROM cte
CROSS APPLY cte.xml.nodes('EXPORT_DATA/child::node()') Tbl(Col)
Then, in the concatenation we need to add check for empty string:
IIF(ISNUMERIC(value) = 1 OR LEN(value) = 0, Value, '''' + Value + '''')
How can I insert multiple xml files into a SQL Server table?
I have managed to get only 1 named file with this script:
CREATE TABLE #ZSK70 (Name varchar(25) , Value varchar(30),);
INSERT INTO #ZSK70 (Name, Value)
SELECT X.variable.query('Name').value('.', 'varchar(25)'),
X.variable.query('Value').value('.', 'varchar(30)')
FROM (
SELECT CAST(x AS XML)
FROM OPENROWSET(
BULK 'path+filename.xml',
SINGLE_BLOB) AS T(x)
) AS T(x)
CROSS APPLY x.nodes('CollectedData/variable') AS X(variable);
Any help is kindly appreciated.