Execute a DELETE command whilst iterating through an XML input parameter - sql-server

I have a stored procedure that receives 2 parameters.
#username VARCHAR(8),
#xmlShiftDays XML
I want to delete multiple rows from the database while iterating through the XML.
I have managed to do something similar for an INSERT (see below)
INSERT INTO table(username, date)
SELECT
username = #username,
CONVERT(DATETIME,shiftDate.date.value('.','VARCHAR(10)'),103)
FROM
#xmlShiftDays.nodes('/shiftDates/date') as shiftDate(date)
This will successfully insert "x" amount of rows into my table.
I now want to re-engineer the query to DELETE "x" amount of rows. If anyone knows how or could point me in the right direction I would greatly appreciate it.
An example of what I want to achieve is:
DECLARE #username VARCHAR(8)
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>21/01/2012</date></shiftDates>'
SET #username = 'A0123456'
DELETE FROM table
WHERE username = #username
AND date = "<b>loop through the nodes in the XML string</b>"

Assuming you're using SQL Server 2008 (or newer) for this so I can use the DATE datatype (unfortunately, you didn't specify in your question which version of SQL Server you're using).....
I would strongly recommend you use a language-independent, regional-settings-independent date format in your XML - use the ISO-8601 format of YYYYMMDD for best results.
So try something like this:
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>20120122</date><date>20120227</date></shiftDates>'
;WITH DatesToDelete AS
(
SELECT
DeletionDate = DT.value('(.)[1]', 'date')
FROM #XmlShiftDays.nodes('/shiftDates/date') AS SD(DT)
)
SELECT * FROM DatesToDelete
This should give you the two dates combined into XML string - right?
Now, you can use this to do the deletion from your table:
DECLARE #username VARCHAR(8)
DECLARE #xmlShiftDays XML
SET #xmlShiftDays = '<shiftDates><date>20120122</date><date>20120227</date></shiftDates>'
SET #username = 'A0123456'
;WITH DatesToDelete AS
(
SELECT
DeletionDate = DT.value('(.)[1]', 'date')
FROM #XmlShiftDays.nodes('/shiftDates/date') AS SD(DT)
)
DELETE FROM dbo.Table
WHERE username = #username
AND date IN (SELECT DeletionDate FROM DatesToDelete)
Does that work for you?

You can select the date from the Xml in the same way you did for the Insert:
DELETE FROM table
WHERE username = #username
AND date IN (SELECT
CONVERT(DATETIME,shiftDate.date.value('.','VARCHAR(10)'),103)
FROM
#xmlShiftDays.nodes('/shiftDates/date') as shiftDate(date))

Related

Openrowset bulk insert on a specific row

I am new to SQL, but am trying to learn its logic, I am assuming bulk insert will insert on all rows in this case a blob. (pdf file) below is my code but what I am trying to accomplish is, inserting a pdf file that I have put on SQL server in a row that has a matching Primary Key that I specify. So far I am missing the where clause to specify the PK
Declare #sql varchar(max)
Declare #filePath varchar(max)
Set #filePath = 'C:\iphone.pdf'
Set #sql='INSERT INTO HDData.dbo.PurchasedCellPhoneInfo(Receipt) SELECT * FROM OPENROWSET(BULK '''+ #filePath+''', SINGLE_BLOB) AS BLOB'
exec(#sql)
May I use an update t-SQL query instead of insert? and how would I drop the where to specify a specific row I want to insert this blob in?
Any help would be appreciated.
I also have tried this, following #misterPositive's suggestion for update query:
Declare #criteria varchar(50)
SET #criteria ='352014075399147'
UPDATE HDData.dbo.PurchasedCellPhoneInfo SET Receipt =
(SELECT Receipt FROM OPENROWSET (BULK 'C:\352014075399147.pdf, SINGLE_BLOB') a)
WHERE(IMEI = #criteria)
i do recieve this message:
Either a format file or one of the three options SINGLE_BLOB, SINGLE_CLOB, or SINGLE_NCLOB must be specified. i like this update query as it seems to fit what im trying to do.
You can do this to UPDATE:
UPDATE MyTable
SET blobField =
(SELECT BulkColumn FROM OPENROWSET (BULK 'C:\Test\Test1.pdf', SINGLE_BLOB) a)
WHERE (CriteriaField = #criteria)
Here is another way for PK
CREATE VIEW [dbo].[VWWorkDataLoad]
AS
SELECT RecordLine
FROM [dbo].[WorkDataLoad];
Now BULK INSERT should then look like:
BULK INSERT [dbo].[VWWorkDataLoad] FROM 'D:\NPfiles\TS082114.trn'
WITH (FIRSTROW = 2,FIELDTERMINATOR = ',' , ROWTERMINATOR = '\n');
If you want to insert new records then you could have an identity column for your PK and not have to worry about it. I have also seen functions used when a table is designed without identity on PK. Something like GetTableNamePK() in the select list.
If you want to update an existing record then you will want a where clause as you mentioned. This worked for me in testing:
Update TestBlob Set BinaryData = (SELECT * FROM OPENROWSET(BULK 'c:\temp\test.pdf', SINGLE_BLOB) AS BLOB)
Where ID = 2
If you do not want to use the Identity or Function this worked where ID is a primary key and I want to insert the BLOB with PK of 3:
INSERT INTO TestBlob2 (ID, BinaryData) SELECT 3, * FROM OPENROWSET(BULK 'c:\temp\test.pdf', SINGLE_BLOB) AS BLOB

Query XML data in SQL Server using replace in criteria

I have an XML column in my SQL Server 2008 table. What I'm trying to do is for a given parameter to my stored procedure strip out any spaces (using REPLACE) in the param and use this in the WHERE criteria but then using the XQuery exist clause also use the REPLACE method on the xml data:
-- Add the parameters for the stored procedure here
#PostCode varchar(20) = ''
AS
BEGIN
-- strip out any spaces from the post code param
SET #PostCode = REPLACE(#PostCode, ' ','')
SELECT TOP 1 *
FROM sd_LocalAuthorities
WHERE PostCodes.exist(N'REPLACE(/PostCodes/PostCode/text(), '' '','''')[. = sql:variable("#PostCode")]') = 1
END
I'm getting the error at XQuery sd_LocalAuthorities.PostCodes.exist()
There is no function '{http://www.w3.org/2004/07/xpath-functions}:REPLACE()
when running the procedure. Is there any alternatives to REPLACE() I can use to strip out spaces just for this WHERE criteria, I don't want to be modifying the table itself.
There is an XQuery function 'replace' but it's not available in TSQL where you want to use it. As an alternative approach you could pull the postcodes out of the XML and do the replace on native values. Something like this;
declare #sd_LocalAuthorities table (id int, postcodes xml)
declare #PostCode varchar(20); set #PostCode = 'BB11BB'
insert #sd_LocalAuthorities values (1, N'<PostCodes><PostCode>AA1 1AA</PostCode></PostCodes>')
insert #sd_LocalAuthorities values (2, N'<PostCodes><PostCode>BB1 1BB</PostCode></PostCodes>')
insert #sd_LocalAuthorities values (3, N'<PostCodes><PostCode>CC1 1CC</PostCode></PostCodes>')
select top 1
la.*
from
#sd_LocalAuthorities la
cross apply la.postcodes.nodes('/PostCodes/PostCode') as t(c)
where
replace(t.c.value('.', 'varchar(20)'), ' ', '') = #PostCode
This approach is more precise than converting the whole XML document/fragment to varchar because it only performs the replace on the postcode values. Depending on your circumstances an XML index may help performance.

SSIS : how to convert the source column from ID to Value

I'm creating a SSIS package to load data from a CSV file to SQL table. The sample CSV file is
EMP_ID,EMP_NAME,DEPT_ID,MANAGER_ID,SALARY
1801,SCOTT,20,1221,3000
1802,ALLEN,30,1221,3400
I need to load data into a SQL Server table, but while loading I need to load Department Name and Manager Name instead of their IDs. So I need to convert the CSV source to
1801,SCOTT,FINANCE,JOHNSON,3000
1802,ALLEN,HR,JOHNSON,3400
The values for Department Name and Manager name come from the SQL Server database only. But how do I query and convert ID to text values?
I'm new to SSIS, please suggest how can I achieve this.
Thanks
John
CREATE PROCEDURE [dbo].[BulkInsert]
(
-- Declare Parameters here for your CSV file
)
AS
BEGIN
SET NOCOUNT ON;
declare #query varchar(max)
CREATE TABLE #TEMP
(
[FieldName] [int] NOT NULL ,
[FieldName] int NOT NULL,
)
SET #query = 'BULK INSERT #TEMP FROM ''' + PathOfYourTextFile + ''' WITH ( FIELDTERMINATOR = '','',ROWTERMINATOR = ''\n'')'
--print #query
--return
execute(#query)
BEGIN TRAN;
MERGE TableName AS Target
-- Now here you can get the value Department Name and Manager Name by using Target.Id --in the table from where you mant to get the value of the Manager Name
USING (SELECT * FROM #TEMP) AS Source
ON (Target.YourTableId = Source.YourTextFileFieldId)
-- In the above line we are checking if the particular row exists in the table(Table1) then update the Table1 if not then insert the new row in Table-1.
WHEN MATCHED THEN
UPDATE SET
Target.SomeId= Source.SomeId
WHEN NOT MATCHED BY TARGET THEN
-- Insert statement
The above code is just an example for you by taking the help from this you can edit in your code. And one more important thing for you, Bulk Insert is one of the great way to save the CSV files. So try to use this..:)
In SSIS package from Data Flow tab use LOOKUP process from the Toolbox. You'll specify the table to get your string values from and which columns to use for the join and the column to substitue your IDs with.

How to pass multiple values in a single parameter for a stored procedures?

I want to pass multiple values in a single parameter. SQL Server 2005
You can have your sproc take an xml typed input variable, then unpack the elements and grab them. For example:
DECLARE #XMLData xml
DECLARE
#Code varchar(10),
#Description varchar(10)
SET #XMLData =
'
<SomeCollection>
<SomeItem>
<Code>ABCD1234</Code>
<Description>Widget</Description>
</SomeItem>
</SomeCollection>
'
SELECT
#Code = SomeItems.SomeItem.value('Code[1]', 'varchar(10)'),
#Description = SomeItems.SomeItem.value('Description[1]', 'varchar(100)')
FROM #XMLDATA.nodes('//SomeItem') SomeItems (SomeItem)
SELECT #Code AS Code, #Description AS Description
Result:
Code Description
========== ===========
ABCD1234 Widget
You can make a function:
ALTER FUNCTION [dbo].[CSVStringsToTable_fn] ( #array VARCHAR(8000) )
RETURNS #Table TABLE ( value VARCHAR(100) )
AS
BEGIN
DECLARE #separator_position INTEGER,
#array_value VARCHAR(8000)
SET #array = #array + ','
WHILE PATINDEX('%,%', #array) <> 0
BEGIN
SELECT #separator_position = PATINDEX('%,%', #array)
SELECT #array_value = LEFT(#array, #separator_position - 1)
INSERT #Table
VALUES ( #array_value )
SELECT #array = STUFF(#array, 1, #separator_position, '')
END
RETURN
END
and select from it:
DECLARE #LocationList VARCHAR(1000)
SET #LocationList = '1,32'
SELECT Locations
FROM table
WHERE LocationID IN ( SELECT CAST(value AS INT)
FROM dbo.CSVStringsToTable_fn(#LocationList) )
OR
SELECT Locations
FROM table loc
INNER JOIN dbo.CSVStringsToTable_fn(#LocationList) list
ON CAST(list.value AS INT) = loc.LocationID
Which is extremely helpful when you attempt to send a multi-value list from SSRS to a PROC.
Edited: to show that you may need to CAST - However be careful to control what is sent in the CSV list
Just to suggest. You can't really do so in SQL Server 2005. At least there is no a straightforward way. You have to use CSV or XML or Base 64 or JSON. However I strongly discourage you to do so since all of them are error prone and generate really big problems.
If you are capable to switch to SQL Server 2008 you can use Table valued parameters (Reference1, Reference2).
If you cannot I'd suggest you to consider the necessity of doing it in stored procedure, i.e. do you really want (should/must) to perform the sql action using SP. If you are solving a problem just use Ad hoc query. If you want to do so in education purposes, you might try don't even try the above mentioned things.
There are multiple ways you can achieve this, by:
Passing CSV list of strings as an argument to a (N)VARCHAR parameter, then parsing it inside your SP, check here.
Create a XML string first of all, then pass it as an XML datatype param. You will need to parse the XML inside the SP, you may need APPLY operator for this, check here.
Create a temp table outside the SP, insert the multiple values as multiple rows, no param needed here. Then inside the SP use the temp table, check here.
If you are in 2008 and above try TVPs (Table Valued Parameters) and pass them as params, check here.

Update a table row with XML (SQL Server)

I have a xml file below generated using
SELECT * FROM case WHERE ticketNo=#ticketNo FOR XML RAW,
ELEMENTS
The XML looks like this:
<row>
<ticketNo>1</ticketNo>
<caller>name</caller>
<category>3</category>
<service>4</service>
<workgroup>5</workgroup>
</row>
And I update my table using this query with the same with some value changed
UPDATE case
set caller = xmldoc.caller
set category = xml.category
from OpenXml(#idoc, '/row')
with (ticketNo VARCHAR(50)'./ticketNo',
caller VARCHAR(50) './caller',
category VARCHAR(50) './category') xmldoc
where dbo.tb_itsc_case.ticketNo = xmldoc.ticketNo
Is it possible to update the table without specifying the individual column?
You can not do an update without specifying the columns and you can not get data from XML without specifying what nodes to get the data from.
If you can use the "new" XML data type that was introduced in SQL Server 2005 you can do like this instead.
declare #XML xml =
'<row>
<ticketNo>1</ticketNo>
<caller>name</caller>
<category>3</category>
<service>4</service>
<workgroup>5</workgroup>
</row>'
update [case] set
[caller] = #XML.value('(/row/caller)[1]', 'varchar(50)'),
category = #XML.value('(/row/category)[1]', 'varchar(50)')
where
ticketNo = #XML.value('(/row/ticketNo)[1]', 'varchar(50)')
I was able to use this to update 1 row in a table. It requires you to have all fields specified in the XML. It replaces the existing row with the one specified in the XML.
I wish I could figure out how to do it for only the columns that are in the XML. I work on projects where the fields in the table change frequently and it requires re-specifying all the procedures to list out the column names.
create procedure Update_TableName (#xml xml) as
DECLARE #handle INT
EXEC sp_xml_preparedocument #handle OUTPUT, #xml
DECLARE #ID varchar(255)
SELECT #ID = ID FROM OPENXML (#handle, '/data', 2) WITH TableName
if exists (select 1 from TableName where ID = #ID)
delete from TableName where ID = #ID
Insert into TableName
SELECT * FROM OPENXML (#handle, '/data', 2) WITH TableName
EXEC sp_xml_removedocument #handle
XML:
<data>
<ID>9999</ID>
<Column1>Data</Column1>
<Column2>Data</Column2>
<Column3>Data</Column3>
</data>

Resources