parsing multiple xml arrays in sql - sql-server

Here's my setup: I am passing two arrays as XML into a SQL stored procedure.
These are:
<PhoneID Value=128/>
<PhoneID Value=129/>
<PhoneID Value=130/>
and
<AddressID Value=268/>
<AddressID Value=157/>
<AddressID Value=395/>
The Address and Phone tables look like this (pseudo-code follows):
Phone:
BIGINT PhoneID
BIGINT PhoneNumber
SMALLINT AreaCode
INT Extension
Address:
BIGINT AddressID
NVARCHAR StreetAddress
NVARCHAR CountryName
NVARCHAR City
BIGINT Zip
My dilemma is this:
I need to walk through the passed in arrays in lock-step to return
ContactInfo:
BIGINT PhoneNumber
NVARCHAR StreetAddress
BIGINT ZipCode
i.e. I need to return one ContactInfo built from Phone where PhoneID = 128 and Address where AddressID = 268, and another where PhoneID = 129 and AddressID = 268, etc.
My big question is: How do I walk two xml arrays in lock-step in sql?
This is in SQL Server 2008 R2.
Thanks everyone :)

The only viable approach I see is trying to do this:
parse/shred your XML arrays into temporary tables for phones and addresses
those temporary tables have an ID INT IDENTITY defined
when inserting into a fresh temporary table with this INT IDENTITY, both sets of data will get consecutive numbering (1, 2, 3, 4......)
you can then join the two temporary tables on their ID and should get your "lock-step" behavior:
So in code, this would look something like (mind you: your XML is invalid, too - the attribute values need to be in double quotes for this to work!):
DECLARE #phones XML = '<PhoneID Value="128"/><PhoneID Value="129"/><PhoneID Value="130"/>'
DECLARE #Addresses XML = '<AddressID Value="268"/><AddressID Value="157"/><AddressID Value="395"/>'
DECLARE #phoneTable TABLE (ID INT IDENTITY PRIMARY KEY, PhoneID INT)
DECLARE #AddressTable TABLE (ID INT IDENTITY PRIMARY KEY, AddressID INT)
INSERT INTO #phoneTable(PhoneID)
SELECT
PHID.value('(#Value)', 'int')
FROM
#phones.nodes('/PhoneID') AS PH(PHID)
INSERT INTO #AddressTable(AddressID)
SELECT
ADRID.value('(#Value)', 'int')
FROM
#addresses.nodes('/AddressID') AS AD(ADRID)
SELECT p.*, a.*
FROM #phoneTable p
INNER JOIN #addresstable a ON p.ID = a.ID
Does that work for you?? From here, you could then insert the data into your actual working tables and do any additional lookups or processing.

Turns out it's possible to in T-SQL, like so:
CREATE PROCEDURE [dbo].[XmlParsingProcedure]
#XmlArgs XML
AS
SELECT * FROM SomeTable
WHERE (s.XmlArgs IN (select x.XmlArgs.value('#Value','smallint') FROM #XmlArgs.nodes('//XmlArgs') as x(XmlArgs)))
However, intrinsic knowledge of the XML is required.
The above implies the XML schema:
<XmlArgs Value="some value"/>
<XmlArgs Value="some other value"/>
<XmlArgs Value="yet another value"/>
where both, 'XmlArgs' and 'Value' are case sensitive.

Related

How to make SQL server recognize unique Thai characters?

Here's my table:
id name
1 ទឹក កាបូន
2 លីអូ បៀរ
3 ស្របៀរ ២៤
4 ស្រាបៀរ ឌ្រាប់
When I query using this statement: SELECT * FROM t1 WHERE name = N'លីអូ បៀរ', it returns all rows. This is weird since those Thai characters are different.
Also, this will cause an issue if I make the name column as unique. Does anyone encounter the same issue and what is the possible fix? I tried changing the collation but still no avail.
How to make SQL server recognize unique Thai characters?
Bing translate identifies those characters as Khmer, not Thai. So you need to pick a collation that has language-specific rules for those characters, eg
drop table if exists t1
create table t1(id int, name nvarchar(200) collate Khmer_100_CI_AI )
insert into t1(id,name)
values (1, N'ទឹក កាបូន'),(2, N'លីអូ បៀរ'),(3, N'ស្របៀរ ២៤'),(4, N'ស្រាបៀរ ឌ្រាប់')
SELECT * FROM t1 WHERE name = N'លីអូ បៀរ'
Or use binary collation, that simply compares the characters by their code point values. eg
drop table if exists t1
create table t1(id int, name nvarchar(200) collate Latin1_General_100_BIN2 )
insert into t1(id,name)
values (1, N'ទឹក កាបូន'),(2, N'លីអូ បៀរ'),(3, N'ស្របៀរ ២៤'),(4, N'ស្រាបៀរ ឌ្រាប់')
SELECT * FROM t1 WHERE name = N'លីអូ បៀរ'
Even some of the newer Latin collations will work, eg
drop table if exists t1
create table t1(id int, name nvarchar(200) collate Latin1_General_100_CI_AI )
insert into t1(id,name)
values (1, N'ទឹក កាបូន'),(2, N'លីអូ បៀរ'),(3, N'ស្របៀរ ២៤'),(4, N'ស្រាបៀរ ឌ្រាប់')
SELECT * FROM t1 WHERE name = N'លីអូ បៀរ'

SQL : Comma separated string in one row V/S multiple rows for single column in terms of performance

I have one table below of signature
"tblProperties"
ID int,
DocID int,
PNAME varchar(50),
PVALUE varchar(max),
PType varchar(50),
Users varchar(max)
and
indexes
create index InxUsers on tblProperties(Users);
create index InxPValue on tblProperties(PVALUE);
create index InxPrDocID on tblProperties(DocID);
Here Users is the comma separated email address of users.
I am performing different select queries on the table like
SELECT DocID
FROM tblProperties
WHERE PNAME = 'AssignDocument'
AND PVALUE LIKE '%abc%'
AND Users LIKE '%ankit#gmail.com%';
To achieve to best performance I should split above table in below two tables
1) "tblProperties"
ID int,
DocID int,
PNAME varchar(50),
PVALUE varchar(max),
PType varchar(50)
2) "tblUsersDocument"
ID int,
DocID int,
User varchar(320)
and indexes
create index InxUsers on tblProperties(Users);
create index InxPValue on tblProperties(PVALUE);
create index InxUseEmail on tblUsersDocument(User);
create index InxDocID on tblUsersDocument(DocID);
create index InxPrDocID on tblProperties(DocID);
And select query is
SELECT p.DocID
FROM tblProperties AS p inner join tblUsersDocument AS u
on p.DocID = u.DocID
WHERE p.PNAME = 'AssignDocument'
AND p.PVALUE LIKE '%abc%'
AND u.User = 'ankit#gmail.com';
Which one is better in terms of performance first one or second one?
I have tested both in SHOWPLAN_XML in single batch first one is giving 25% and second one is giving 75% query cost.

How to find specific characters in a string and replace them with values fetched from a table in SQL Server

I have text stored in the table "StructureStrings"
Create Table StructureStrings(Id INT Primary Key,String nvarchar(4000))
Sample Data:
Id String
1 Select * from Employee where Id BETWEEN ### and ### and Customer Id> ###
2 Select * from Customer where Id BETWEEN ### and ###
3 Select * from Department where Id=###
and I want to replace the "###" word with a values fetched from another table
named "StructureValues"
Create Table StructureValues (Id INT Primary Key,Value nvarcrhar(255))
Id Value
1 33
2 20
3 44
I want to replace the "###" token present in the strings like
Select * from Employee where Id BETWEEN 33 and 20 and Customer Id> 44
Select * from Customer where Id BETWEEN 33 and 20
Select * from Department where Id=33
PS: 1. Here an assumption is that the values will be replaced with the tokens in the same order i.e first occurence of "###" will be replaced by first value of
"StructureValues.Value" column and so on.
Posting this as a new answer, rather than editting my previous.
This uses Jeff Moden's DelimitedSplit8K; it does not use the built in splitter available in SQL Server 2016 onwards, as it does not provide an item number (thus no join criteria).
You'll need to firstly put the function on your server, then you'll be able to use this. DO NOT expect it to perform well. There's a lot of REPLACE in this, which will hinder performance.
SELECT (SELECT REPLACE(DS.Item, '###', CONVERT(nvarchar(100), SV.[Value]))
FROM StructureStrings sq
CROSS APPLY DelimitedSplit8K (REPLACE(sq.String,'###','###|'), '|') DS --NOTE this uses a varchar, not an nvarchar, you may need to change this if you really have Unicode characters
JOIN StructureValues SV ON DS.ItemNumber = SV.Id
WHERE SS.Id = sq.id
FOR XML PATH ('')) AS NewString
FROM StructureStrings SS;
If you have any question, please place the comments on this answer; do not put them under the question which has already become quite a long discussion.
Maybe this is what you are looking for.
DECLARE #Employee TABLE (Id int)
DECLARE #StructureValues TABLE (Id int, Value int)
INSERT INTO #Employee
VALUES (1), (2), (3), (10), (15), (20), (21)
INSERT INTO #StructureValues
VALUES (1, 10), (2, 20)
SELECT *
FROM #Employee
WHERE Id BETWEEN (SELECT MIN(Value) FROM #StructureValues) AND (SELECT MAX(Value) FROM #StructureValues)
Very different take here:
CREATE TABLE StructureStrings(Id int PRIMARY KEY,String nvarchar(4000));
INSERT INTO StructureStrings
VALUES (1,'SELECT * FROM Employee WHERE Id BETWEEN ### AND ###'),
(2,'SELECT * FROM Customer WHERE Id BETWEEN ### AND ###');
CREATE TABLE StructureValues (Id int, [Value] int);
INSERT INTO StructureValues
VALUES (1,10),
(2,20);
GO
DECLARE #SQL nvarchar(4000);
--I'm asuming that as you gave one output you are supplying an ID or something?
DECLARE #Id int = 1;
WITH CTE AS(
SELECT SS.Id,
SS.String,
SV.[Value],
LEAD([Value]) OVER (ORDER BY SV.Id) AS NextValue,
STUFF(SS.String,PATINDEX('%###%',SS.String),3,CONVERT(varchar(10),[Value])) AS ReplacedString
FROM StructureStrings SS
JOIN StructureValues SV ON SS.Id = SV.Id)
SELECT #SQL = STUFF(ReplacedString,PATINDEX('%###%',ReplacedString),3,CONVERT(varchar(10),NextValue))
FROM CTE
WHERE Id = #Id;
PRINT #SQL;
--EXEC (#SQL); --yes, I should really be using sp_executesql
GO
DROP TABLE StructureValues;
DROP TABLE StructureStrings;
Edit: Note that Id 2 will return NULL, as there isn't a value to LEAD to. If this needs to change, we'll need more logic on what the value should be if there is not value to LEAD to.
Edit 2: This was based on the OP's original post, not what he puts it as later. As it currently stands, it's impossible.

Foreign Key Constraint in Stored Procedure - SQL Server

I have the following stored procedure, I have two tables here, Movie and Director. Both need to be updated when a new movie has been created or added. How do you handle FKs in stored procedures? The FK in this case is director_id. It is a primary key in Director but a FK in Movie Do I need to specify it twice like so? I am getting conflict errors
CREATE PROCEDURE Book_Book_Creation
#Book_id_arg DECIMAL(12),
#author_id_arg DECIMAL(12),
#type_id_arg DECIMAL(12),
#title_arg VARCHAR(64), -
#copyright_arg DECIMAL(4),
#dauthor_id_2_arg DECIMAL(12),
#author_fname_arg VARCHAR (64),
#author_lname_arg VARCHAR (64)
AS
BEGIN
INSERT INTO Book(Book_id, author_id,genre_id, title, copyright)
VALUES (#author_arg, #author_id_arg, #type_id_arg, #title_arg, #copyright_arg);
INSERT INTO Author(author_id, author_fname, author_lname)
VALUES (#director_id_2_arg, #director_fname_arg, #director_lname_arg)
END;
EXECUTE Book_Book_Creation 32,32,1,'Tempting',2013,32,'Taylor','Mendez';
Basically, you just need to do this:
insert into the Director table first
get the newly inserted ID from that table (assuming that the Director_Id column is your primary key and is of type INT IDENTITY)
then insert into the Movie table with that new ID
Something like this:
DECLARE #NewDirectorID INT
INSERT INTO Director (Director_id, Director_fname, director_lname)
VALUES (#director_id_2_arg, #director_fname_arg, #director_lname_arg)
SELECT #NewDirectorID = SCOPE_IDENTITY()
INSERT INTO Movie (Movie_id, director_id,genre_id, title, copyright)
VALUES (#movie_id_arg, #NewDirectorID, #genre_id_arg, #title_arg, #copyright_arg);
I don't see why you would pass in the director's ID as a parameter - twice!
Try this one -
ALTER PROCEDURE dbo.Movie_Movie_Creation12
#movie_id_arg DECIMAL(12),
#director_id_arg DECIMAL(12),
#genre_id_arg DECIMAL(12),
#title_arg VARCHAR(64),
#copyright_arg DECIMAL(4),
#director_fname_arg VARCHAR (64),
#director_lname_arg VARCHAR (64)
AS BEGIN
INSERT INTO dbo.Director (Director_id, Director_fname, director_lname)
SELECT #director_id_arg, #director_fname_arg, #director_lname_arg
INSERT INTO dbo.Movie (Movie_id, director_id,genre_id, title, copyright)
SELECT #movie_id_arg, #director_id_arg, #genre_id_arg, #title_arg, #copyright_arg
END
EXECUTE dbo.Movie_Movie_Creation12
#movie_id_arg = 32
, #director_id_arg = 32
, #genre_id_arg = 1
, #title_arg = 'Argo'
, #copyright_arg = 2012
, #director_fname_arg = 'Ben'
, #director_lname_arg = 'Affleck'

Return Data and Update Row without Multiple Lookups?

I have a stored procedure that looks up an article based on the article's title. But I also need to increment a column in the same table that counts the number of times the article is viewed.
Trying to be as efficient as possible, I see two possible ways to approach this:
Perform one SELECT to obtain the PK on the target row. Then use that PK to increment the number of views and, finally, another SELECT using the PK to return the article data.
Perform one SELECT to return the article data to my application, and then use the returned PK to make another round trip to the database to increment the number of views.
I know #1 would be pretty fast, but it's three lookups. And #2 requires two round trips to the database. Is there no way to optimize this task?
EDIT Based on feedback, I came up with the following. Thanks for any comments or constructive criticism.
DECLARE #Slug VARCHAR(250) -- Stored procedure argument
-- declare #UpdatedArticle table variable
DECLARE #UpdatedArticle TABLE
(
ArtID INT,
ArtUserID UNIQUEIDENTIFIER,
ArtSubcategoryID INT,
ArtTitle VARCHAR(250),
ArtHtml VARCHAR(MAX),
ArtDescription VARCHAR(350),
ArtKeywords VARCHAR(250),
ArtLicenseID VARCHAR(10),
ArtViews BIGINT,
ArtCreated DATETIME2(7),
ArtUpdated DATETIME2(7)
);
UPDATE Article
SET ArtViews = ArtViews + 1
OUTPUT
INSERTED.ArtID,
INSERTED.ArtUserID,
inserted.ArtSubcategoryID,
INSERTED.ArtTitle,
INSERTED.ArtHtml,
INSERTED.ArtDescription,
INSERTED.ArtKeywords,
INSERTED.ArtLicenseID,
INSERTED.ArtViews,
INSERTED.ArtUpdated,
INSERTED.ArtCreated
INTO #UpdatedArticle
WHERE ArtSlugHash = CHECKSUM(#Slug) AND ArtSlug = #Slug AND ArtApproved = 1
SELECT a.ArtID, a.ArtUserID, a.ArtTitle, a.ArtHtml, a.ArtDescription, a.ArtKeywords, a.ArtLicenseID,
l.licTitle, a.ArtViews, a.ArtCreated, a.ArtUpdated, s.SubID, s.SubTitle, c.CatID, c.CatTitle,
sec.SecID, sec.SecTitle, u.UsrDisplayName AS UserName
FROM #UpdatedArticle a
INNER JOIN Subcategory s ON a.ArtSubcategoryID = s.SubID
INNER JOIN Category c ON s.SubCatID = c.CatID
INNER JOIN [Section] sec ON c.CatSectionID = sec.SecID
INNER JOIN [User] u ON a.ArtUserID = u.UsrID
INNER JOIN License l ON a.ArtLicenseID = l.LicID
Here is a way using the OUTPUT statement (SQL Server 2005 onwards), in a single update statement:
IF OBJECT_ID ('Books', 'U') IS NOT NULL
DROP TABLE dbo.Books;
CREATE TABLE dbo.Books
(
BookID int NOT NULL PRIMARY KEY,
BookTitle nvarchar(50) NOT NULL,
ModifiedDate datetime NOT NULL,
NumViews int not null CONSTRAINT DF_Numviews DEFAULT (0)
);
INSERT INTO dbo.Books
(BookID, BookTitle, ModifiedDate)
VALUES
(106, 'abc', GETDATE()),
(107, 'Great Expectations', GETDATE());
-- declare #UpdateOutput1 table variable
DECLARE #UpdateOutput1 table
(
BookID int,
BookTitle nvarchar(50),
ModifiedDate datetime,
NumViews int
);
-- >>>> here is the update of Numviews and the Fetch
-- update Numviews in Books table, and retrive the row
UPDATE Books
SET
NumViews = NumViews + 1
OUTPUT
INSERTED.BookID,
INSERTED.BookTitle,
INSERTED.ModifiedDate,
INSERTED.NumViews
INTO #UpdateOutput1
WHERE BookID = 106
-- view updated row in Books table
SELECT * FROM Books;
-- view output row in #UpdateOutput1 variable
SELECT * FROM #UpdateOutput1;

Resources