In 'CASE' statement in SQL we use a bool condition and get a TRUE or FALSE result. In this situation I have to use non-bool unlimited condition. But I can't...
ALTER proc [dbo].[sp_StudentList](#CreatedBy nvarchar(max))
as
begin
declare #LikedBy nvarchar(max) = (Select LikedBy from LikeStatus)
declare #TeacherRequestID int = (Select TeacherRequestID from LikeStatus where LikedBy=#CreatedBy)
declare #UserName nvarchar(max) = #CreatedBy
declare #i int = 1
declare #NumberOfRows int = (select count(*) from TeacherRequest)
select SP.StuThana, SP.StuDist, TR.StudentName,TR.StudentCode, TR.Class, TR.Subject, TR.StuGroup,TR.StuRelation, TR.Institute,TR.Status, TR.LikeStatus,
**CASE
WHEN
WHILE(#i <= #NumberOfRows)
BEGIN
#TeacherRequestID = TR.ID THEN 'Liked' Else 'Like'
set #i = #i + 1
END
END as LikeFlag**
from StudentsProfile SP join TeacherRequest TR on SP.CreatedBy=TR.CreatedBy
--sp_StudentList 'teacher1#gmail.com'
end
The technical answer to your question as posed in your title is that you can't.
declare #i int = 5;
select case when (while #i > 0 begin set #i = #i - 1 end) then 1 else 0 end;
-- Incorrect syntax near the keyword 'while'
Is your intention to just determine whether a student listed in a row likes the associated teacher? If so, then you're looking for whether an entry exists in another table, not how often it occurs. And I would tie it to sp.createdBy, not #createdBy.
select // ...,
likeFlag =
case when exists (
select 0
from likeStatus ls
where ls.likedBy = sp.createdBy
and ls.TeacherRequestId = tr.id
) then 'Liked'
else 'Like'
end
from studentsProfile sp
join teacherRequest tr on sp.createdBy = tr.createdBy
If for some reason you really only need 'Liked' based on #createdBy, then change ls.likedBy = sp.createdBy to ls.likedBy = #createdBy, but I don't see a strong use case for that.
The title says it all. I need to create a table(ID, FirstName, LastName) that will be populated with randomly generated 'words' that have a random length and are created from my 'alphabet'. Each word needs to be randomly generated by the DB. The whole table should have 1,000,000 rows.
Let me fill you in what I have done so far.
Created a VIEW that generates a random number:
CREATE VIEW [dbo].[RANDOM]
AS SELECT RAND() RandomResult
Created a SCALAR FUNCTION that generates a random lengh 'word' from the set of determined 'letters' :
CREATE FUNCTION [dbo].[WordGenerator] (#RandomWord VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS BEGIN
DECLARE #Alphabet VARCHAR(33) = 'abcdefghijklmnoprstuówxyzęąśłżźćń',
#StrLength INT,
#LoopCount INT,
#RandomString VARCHAR(MAX),
#AlphabetLength INT;
SELECT #StrLength = (SELECT RandomResult FROM dbo.Random) * 4 + 7, #LoopCount = 0, #RandomString = '', #AlphabetLength = LEN(#Alphabet);
WHILE (#LoopCount < #StrLength)
BEGIN
SELECT #RandomString = #RandomString + SUBSTRING(#Alphabet, CONVERT(INT, (SELECT RandomResult FROM dbo.Random) * #AlphabetLength), 1)
SET #LoopCount = #LoopCount + 1;
END
RETURN #RandomString;
END
Now I want to use this FUNCTION called 'WordGenerator' in the INSERT INTO Clause however it simply does not work because I am not able to call it.
How can I call my Function that every single time it is supposed to generate a new, random word?
Using SELECT TOP 1 RandomWord FROM dbo.WordGenerator() does not work.
Using SELECT dbo.WordGenerator() does not work.
Using SELECT * FROM dbo.WordGenerator() does not work.
Any ideas?
The issue is that your function expects a parameter that is never used and not passed in. So change it to:
CREATE FUNCTION [dbo].[WordGenerator] ()
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #Alphabet VARCHAR(33) = 'abcdefghijklmnoprstuówxyzęąśłżźćń',
DECLARE #StrLength INT;
DECLARE #LoopCount INT;
DECLARE #RandomString VARCHAR(MAX);
DECLARE #AlphabetLength INT;
SELECT #StrLength = RandomResult * 4 + 7, #LoopCount = 0, #RandomString = '', #AlphabetLength = LEN(#Alphabet)
FROM dbo.Random;
WHILE #LoopCount < #StrLength
BEGIN
SELECT #RandomString = #RandomString + SUBSTRING(#Alphabet, CONVERT(INT, RandomResult * #AlphabetLength), 1)
FROM dbo.Random;
SET #LoopCount += 1;
END;
RETURN #RandomString;
END;
And then just call it like that: SELECT dbo.WordGenerator(); That's the way you call a scalar function.
SELECT * FROM dbo.WordGenerator(); this way you're calling table valued functions.
UPDATE SampleTable
SET Schemaname = #SchemaName,
SchemaCode = #SchemaCode,
ForeignKeyColumn = #ForeignKeyColumn,
IsChildSchema = #IsChildSchema,
ModifiedBy = #ModifiedBy,
ModifiedDate = #ModifiedDate
WHERE
DataSchemaID = #DataSchemaId
My #ForeignKeyColumn parameter is
2233^SITE_CLM_NUMBER,2236^SITE_ID_N,
Can anyone help me in updating ForeignKeyColumn='SITE_CLM_NUMBER' where DataSchemaID=2233 and ForeignKeyColumn='SITE_ID_N' where DataSchemaID=2236
It's easy to pass multiple parameter values to a query, using a Table Valued Parameter. These are available in all versions of SQL Server since 2008.
First, you need to create a Table type with the fields you want:
CREATE TYPE dbo.KeyValueType AS TABLE
( Key int, Value nvarchar(50) )
This allows you to specify a parameter of type KeyValueType with the Key/Value combinations you want, eg #updatedColumns.
You can join the target table with the TVP to update rows with matching DataSchemaID values:
Create Procedure UpdateSchemas(...., #updatedColumns dbo.KeyValueType)
UPDATE SampleTable
SET
Schemaname=#SchemaName
,SchemaCode=#SchemaCode
,ForeignKeyColumn=t.Value
,IsChildSchema=#IsChildSchema
,ModifiedBy=#ModifiedBy
,ModifiedDate=#ModifiedDate
FROM SampleTable
INNER JOIN #updatedColumns t
ON t.ID=DataSchemaID
You can add an SplitString function, like this one :
How to Split String by Character into Separate Columns in SQL Server
CREATE FUNCTION [dbo].[Split]
(
#String varchar(max)
,#Delimiter char
)
RETURNS #Results table
(
Ordinal int
,StringValue varchar(max)
)
as
begin
set #String = isnull(#String,'')
set #Delimiter = isnull(#Delimiter,'')
declare
#TempString varchar(max) = #String
,#Ordinal int = 0
,#CharIndex int = 0
set #CharIndex = charindex(#Delimiter, #TempString)
while #CharIndex != 0 begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,substring(#TempString, 0, #CharIndex)
)
set #TempString = substring(#TempString, #CharIndex + 1, len(#TempString) - #CharIndex)
set #CharIndex = charindex(#Delimiter, #TempString)
end
if #TempString != '' begin
set #Ordinal += 1
insert #Results values
(
#Ordinal
,#TempString
)
end
return
end
Now you can easily extract each part of your input parameter.
declare #I int;
declare #TMP nvarchar(255);
set #I = 1;
set #TMP = null;
set #TMP = (select StringValue from Split(#ForeignKeyCoumn, ',') where Ordinal = 1);
while #TMP <> null
begin
set #ForeignKeyColumn = (select StringValue from Split(#TMP, '^') where Ordinal = 1);
set #DataSchemaID = (select StringValue from Split(#TMP, '^') where Ordinal = 2);
-- Update here your table with #ForeignKeyColumn and #DataSchemaID values
set #I = #I + 1;
set #TMP = null;
set #TMP = (select StringValue from Split(#ForeignKeyCoumn, ',') where Ordinal = #I);
end
PS: If your are using SQL Server 2016 it already includes an SplitString function, so you won't need to add your own. https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
My code is:
Declare #Users table(Names nvarchar(50) not null, Flag int);
Declare #ValidUsers table(Names nvarchar(50) not null);
Declare #Office int;
Declare #NumberOfRecords int;
Declare #Count int;
Declare #IntCount int;
Declare #Binary AS nvarchar(16);
Declare #bit as nvarchar(1);
Declare #PermissionSub as nvarchar(1);
Declare #Permission as nvarchar(16);
Declare #ShouldContinue as bit;
set #ShouldContinue = 1;
set #Permission = '0001111111111111'; /* going to pass this value */
set #Count = '1';
set #IntCount = '1';
set #Office = '3'; /* going to pass this value */
Insert into #Users
Select
dbUser.usrFullName, udFeeEarnerLicence.purchaseFlag
From
[OMSBB].[dbo].[udFeeEarnerLicence]
Inner Join
[OMSBB].[dbo].[dbUser] ON udFeeEarnerLicence.feeUsrId = dbUser.usrID
Where
dbUser.brId = #Office;
select #NumberOfRecords = COUNT(Flag) from #Users;
DECLARE #Flag AS int;
select #Flag = Flag from #Users;
while(#Count <= #NumberOfRecords)
begin
WITH CTE AS
(
SELECT
Flag, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM
#Users
)
SELECT TOP(1) #Flag = Flag -- this TOP(1) is just a fail-safe
FROM CTE
WHERE RwNr = #Count;
WITH A AS
(
SELECT 0 AS ORD, #Flag AS NUMBER, CAST('' AS VARCHAR(20)) AS BITS
UNION ALL
SELECT ORD+1, NUMBER/2, CAST(BITS+CAST(NUMBER%2 AS VARCHAR(20)) AS VARCHAR(20))
FROM A
WHERE NUMBER > 0
)
SELECT #Binary = RIGHT('000000000000000'+ CASE WHEN BITS='' THEN '0' ELSE REVERSE(BITS) END,16)
FROM A
WHERE NUMBER = 0;
WHILE (#IntCount <= 16)
BEGIN
select #bit = SUBSTRING(#Binary, #IntCount, #IntCount + 1);
select #PermissionSub = SUBSTRING(#Permission, #IntCount, #IntCount + 1);
if(#PermissionSub = '1' and #bit != '1') /* if Permission selection is required and user does not have permission*/
begin
SET #ShouldContinue = 0;
break;
end
end
Set #IntCount = 0;
if(#ShouldContinue = 0)
begin
continue;
end
; WITH CTE AS
(
SELECT Names, ROW_NUMBER() OVER (ORDER BY Flag) AS RwNr
FROM #Users
)
INSERT INTO #ValidUsers
SELECT Names
FROM CTE
WHERE RwNr = #Count;
end
select * from #ValidUsers
I will be adapting this code to use it inside of an SSRS report so that's why there are comments on some parameters saying that I will be passing the parameters. This code at its basics finds all users who are from a specified office and have the specified permissions. The permission a user has are set in 5 flags in this example I'm using the purchaseFlag. This value is an int and it calculated by creating an order of permissions and set their bit values to create a string of 0's and 1's and then converting that binary number into a decimal for example '8191' which the binary value of would be '0001111111111111'. I use two while loops in this one to go through the users and the other to go through each of the 16 characters in the permissions. My issue is that this I'm almost certain that this query works but it takes so long to run that I haven't seen the result of it yet and people have recommended that I use sets instead.
Using only SQL Server 2008 R2 (this is going to be in a stored proc), how can I determine if two variables of type XML are equivalent?
Here is what I want to do:
DECLARE #XmlA XML
DECLARE #XmlB XML
SET #XmlA = '[Really long Xml value]'
SET #XmlB = '[Really long Xml value]'
IF #XmlA = #XmlB
SELECT 'Matching Xml!'
But as you probably know, it returns:
Msg 305, Level 16, State 1, Line 7 The XML data type cannot be
compared or sorted, except when using the IS NULL operator.
I can convert to VarChar(MAX) and compare, but that only compares the first 2MB. Is there another way?
Check this SQL function:
CREATE FUNCTION [dbo].[CompareXml]
(
#xml1 XML,
#xml2 XML
)
RETURNS INT
AS
BEGIN
DECLARE #ret INT
SELECT #ret = 0
-- -------------------------------------------------------------
-- If one of the arguments is NULL then we assume that they are
-- not equal.
-- -------------------------------------------------------------
IF #xml1 IS NULL OR #xml2 IS NULL
BEGIN
RETURN 1
END
-- -------------------------------------------------------------
-- Match the name of the elements
-- -------------------------------------------------------------
IF (SELECT #xml1.value('(local-name((/*)[1]))','VARCHAR(MAX)'))
<>
(SELECT #xml2.value('(local-name((/*)[1]))','VARCHAR(MAX)'))
BEGIN
RETURN 1
END
---------------------------------------------------------------
--Match the value of the elements
---------------------------------------------------------------
IF((#xml1.query('count(/*)').value('.','INT') = 1) AND (#xml2.query('count(/*)').value('.','INT') = 1))
BEGIN
DECLARE #elValue1 VARCHAR(MAX), #elValue2 VARCHAR(MAX)
SELECT
#elValue1 = #xml1.value('((/*)[1])','VARCHAR(MAX)'),
#elValue2 = #xml2.value('((/*)[1])','VARCHAR(MAX)')
IF #elValue1 <> #elValue2
BEGIN
RETURN 1
END
END
-- -------------------------------------------------------------
-- Match the number of attributes
-- -------------------------------------------------------------
DECLARE #attCnt1 INT, #attCnt2 INT
SELECT
#attCnt1 = #xml1.query('count(/*/#*)').value('.','INT'),
#attCnt2 = #xml2.query('count(/*/#*)').value('.','INT')
IF #attCnt1 <> #attCnt2 BEGIN
RETURN 1
END
-- -------------------------------------------------------------
-- Match the attributes of attributes
-- Here we need to run a loop over each attribute in the
-- first XML element and see if the same attribut exists
-- in the second element. If the attribute exists, we
-- need to check if the value is the same.
-- -------------------------------------------------------------
DECLARE #cnt INT, #cnt2 INT
DECLARE #attName VARCHAR(MAX)
DECLARE #attValue VARCHAR(MAX)
SELECT #cnt = 1
WHILE #cnt <= #attCnt1
BEGIN
SELECT #attName = NULL, #attValue = NULL
SELECT
#attName = #xml1.value(
'local-name((/*/#*[sql:variable("#cnt")])[1])',
'varchar(MAX)'),
#attValue = #xml1.value(
'(/*/#*[sql:variable("#cnt")])[1]',
'varchar(MAX)')
-- check if the attribute exists in the other XML document
IF #xml2.exist(
'(/*/#*[local-name()=sql:variable("#attName")])[1]'
) = 0
BEGIN
RETURN 1
END
IF #xml2.value(
'(/*/#*[local-name()=sql:variable("#attName")])[1]',
'varchar(MAX)')
<>
#attValue
BEGIN
RETURN 1
END
SELECT #cnt = #cnt + 1
END
-- -------------------------------------------------------------
-- Match the number of child elements
-- -------------------------------------------------------------
DECLARE #elCnt1 INT, #elCnt2 INT
SELECT
#elCnt1 = #xml1.query('count(/*/*)').value('.','INT'),
#elCnt2 = #xml2.query('count(/*/*)').value('.','INT')
IF #elCnt1 <> #elCnt2
BEGIN
RETURN 1
END
-- -------------------------------------------------------------
-- Start recursion for each child element
-- -------------------------------------------------------------
SELECT #cnt = 1
SELECT #cnt2 = 1
DECLARE #x1 XML, #x2 XML
DECLARE #noMatch INT
WHILE #cnt <= #elCnt1
BEGIN
SELECT #x1 = #xml1.query('/*/*[sql:variable("#cnt")]')
--RETURN CONVERT(VARCHAR(MAX),#x1)
WHILE #cnt2 <= #elCnt2
BEGIN
SELECT #x2 = #xml2.query('/*/*[sql:variable("#cnt2")]')
SELECT #noMatch = dbo.CompareXml( #x1, #x2 )
IF #noMatch = 0 BREAK
SELECT #cnt2 = #cnt2 + 1
END
SELECT #cnt2 = 1
IF #noMatch = 1
BEGIN
RETURN 1
END
SELECT #cnt = #cnt + 1
END
RETURN #ret
END
Here is the Source
The function fails to compare XML fragments e.g. when there is not a single root element, like:
SELECT dbo.CompareXml('<data/>', '<data/><data234/>')
In order to fix this, you must wrap your XMLs in root elements, when they are passed to the function or edit the function to do this. For, example:
SELECT dbo.CompareXml('<r><data/></r>', '<r><data/><data234/></r>')
There are many different ways of comparing two XML documents, and a lot depends on what kind of differences you want to tolerate: you definitely need to tolerate differences in encoding, attribute order, insignificant whitespace, numeric character references, and use of attribute delimiters, and you should probably also tolerate differences in use of comments, namespace prefixes, and CDATA. So comparing two XML documents as strings is definitely not a good idea - unless you invoke XML canonicalization first.
For many purposes the XQuery deep-equals() function does the right thing (and is more-or-less equivalent to comparing the canonical forms of the two XML documents). I don't know enough about Microsoft's SQL Server implementation of XQuery to tell you how to invoke this from the SQL level.
You may cast fields to varbinary(max), hash them and compare hashes. But you definitely miss if XMLs are equivalent but not identical
To calculate hash you may use either CLR function:
using System;
using System.Data.SqlTypes;
using System.IO;
namespace ClrHelpers
{
public partial class UserDefinedFunctions {
[Microsoft.SqlServer.Server.SqlFunction]
public static Guid HashMD5(SqlBytes data) {
System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
md5.Initialize();
int len = 0;
byte[] b = new byte[8192];
Stream s = data.Stream;
do {
len = s.Read(b, 0, 8192);
md5.TransformBlock(b, 0, len, b, 0);
} while(len > 0);
md5.TransformFinalBlock(b, 0, 0);
Guid g = new Guid(md5.Hash);
return g;
}
};
}
Or sql function:
CREATE FUNCTION dbo.GetMyLongHash(#data VARBINARY(MAX))
RETURNS VARBINARY(MAX)
WITH RETURNS NULL ON NULL INPUT
AS
BEGIN
DECLARE #res VARBINARY(MAX) = 0x
DECLARE #position INT = 1, #len INT = DATALENGTH(#data)
WHILE 1 = 1
BEGIN
SET #res = #res + HASHBYTES('MD5', SUBSTRING(#data, #position, 8000))
SET #position = #position+8000
IF #Position > #len
BREAK
END
WHILE DATALENGTH(#res) > 16 SET #res= dbo.GetMyLongHash(#res)
RETURN #res
END
If you can use SQL CLR, I suggest to write a function using XNode.DeepEquals Method:
var xmlTree1 = new XElement("Root",
new XAttribute("Att1", 1),
new XAttribute("Att2", 2),
new XElement("Child1", 1),
new XElement("Child2", "some content")
);
var xmlTree2 = new XElement("Root",
new XAttribute("Att1", 1),
new XAttribute("Att2", 2),
new XElement("Child1", 1),
new XElement("Child2", "some content")
);
Console.WriteLine(XNode.DeepEquals(xmlTree1, xmlTree2));
If you cannot, you can write your own function (see SQL FIDDLE EXAMPLE):
CREATE function [dbo].[udf_XML_Is_Equal]
(
#Data1 xml,
#Data2 xml
)
returns bit
as
begin
declare
#i bigint, #cnt1 bigint, #cnt2 bigint,
#Sub_Data1 xml, #Sub_Data2 xml,
#Name varchar(max), #Value1 nvarchar(max), #Value2 nvarchar(max)
if #Data1 is null or #Data2 is null
return 1
--=========================================================================================================
-- If more than one root - recurse for each element
--=========================================================================================================
select
#cnt1 = #Data1.query('count(/*)').value('.','int'),
#cnt2 = #Data1.query('count(/*)').value('.','int')
if #cnt1 <> #cnt2
return 0
if #cnt1 > 1
begin
select #i = 1
while #i <= #cnt1
begin
select
#Sub_Data1 = #Data1.query('/*[sql:variable("#i")]'),
#Sub_Data2 = #Data2.query('/*[sql:variable("#i")]')
if dbo.udf_XML_Is_Equal_New(#Sub_Data1, #Sub_Data2) = 0
return 0
select #i = #i + 1
end
return 1
end
--=========================================================================================================
-- Comparing root data
--=========================================================================================================
if #Data1.value('local-name(/*[1])','nvarchar(max)') <> #Data2.value('local-name(/*[1])','nvarchar(max)')
return 0
if #Data1.value('/*[1]', 'nvarchar(max)') <> #Data2.value('/*[1]', 'nvarchar(max)')
return 0
--=========================================================================================================
-- Comparing attributes
--=========================================================================================================
select
#cnt1 = #Data1.query('count(/*[1]/#*)').value('.','int'),
#cnt2 = #Data1.query('count(/*[1]/#*)').value('.','int')
if #cnt1 <> #cnt2
return 0
if exists (
select *
from
(
select
T.C.value('local-name(.)', 'nvarchar(max)') as Name,
T.C.value('.', 'nvarchar(max)') as Value
from #Data1.nodes('/*[1]/#*') as T(C)
) as D1
full outer join
(
select
T.C.value('local-name(.)', 'nvarchar(max)') as Name,
T.C.value('.', 'nvarchar(max)') as Value
from #Data2.nodes('/*[1]/#*') as T(C)
) as D2
on D1.Name = D2.Name
where
not
(
D1.Value is null and D2.Value is null or
D1.Value is not null and D2.Value is not null and D1.Value = D2.Value
)
)
return 0
--=========================================================================================================
-- Recursively running for each child
--=========================================================================================================
select
#cnt1 = #Data1.query('count(/*[1]/*)').value('.','int'),
#cnt2 = #Data2.query('count(/*[1]/*)').value('.','int')
if #cnt1 <> #cnt2
return 0
select #i = 1
while #i <= #cnt1
begin
select
#Sub_Data1 = #Data1.query('/*/*[sql:variable("#i")]'),
#Sub_Data2 = #Data2.query('/*/*[sql:variable("#i")]')
if dbo.udf_XML_Is_Equal(#Sub_Data1, #Sub_Data2) = 0
return 0
select #i = #i + 1
end
return 1
END
I stumbled upon this fairly comprehensive article which goes into more detail of actually comparing the CONTENT of 2 XML entries to determine whether they are the same. It makes sense, as the ordering of attributes in nodes CAN differ, even though their values are exactly the same. I'd recommend you read through it and even implement the function to see if it works for you... I tried it out quickly and it seemed to work for me?