Insert element into XML using XQuery in SQL Server - sql-server

I need to insert a column in a stored procedure which reads XML as input and reads the XML value using XQuery in SQL Server.
Consider a table dbo.emailDetails
Create Table dbo.emailDetails
(
EmailId int,
HomeEmail varchar(250),
);
XML file:
<EmailDetails xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<EmailId>1</EmailId>
<HomeEmail>test#sample.com</HomeEmail>
</EmailDetails>
SQL Server stored procedure:
CREATE PROCEDURE [apply].[UpdateEmailDetails]
#EmailDetails XML
(DOCUMENT [dbo].[EmailDetails])
AS
BEGIN
DECLARE #EmailId INT, #HomeEmail NVARCHAR(250)
SET #HomeEmail = #EmailDetails.value(N'(//EmailDetails/HomeEmail)[1]',N'NVARCHAR(255)');
SET #EmailId = #EmailDetails.value(N'(//EmailDetails/EmailID)[1]',N'INT');
INSERT INTO dbo.emails (emailid, homeemail)
VALUES (#EmailId, #HomeEmail )
END
Now I would like to include the IsConfirmed in the above stored procedure.
alter table dbo.Emaildetails
add IsConfirmed bit not null default 0
----- Executed successfully and added the new column in the table.
While I tried the below changes in the stored procedure:
Declare #IsConfirmed bit
SET #IsConfirmed = #EmailDetails.value(N'(//EmailDetails/IsConfirmed)[1]',N'BIT');
and got the error:
XQuery [value()]: There is no element named 'IsConfirmed' in the type 'element(EmailDetails,#anonymous) *'.
Could someone help me with this?
Kindly let me know if any details required further.
Regards,
Viswa V.

Your stored procedure has an input parameter which is as follows:
#EmailDetails XML (DOCUMENT [dbo].[EmailDetails])
This means you have an XML Schema Collection named "[dbo].[EmailDetails]" defined which basically says what format the incoming XML should be in.
If you expand the Programmability > Types > XML Schema Collections nodes in your database explorer you will see your "[dbo].[EmailDetails]" schema. If you then right click and select Script as > Create to new window you will see what nodes are expected to be in the XML you pass to your procedure.
You need to ALTER this schema to include a definition for your new
<IsConfirmed>
node.
Once you alter it you should then be able to run your alter procedure command again.
This MSDN article explains more on the topic

Related

Have a CREATE TABLE or CREATE PROCEDURE automatically determine column type in SSMS?

I have a database creation script that sets up tables, stored procedures, views, etc. When I change the type of a column in a create table statement, I want this change to be reflected in the create stored procedures / views / etc statements that reference that table without having to go through and manually change each one.
In other words I want my stored procedures to automatically determine the column type based on another column's type on creation. I don't need this to work on a live database with data, just while I'm iterating over the design and prototyping.
Something like a TYPE_OF() in this (fictional) example:
create table Logs
(
id int identity(1, 1) primary key,
userName varchar(32),
logType int foreign key references LogType(id),
description varchar(128),
datestamp datetime
);
go
create procedure WriteLog
(
#userName TYPE_OF(Logs.userName), -- should be varchar(32),
#logType int,
#description TYPE_OF(Logs.description) -- should be varchar(128)
)
as
begin
insert into Logs
values(#userName, #logType, #description, SYSDATETIME());
end
go;
I think I remember something similar from Oracle / SQL Plus / PLSQL but I am having trouble finding it.
I'm using SQL Server Management Studio v18.4
Not sure if the TYPEOF feature you're looking for exitsts, but you could try and use a DDL Trigger to keep your procedure in sync with the column type changes.
This trigger would get fired every time a table is altered and you'd just have to parse the EVENTDATA() to see if the column types in the Logs table have changed. The body of your trigger would look something like this:
CREATE TRIGGER OnLogsChanged
ON DATABASE
FOR ALTER_TABLE
AS
BEGIN
-- 1. Parse EVENTDATA() to see if the Logs table was altered
-- 2. If it has, store the definition of the WriteLog procedure into a variable by reading it from sys.procedures
-- 3. Read the new types for the columns of the Logs table from sys.all_columns
-- 4. replace the parameter declarations in the procedure definition to match the new types in the Logs table
-- 5. alter the procedure with the new definition by building up the ALTER PROCEDURE statement as a string and executing it with sp_executesql
END
As long as the trigger stays enabled your procedure should stay in sync with the table column types.

How to pass string array in 'In' clause in sql stored procedure

I have user table in database storing user information. I want to create stored procedure in which I will pass UserIDs as a list. I want to use this list to fetch data from user table.
I am creating type as a table using following query:
CREATE TYPE dbo.MyUserIDs AS TABLE (UserID int)
Now I am using this type in stored procedure:
ALTER PROCEDURE [dbo].[Test_in_Query]
#MyUserids MyUserIDs READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT * From Tbl_UserMast where UserID in (select UserID from #MyUserids)
END
When I execute this code I am getting following error:
Operand type clash: int is incompatible with MyUserIDs
I am following this link as a reference:
Reference link
I am directly executing stored procedure in sql server
I don't think you can pass a TVP-value using the SSMS gui (or at least I'm not aware of how to do it), but you have to do it in t-sql code, like so:
-- declare a variable using the user-defined type
DECLARE #MyUsers MyUserIDs
-- insert some data into it
INSERT INTO #MyUsers(UserID) VALUES (1),(2),(4)
-- and pass it into the proc
EXEC Test_in_Query #MyUserids = #MyUsers

EF4 - The selected stored procedure returns no columns

I have query in a stored procedure that calls some linked servers with some dynamic SQL. I understand that EF doesn't like that, so I specifically listed all the columns that would be returned. Yet, it still doesn't like that. What am I doing wrong here? I just want EF to be able to detect the columns returned from the stored procedure so I can create the classes I need.
Please see the following code that makes up the last lines of my stored procedure:
SELECT
#TempMain.ID,
#TempMain.Class_Data,
#TempMain.Web_Store_Class1,
#TempMain.Web_Store_Class2,
#TempMain.Web_Store_Status,
#TempMain.Cur_1pc_Cat51_Price,
#TempMain.Cur_1pc_Cat52_Price,
#TempMain.Cur_1pc_Cat61_Price,
#TempMain.Cur_1pc_Cat62_Price,
#TempMain.Cur_1pc_Cat63_Price,
#TempMain.Flat_Length,
#TempMain.Flat_Width,
#TempMain.Item_Height,
#TempMain.Item_Weight,
#TempMain.Um,
#TempMain.Lead_Time_Code,
#TempMain.Wp_Image_Nme,
#TempMain.Wp_Mod_Dte,
#TempMain.Catalog_Price_Chg_Dt,
#TempMain.Description,
#TempMain.Supersede_Ctl,
#TempMain.Supersede_Pn,
TempDesc.Cust_Desc,
TempMfgr.Mfgr_Item_Nbr,
TempMfgr.Mfgr_Name,
TempMfgr.Vendor_ID
FROM
#TempMain
LEFT JOIN TempDesc ON #TempMain.ID = TempDesc.ID
LEFT JOIN TempMfgr ON #TempMain.ID = TempMfgr.ID
EF doesn't support importing stored procedures which build result set from:
Dynamic queries
Temporary tables
The reason is that to import the procedure EF must execute it. Such operation can be dangerous because it can trigger some changes in the database. Because of that EF uses special SQL command before it executes the stored procedure:
SET FMTONLY ON
By executing this command stored procedure will return only "metadata" about columns in its result set and it will not execute its logic. But because the logic wasn't executed there is no temporary table (or built dynamic query) so metadata contains nothing.
You have two choices (except the one which requires re-writing your stored procedure to not use these features):
Define the returned complex type manually (I guess it should work)
Use a hack and just for adding the stored procedure put at its beginning SET FMTONLY OFF. This will allow rest of your SP's code to execute in normal way. Just make sure that your SP doesn't modify any data because these modifications will be executed during import! After successful import remove that hack.
Adding this Non-Logical block of code solved the problem. Even though it will never Hit
IF 1=0 BEGIN
SET FMTONLY OFF
END
Why does my typed dataset not like temporary tables?
http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataset/thread/fe76d511-64a8-436d-9c16-6d09ecf436ea/
Or you can create a User-Defined Table Type and return that.
CREATE TYPE T1 AS TABLE
( ID bigint NOT NULL
,Field1 varchar(max) COLLATE Latin1_General_CI_AI NOT NULL
,Field2 bit NOT NULL
,Field3 varchar(500) NOT NULL
);
GO
Then in the procedure:
DECLARE #tempTable dbo.T1
INSERT #tempTable (ID, Field1, Field2, Field3)
SELECT .....
....
SELECT * FROM #tempTable
Now EF should be able to recognize the returned columns type.
As some others have noted, make sure the procedure actually runs. In particular, in my case, I was running the procedure happily without error in SQL Server Management Studio completely forgetting that I was logged in with admin rights. As soon as I tried running the procedure using my application's principal user I found there was a table in the query that that user did not have permission to access.
Interesting side note: Had the same problem which I first solved by using Table Variables, rather than Temp Tables (just for the import). That wasn't particularly intuitive to me, and threw me off when initially observing my two SProcs: one using Temp tables and one with Table Variables.
(SET FMTONLY OFF never worked for me, so I just changed my SProcs temporarily to get the column info, rather than bothering with the hack on the EF side just as an FYI.)
My best option was really just manually creating the complex type and mapping the function import to it. Worked great, and the only difference ended up being that an additional FactoryMethod to create the properties was included in the Designer.
What I would add is:
That the import also fails if the stored procedures has parameters and returns no result set for the default parameter values.
My stored procedure had 2 float parameters and would not return anything when both parameters are 0.
So in order to add this stored procedure to the entity model, I set the value of these parameters in the stored procedure so that it is guaranteed to return some rows, no matter what the parameters actually are.
Then after adding this stored procedure to the entity model I undid the changes.
both solutions :
1- Define the returned complex type manually (I guess it should work)
2- Use a hack and just for adding the stored procedure put at its beginning SET FMTONLY OFF.
not working with me in some procedure however it worked with other one!
my procedure ends with this line:
SELECT machineId, production [AProduction]
, (select production FROM #ShiftBFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [BProduction]
, (select production FROM #ShiftCFinalProd WHERE machineId = #ShiftAFinalProd.machineId) [CProduction]
FROM #ShiftAFinalProd
ORDER BY machineId
Thanks
In addition to what #tmanthley said, be sure that your stored procedure actually works by running it first in SSMS. I had imported some stored procedures and forgot about a couple dependent scalar functions, which caused EF to determine that the procedure returned no columns. Seems like a mistake I should have caught earlier on, but EF doesn't give you an error message in that case.
Entity Framework will try to get the columns by executing your stored procedure, passing NULL for every argument.
Please make sure that the stored procedure will return something under all the circumstances. Note it may have been smarter for Entity Framework to execute the stored proc with default values for the arguments, as opposed to NULLs.
ER does the following to get the metadata of the table:
SET FMTONLY ON
This will break your stored procedure in various circumstances, in particular, if it uses a temporary table.
So to get a result as complex type; please try by adding
SET FMTONLY OFF;
This worked for me - hope it works for you too.
Referred from https://social.msdn.microsoft.com/Forums/en-US/e7f598a2-6827-4b27-a09d-aefe733b48e6/entity-model-add-function-import-stored-procedure-returns-no-columns?forum=adodotnetentityframework
In my case adding SET NOCOUNT ON; at the top of the procedure fixed the problem. It's best practice anyway.
In my case SET FMTONLY OFF did not work. The method I followed is, I took backup of original stored procedure and replace with only column name like the below query.
Select Convert(max,'') as Id,Convert(max,'') as Name
After this change, create new function import, complex type in entity framework.
Once the function import and complex type is created, replace the above query with your original stored procedure.
SET FMTONLY OFF
worked for me for one of the procedure but failed for other procedure. Following steps helps me to resolve my problem
Within a stored procedure, I have created temporary table with the same column type and inserted all the data returned by dynamic query to temp table.
and selected the temp table data.
Create table #temp
(
-- columns with same types as dynamic query
)
EXEC sp_executeSQL #sql
insert into #temp
Select * from #temp
drop table #temp
Deleted existing complex type, import function and stored procedure instance for old stored procedure and updated entity model for current new procedure.
Edit the imported Function in entity modal for desired complex type, you will get all the column information there which is not getting for previous stored procedure.
once you have done with the type creation you can delete the temporary table from stored procedure and then refresh Entity Framework.
In Entity framework, while getting column information the sql executes the procedure with passing null values in parameter. So I handled null case differently by creating a temp table with all the required columns and returning all the columns with no value when null is passed to the procedure.
In my procedure there was dynamic query, something like
declare #category_id int
set #category_id = (SELECT CATEGORY_ID FROM CORE_USER where USER_ID = #USER_ID)
declare #tableName varchar(15)
declare #sql VARCHAR(max)
declare #USER_IDT varchar(100)
declare #SESSION_IDT varchar(10)
IF (#category_id = 3)
set #tableName = 'STUD_STUDENT'
else if(#category_id = 4)
set #tableName = 'STUD_GUARDIAN'
if isnull(#tableName,'')<>''
begin
set #sql = 'SELECT [USER_ID], [FIRST_NAME], SCHOOL_NAME, SOCIETY_NAME, SCHOOL_ID,
SESSION_ID, [START_DATE], [END_DATE]
from #tableName
....
EXECUTE (#sql)
END
ELSE
BEGIN
SELECT * from #UserPrfTemp
END
I was not getting the column information in
my case after using the set FMTONLY OFF trick.
This is temp table I created to get the blank data.
Now I am getting the column info
Create table #UserPrfTemp
(
[USER_ID] bigint,
[FIRST_NAME] nvarchar(60),
SCHOOL_NAME nvarchar(60),
SOCIETY_NAME nvarchar(200)
.....
}
I solved this problem creating a table variable and then returning from it.
DECLARE #VarTable TABLE (
NeededColumn1 VARCHAR(100),
NeededColumn2 INT,
NeededColumn3 VARCHAR(100)
)
...
--Fetch Data from Linked server here
...
INSERT INTO #VarTable (NeededColumn1,NeededColumn2,NeededColumn3)
SELECT Column1, Column2, Column3
FROM #TempTable
SELECT * FROM #VarTable.
In that manner, your the SP result will be bounded to the table variable, which EF has access to.
I discovered a method that should help most people out whatever's happening.
Pull up your favourite SQL client and run the proc that you're trying to update with every parameter = null. Visual Studio is literally trying to do this when SET FMTONLY ON. Run a trace. You'll see.
You'll probably get an error, or unexpected data out. Fix that and your issue is fixed.
In my case the function read in JSON and failed because the JSON string was empty.
I just put something like
IF(#FooJSON IS NULL)
BEGIN
SELECT 1 VAR1, 2 VAR2;
END
ELSE
--OTHER LOGIC
That's probably an ugly solution, but I inherited this mess and we don't go into Ravenholm.
Change #Temp tables with WITH SQL EXPRESSION

SSIS 2008 - import xml file contents into sql server

I want to take a series of xml files and pull the xml of the file into a table in the database. I have a for each file enumerator, then an xml task to pull out the dtd and put the contents in a variable. Now that I have the file name and the contents in a variable, I need to insert both pieces of data into the database.
My table to store the data looks like this:
create table Import_Files
(
SequenceId int IDENTITY(1,1) NOT NULL,
FileName varchar(200) NOT NULL,
FileXml xml NOT NULL,
Created datetime DEFAULT(GETDATE()) NOT NULL,
Processed bit DEFAULT(0) NOT NULL
)
My Stored procedure:
CREATE PROCEDURE [dbo].[AddFile]
#FileName varchar(200),
#FileXml xml
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
--Add new record
INSERT INTO Import_Files
([FileName], FileXml)
VALUES
(#FileName, #FileXml)
END
I can't get it to work because hte xml data type isn't available in my execute sql task. Any ideas on how to make this work?
I would look at the import column transformation. It allows you to import the contents of a file for each row in a dataflow. The data source would simply be a listing of each file and any other column level meta data you need.
This would probably be more performant then doing a row by row insert from a spro
More info on setting this up here:
http://msdn.microsoft.com/en-us/library/ms141262.aspx
Have you tried using a string datatype?
As sugested earlier change the proc to accept a string datatype varchar(max) and do a convert to xml in the proc if you must store it in an xml column
Instead of using an Execute SQL Task, I would use an XML Source - generate all the fields you wanted into columns and then just pass those into the database table using an OLE DB Destination. This way you don't have to even use your stored procedure since SSIS already would do these inserts for you and it would recognize your xml type.
I had to use a Script task and call the sproc through code.

Best way to transfer an xml to SQL Server?

I have been hearing the podcast blog for a while, I hope I dont break this.
The question is this: I have to insert an xml to a database. This will be for already defined tables and fields. So what is the best way to accomplish this? So far I am leaning toward programatic. I have been seeing varios options, one is Data Transfer Objects (DTO), in the SQL Server there is the sp_xml_preparedocument that is used to get transfer XMLs to an object and throught code.
I am using CSharp and SQL Server 2005. The fields are not XML fields, they are the usual SQL datatypes.
In an attempt to try and help, we may need some clarification. Maybe by restating the problem you can let us know if this is what you're asking:
How can one import existing xml into a SQL 2005 database, without relying on the built-in xml type?
A fairly straight forward solution that you already mentioned is the sp_xml_preparedocument, combined with openxml.
Hopefully the following example illustrates the correct usage. For a more complete example checkout the MSDN docs on Using OPENXML.
declare #XmlDocumentHandle int
declare #XmlDocument nvarchar(1000)
set #XmlDocument = N'<ROOT>
<Customer>
<FirstName>Will</FirstName>
<LastName>Smith</LastName>
</Customer>
</ROOT>'
-- Create temp table to insert data into
create table #Customer
(
FirstName varchar(20),
LastName varchar(20)
)
-- Create an internal representation of the XML document.
exec sp_xml_preparedocument #XmlDocumentHandle output, #XmlDocument
-- Insert using openxml allows us to read the structure
insert into #Customer
select
FirstName = XmlFirstName,
LastName = XmlLastName
from openxml ( #XmlDocumentHandle, '/ROOT/Customer',2 )
with
(
XmlFirstName varchar(20) 'FirstName',
XmlLastName varchar(20) 'LastName'
)
where ( XmlFirstName = 'Will' and XmlLastName = 'Smith' )
-- Cleanup xml document
exec sp_xml_removedocument #XmlDocumentHandle
-- Show the data
select *
from #Customer
-- Drop tmp table
drop table #Customer
If you have an xml file and are using C#, then defining a stored procedure that does something like the above and then passing the entire xml file contents to the stored procedure as a string should give you a fairly straight forward way of importing xml into your existing table(s).
If your XML conforms to a particular XSD schema, you can look into using the "xsd.exe" command line tool to generate C# object classes that you can bind the XML to, and then form your insert statements using the properties of those objects: MSDN XSD Doc
Peruse this document and it will give you the options:
MSDN: XML Options in Microsoft SQL Server 2005
You may want to use XSLT to transfer your XML into SQL statements... ie
<xml type="user">
<data>1</data>
<data>2</data>
<xml>
Then the XSLT would look like
<xsl:template match="xml">
INSERT INTO <xsl:value-of select="#type" /> (data1, data2) VALUES (
'<xsl:value-of select="data[1]" />',
'<xsl:value-of select="data[2]" />');
</xsl:template>
The match statement most likely won't be the root node, but hopefully you get the idea. You may also need to wrap the non xsl:value-of parts in xsl:text to prevent extra characters from being dumped into the query. And you'd have to make sure the output of the XSLT was text. That said you could get a list of SQL statements that you could run through the DB. or you could use XSLT to output a T-SQL statement that you could load as a stored procedure.

Resources