Linear interpolation with Variables in T-SQL - sql-server

Input to my procedur
#time datetime -- Example: 27.07.2015 01:00
With some SELECT staments I get the following information
#prevTime datetime -- Example: 27.07.2015 00:00
#prevValue real -- Example: 1
#nextTime datetime -- Example: 27.07.2015 02:00
#nextValue real -- Example: 3
Now I want to calculate
#value real -- In this Example the result should be: 2
i want to use "linear interpolation", see here: https://en.wikipedia.org/wiki/Linear_interpolation
Can someone tell me, how this gets done best? (Can i just convert the datetime into real?)
EDIT:
My first Quick&Dirty Idea:
SET #value = #prevValue + ((#nextValue-#prevValue)/(CAST(#nextTime as real)-CAST(#prevTime as real))) * (CAST(#time as real)-CAST(#prevTime as real));
Don't work, because datetime -> real is not precise
My second Quick&Dirty Idea:
SET #value = #prevValue + ((#nextValue-#prevValue)/(DATEDIFF(second,#nextTime,GETDATE())-DATEDIFF(second,#prevTime,GETDATE()))) * (DATEDIFF(second,#time,GETDATE())-DATEDIFF(second,#prevTime,GETDATE()));
Work's, but i would be happy if someone told me a nicer way

I'd submitted such code previously to SQL Server central as a code example at http://www.sqlservercentral.com/Forums/Topic872361-392-1.aspx
The code in question is below and performs standard gauss-jordan elimination to reduce supplied data points to an equation, then extrapolates accordingly:
-- Point type
CREATE TYPE Point AS TABLE (XCoordinate FLOAT, YCoordinate FLOAT);
GO
-- Curve fitting coefficient type
CREATE TYPE Coefficient AS TABLE (Multiplier FLOAT, PowerOfX INT);
GO
/**
* Function: fn_CurveFitPoints
* Author: Steven James Gray [ steve#cobaltsoftware.net ]
* Date: 24th February, 2010
* Version: 1.0
* Description:
* Takes a series of points in a table variant in POINT (X float, Y float) format and
* computes the n-1 power series curve fit using the Gauss-Jordan elimination process.
* Return value is a series of Multiplier, Power elements that can be used for producing
* an estimated Y value for any X value.
*
* Please refer to fn_ExtrapolateYValue(#xvalue, #coefficients) for a simple implementation
* of how to use the output of this function.
**/
CREATE FUNCTION dbo.fn_CurveFitPoints
(
#PointsToCurveFit POINT readonly
)
RETURNS #Result TABLE (Multiplier FLOAT, PowerOfX INT)
AS
BEGIN
-- ==========================================================================================
-- Stage 1 - Convert #PointsToFit into a matrix
-- ==========================================================================================
DECLARE #Matrix TABLE (MatrixRow INT, MatrixColumn INT, MatrixValue FLOAT);
DECLARE #TotalPoints INT = (SELECT COUNT(1) FROM #PointsToCurveFit);
WITH NumberProjectionCTE(CurrentNumber)
AS
(
SELECT 1
UNION ALL
SELECT 1+CurrentNumber FROM NumberProjectionCTE WHERE CurrentNumber < #TotalPoints
) INSERT INTO #Matrix
SELECT
Sequence-1, -- Each point gets it's own row
PWR.CurrentNumber-1, -- Column per power of X
CASE
WHEN PWR.CurrentNumber = 1 -- 1st column is X^0 = 1 Always
THEN 1
ELSE POWER(XCoordinate,PWR.CurrentNumber-1) -- Raise nth column to power n-1.
END
FROM
NumberProjectionCTE PWR, -- Cross join numeric point data and column indexes
(SELECT
ROW_NUMBER() OVER (ORDER BY XCoordinate, YCoordinate) AS Sequence,
XCoordinate,
YCoordinate
FROM
#PointsToCurveFit
) ValueData;
/* Append Y values as nth column */
INSERT INTO #Matrix
SELECT
ROW_NUMBER() OVER (ORDER BY XCoordinate, YCoordinate) - 1 AS Sequence,
#TotalPoints,
YCoordinate
FROM
#PointsToCurveFit;
-- ==========================================================================================
-- Stage 2 - Compute row echelon form of matrix
-- ==========================================================================================
DECLARE #lead INT = 0, #index INT = 0, #current FLOAT;
DECLARE #Rows INT = (SELECT MAX(MatrixRow) FROM #Matrix);
DECLARE #Columns INT = (SELECT MAX(MatrixColumn) FROM #Matrix);
DECLARE #Solved INT -- 0=Unsolvable, 1 = Solved
DECLARE #R INT = 0
WHILE #R <= #Rows
BEGIN
IF #Columns <= #lead
BEGIN
-- Cannot solve this one
SET #Solved = 0;
BREAK;
END;
SET #index = #R;
-- Determine if any row swaps are needed.
WHILE (SELECT MatrixValue FROM #Matrix WHERE MatrixRow = #index AND MatrixColumn = #lead) = 0
BEGIN
SET #index = #index + 1;
IF #Rows = #index
BEGIN
SET #index = #R;
SET #lead = #lead + 1;
IF #Columns = #lead
BEGIN
-- Cannot solve
SET #Solved = 0;
BREAK;
END;
END;
END;
-- Move this row to the correct position if needed.
IF #index <> #R
BEGIN
-- Swap rows
UPDATE #Matrix
SET MatrixRow = CASE MatrixRow
WHEN #R THEN #index
WHEN #index THEN #R
END
WHERE MatrixRow IN (#index, #R);
END;
-- Divide this row by it's lead column value, so that this row's lead is 1 (this will actually multiply/increase the value if lead <0)
DECLARE #Divisor FLOAT = (SELECT MatrixValue FROM #Matrix WHERE MatrixRow = #R AND MatrixColumn = #lead);
If #Divisor <> 1
BEGIN
UPDATE #Matrix SET MatrixValue = MatrixValue / #Divisor WHERE MatrixRow = #R;
END;
-- Update other rows and divide them by the appropriate multiple of this row in order to zero the current lead column.
UPDATE I
SET
MatrixValue = I.MatrixValue - (M.MatrixValue * R.MatrixValue)
FROM
#Matrix I
INNER JOIN #Matrix M ON M.MatrixRow = I.MatrixRow AND M.MatrixColumn = #lead
INNER JOIN #Matrix R ON R.MatrixColumn = I.MatrixColumn AND R.MatrixRow = #R AND R.MatrixRow <> I.MatrixRow
SET #lead = #lead + 1;
-- Move to next
SET #R = #R + 1;
END;
-- If we didn't bomb out, we're solved.
IF #Solved IS NULL
BEGIN
SET #Solved = 1
END;
-- ==========================================================================================
-- Stage 3 - Produce coefficients list (The final colum when in REF)
-- ==========================================================================================
IF #Solved = 1
BEGIN
INSERT INTO #Result (Multiplier, PowerOfX)
SELECT
MatrixValue,
MatrixRow
FROM #Matrix
WHERE MatrixColumn = #Columns;
END;
RETURN;
END;
GO
CREATE FUNCTION dbo.fn_ExtrapolateYValue
(
#XValue FLOAT,
#Coefficients Coefficient readonly
)
RETURNS FLOAT
AS
BEGIN
RETURN (SELECT SUM(Multiplier * POWER(#XValue, PowerOfX)) FROM #Coefficients);
END
For example:
DECLARE #PointsToCurveFit Point
-- A few simple X/Y values
INSERT INTO #PointsToCurveFit SELECT 1 , 6
INSERT INTO #PointsToCurveFit SELECT 2 , 3
INSERT INTO #PointsToCurveFit SELECT 3 , 2
-- Calculate the curve fitting coefficients
DECLARE #Coefficients Coefficient
INSERT INTO #Coefficients SELECT * FROM dbo.fn_CurveFitPoints(#PointsToCurveFit);
-- Shows that y= 11x^0 + 6x + x^2
SELECT * FROM #Coefficients;
-- Show the values for X=-5 to 5
WITH NumberCTE(Number)
AS
(
SELECT -5
UNION ALL
SELECT 1 + Number FROM NumberCTE WHERE Number < 5
) SELECT
Number AS XValue,
dbo.fn_ExtrapolateYValue(Number, #Coefficients) AS YValue
FROM NumberCTE;
In the code specified, I extrapolate the function of the curve from -5 to +5 on the X axis range.

I had this quest for enginering system, calucalting welding strength and here is how I solved it - clean and simple.
CREATE TABLE WeldStrengthReduction
(
[Temperature] numeric(18,4) NOT NULL ,
[Reduction] numeric(18,4) NOT NULL ,
);
GO
insert into WeldStrengthReduction (Temperature,Reduction)
values
(510,1),
(538,0.95),
(566,0.91),
(593,0.86),
(621,0.82),
(649,0.77),
(677,0.73),
(704,0.68),
(732,0.64),
(760,0.59),
(788,0.55),
(816,0.5);
Go
Create Function WSRF(#Tempreture Numeric(18,4) ) returns Numeric(18,4)
as
begin
declare
#X1 Numeric(18,4),
#X2 Numeric(18,4),
#X3 Numeric(18,4),
#Y1 Numeric(18,4),
#Y2 Numeric(18,4),
#Y3 Numeric(18,4),
#pointer int
set #X2 = #Tempreture
declare #Templist table (id int IDENTITY(1,1), temp numeric(18,4), red numeric(18,4))
insert into #Templist select Temperature,Reduction from WeldStrengthReduction order by Temperature
select top 1 #X3 = temp, #Y3 = red, #pointer = id from #Templist where temp >= #Tempreture
if #pointer = 1 return #Y3 -- if incomming tempereture is below lowest, return according to lowewst temp
if #pointer is null return null -- if incomming tempereture is above highest, return null
select #X1 = temp, #Y1 = red from #Templist where id = #pointer - 1
set #Y2 = ((#X2-#X1)*(#Y3-#Y1))/(#X3 - #X1) + #Y1
return #Y2
end;
Go
select WSRF(772);
select WSRF(300);
select WSRF(1200);

Related

Struggling to convert my sql query to be set based instead of using while loops

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.

update value based on row number?

i'm pretty new to SQL and stored procedures and i'm a bit stuck - so any help would be appreciated
how do i loop through each row and assign it the random value i'm generating?
Here is my Storedproc:
CREATE PROCEDURE StoredProc8
AS
BEGIN
DECLARE #total INT
DECLARE #Count INT = 0
DECLARE #Random INT = 0
SELECT #total = COUNT(CustomerID) FROM Customers
WHILE(#Count<= #total)
BEGIN
SELECT #Random = 2 * RAND()
EXEC ('update Customers set col1= ' + #Random )
SELECT #Count = #Count+1
END
END
If you simple need to assign 0 or 1 randomly - you can use RAND() with random seed:
UPDATE Customers SET COL1 = RAND(CHECKSUM(NEWID()))*2
Demo: http://sqlfiddle.com/#!3/31699/9

SQL Server: Finding Nearest Location

Given the following SQL which returns a list of airports based on a given city, how would I further enhance it to sort the results by what's nearest to me? It seems like it should be simple enough but it eludes me.
DECLARE #latRange float
DECLARE #LongRange float
DECLARE #LowLatitude float
DECLARE #HighLatitude float
DECLARE #LowLongitude float
DECLARE #HighLongitude float
DECLARE #Radius float = 100
DECLARE #istartlat float
DECLARE #istartlong float
Select #istartlat=Latitude, #istartlong=Longitude from Lookup where PlaceName = '"Franklin"' and StateCode = '"AR"'
Select #latRange = #Radius / ((6076 / 5280) * 60)
Select #LongRange = #Radius / (((COS((#istartlat * 3.14592653589 / 180)) * 6076.) / 5280. * 60))
Select #LowLatitude = #istartlat - #latRange
Select #HighLatitude = #istartlat + #latRange
Select #LowLongitude = #istartlong - #LongRange
Select #HighLongitude = #istartlong + #LongRange
Select a.City, a.State, a.AirportCode, a.AirportName, a.CountryCode
from PFM_Airport a
where (a.Latitude <= #HighLatitude) and (a.Latitude >= #LowLatitude) and (a.Longitude >= #LowLongitude)
and (a.Longitude <= #HighLongitude)
--and a.CountryCode in ('USA', 'CANADA')
order by a.Latitude, a.Longitude;
#hatchet is right.. Assuming your instance of SQL Server support geography spatial data then you should consider using that data type for the calculations, you also may find useful some of this code, just replace the table names, conditionals and pagination as needed:
ALTER PROCEDURE [dbo].[SP_NearestPOIReloaded]
(
-- Add the parameters for the stored procedure here
#LAT float,
#LNG float,
#DISTANCE int,
#CURRENTPAGE Int,
#PAGESIZE Int,
#COUNT int OUTPUT
)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
/*for pagination*/
SET #COUNT = (SELECT COUNT(*) FROM dbo.Lugares as [L] WHERE dbo.DistanceBetween(#LAT, #LNG, [L].lat, [L].lng) < #DISTANCE)
/*paginated resultset*/
SELECT * FROM (
SELECT ROW_NUMBER()Over(Order by dbo.DistanceBetween(#LAT, #LNG, [L].lat, [L].lng) Asc) As [RowNum], dbo.DistanceBetween(#LAT, #LNG, [L].lat, [L].lng) as [distance], [L].*, [E].name as [empresaName], [U].userName as [userName]
FROM dbo.Lugares as [L], dbo.Empresas as [E], dbo.Usuarios as [U]
WHERE dbo.DistanceBetween(#LAT, #LNG, [L].lat, [L].lng) < #DISTANCE AND
[L].usuarioId = [U].id AND [L].empresaId = [E].id
)
AS ResultadoPaginado
WHERE RowNum BETWEEN (#CURRENTPAGE - 1) * #PAGESIZE + 1 AND #CURRENTPAGE * #PAGESIZE
END
this depends on a function called DistanceBetween (if your instance does not support spacial data type, then this is the part you must replace with a variation of your own code):
ALTER FUNCTION [dbo].[DistanceBetween]
(
-- Add the parameters for the function here
#PIVOTE_LAT as float,
#PIVOTE_LNG as float,
#LAT as float,
#LNG as float
)
returns real
as begin
declare #distance real;
declare #PIVOTE_POINT geography = geography::Point(#PIVOTE_LAT,#PIVOTE_LNG, 4326);
declare #POINT geography = geography::Point(#LAT,#LNG, 4326);
set #distance = #PIVOTE_POINT.STDistance(#POINT);
return (#distance);
end
how would I further enhance it to sort the results by what's nearest to me?
I believe that in SQL Server 2000, to sort on a calculated value you must either duplicate the calculation in the ORDER BY clause ORDER BY < the calculation> or you can do this:
select FOO.a, FOO.b, FOO.myvalue
from
(
select a, b, <some calculation> as myvalue
from T
where <some calculation> <= {some value}
) as FOO
order by FOO.myvalue
P.S. But in later versions of SQL you can sort on a column alias.
In any case, you must have a column to contain the calculated distance.

T-SQL: How can I compare two variables of type XML when length > VarChar(MAX)?

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?

Converting text to binary code - TSQL

I found a few threads on this using the search feature, but nothing for a purely T-SQL solution.
the need - A system is storing a weekly schedule as 0's and 1's in a string format to represent a week. 1 means yes, 0 means no....so 1100111 means sunday yes (first one), Monday yes (second 1), Tuesday no (the 0)...etc.
Short question - How do I go from an ascii char such as '>' to it's hex code '3E' and ultimately to it's binary '00111110' representation?
Long question - I'm extracting from a flat file system that stores a table as:
ID int,
priority_1 varchar(2)
...
It actually goes to priroity_128 (silly flat file), but I'm only interested in 1-7 and the logic for one should be easily reused for the others. I unfortunately have no control over this part of the extract. The values I get look like:
1 >
2 (edit, I actually put a symbol here that I receive from the system but the forum doesn't like.)
3 |
4 Y
I get the feeling these are appearing as their ascii chars because of the conversion as I extract.
select convert(varbinary,'>',2)
This returns 0x3E. The 0x part can be ignored... 3 in binary is 0011 and E is 1110...3E = 00111110. Trim the first 0 and it leaves the 7 bit code that I'm looking for. Unfortunately I have no idea how to express this logic here in T-SQL. Any ideas? I'm thinking as a function would be easiest to use...something like:
select id, binaryversionof(priority_1)
Here's a UDF that will convert from base-10 to any other base, including base-2...
Here's how you can use it:
SELECT YourDatabase.dbo.udf_ConvertFromBase10(convert(varbinary, '>', 2), 2)
Here's what it returns:
111110
And here's the function definition:
CREATE FUNCTION [dbo].[udf_ConvertFromBase10]
(
#num INT,
#base TINYINT
)
RETURNS VARCHAR(255)
AS
BEGIN
-- Check for a null value.
IF (#num IS NULL)
RETURN NULL
-- Declarations
DECLARE #string VARCHAR(255)
DECLARE #return VARCHAR(255)
DECLARE #finished BIT
DECLARE #div INT
DECLARE #rem INT
DECLARE #char CHAR(1)
-- Initialize
SELECT #string = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
SELECT #return = CASE WHEN #num <= 0 THEN '0' ELSE '' END
SELECT #finished = CASE WHEN #num <= 0 THEN 1 ELSE 0 END
SELECT #base = CASE WHEN #base < 2 OR #base IS NULL THEN 2 WHEN #base > 36 THEN 36 ELSE #base END
-- Loop
WHILE #finished = 0
BEGIN
-- Do the math
SELECT #div = #num / #base
SELECT #rem = #num - (#div * #base)
SELECT #char = SUBSTRING(#string, #rem + 1, 1)
SELECT #return = #char + #return
SELECT #num = #div
-- Nothing left?
IF #num = 0 SELECT #finished = 1
END
-- Done
RETURN #return
END
Your solution returns a string of a variable length. Not sure whether it was by design or you simply overlooked that fact.
Anyway, here's my solution, which always returns 7 0s or 1s:
CREATE FUNCTION fnIntTo7Bits (#Value int)
RETURNS varchar(7)
AS BEGIN
DECLARE #Bits varchar(7);
SELECT #Bits = COALESCE(#Bits, '') + CAST(CAST(#Value & number AS bit) AS varchar)
FROM master..spt_values
WHERE type = 'P' AND number IN (1, 2, 4, 8, 16, 32, 64)
ORDER BY number DESC;
RETURN #Bits;
END;
The master..spt_values table is a system table used internally but also accessible to the user. It seems to have been inherited from Sybase so it's a very old tool, which, to my mind, means it won't go too soon.
But if you like, you can use your own number table, which you don't even have to materialise, like this:
...
SELECT #Bits = COALESCE(#Bits, '') + CAST(CAST(#Value & number AS bit) AS varchar)
FROM (
SELECT 1 UNION ALL SELECT 2 UNION ALL
SELECT 4 UNION ALL SELECT 8 UNION ALL
SELECT 16 UNION ALL SELECT 32 UNION ALL SELECT 64
) s (number)
ORDER BY number DESC;
...
Answering my own question...though curious if anyone has something more elegant. I found this unsourced function using google:
CREATE FUNCTION udf_bin_me (#IncomingNumber int)
RETURNS varchar(200)
as
BEGIN
DECLARE #BinNumber VARCHAR(200)
SET #BinNumber = ''
WHILE #IncomingNumber <> 0
BEGIN
SET #BinNumber = SUBSTRING('0123456789', (#IncomingNumber % 2) + 1, 1) + #BinNumber
SET #IncomingNumber = #IncomingNumber / 2
END
RETURN #BinNumber
END
Then use the Ascii function to get the char to it's ascii decimal value:
select dbo.udf_bin_me(ascii('>'))
Seems to be a bit of a run around, but I can work from that. Better solution anyone?
I just whipped this up, it maybe buggy... but it works:
DECLARE #value INT, #binary VARCHAR(10)
SELECT #value = ASCII('m'), #binary = ''
;WITH [BINARY] ([Location], [x], [BIT])
AS
(
-- Base case
SELECT 64, #value, #value % 2
UNION ALL
-- Recursive
SELECT [BINARY].[Location] / 2, [BINARY].[x] / 2, ([BINARY].[x] / 2) % 2
FROM [BINARY]
WHERE [BINARY].[Location] >= 2
)
SELECT #binary = CAST([BIT] AS CHAR(1)) + #binary FROM [BINARY]
SELECT #binary

Resources