Available options with current script > Table or View needed (SQL Server) - sql

So, I have the current script below, but I need to create this as a view and I know you can't use variable within it. I understand there is the option of creating a stored procedure, but I'm not quite sure how to go about.
Create View vwPUs__cwa as (
Declare #salt varchar (25);
DECLARE #Seed int;
DECLARE #LCV tinyint;
DECLARE #CTime DATETIME;
SET #CTime = GETDATE();
SET #Seed = (DATEPART(hh, #Ctime) * 10000000) + (DATEPART(n, #CTime) * 100000)
+ (DATEPART(s, #CTime) * 1000) + DATEPART(ms, #CTime);
SET #LCV = 1;
SET #Salt = CHAR(ROUND((RAND(#Seed) * 94.0) + 32, 3));
WHILE (#LCV < 25)
BEGIN
SET #Salt = #Salt + CHAR(ROUND((RAND() * 94.0) + 32, 3));
SET #LCV = #LCV + 1;
END;
SELECT dc.id,
sys.Fn_varbintohexsubstring(0, Hashbytes('SHA2_512', #salt + dc.decrypt),
1, 0)
AS SaltyHashbrowns,
dc.firstname,
dc.lastname,
dc.statusname,
dc.processingunit,
dc.processingunitnumber
FROM vwdecrypt_cwa dc
WHERE dc.processingunitnumber IN ( 0201301, 0201302, 0201303, 0201308,
0201309, 0201311, 0201312 )
;

The Prcoedure you need to create will be along the lines of
create procedure PUs__cwa
as
set nocount on
declare #salt varchar (25), #Seed int, #LCV tinyint=1, #CTime datetime=GetDate()
set #Seed = (DatePart(hh, #CTime) * 10000000) + (DatePart(n, #CTime) * 100000)
+ (DatePart(s, #CTime) * 1000) + DatePart(ms, #CTime);
set #salt = Char(Round((Rand(#Seed) * 94.0) + 32, 3));
while (#LCV < 25)
begin
set #salt = #salt + Char(Round((Rand() * 94.0) + 32, 3));
set #LCV += 1;
end;
select dc.id,
sys.fn_varbintohexsubstring(0, HashBytes('SHA2_512', #salt + dc.decrypt), 1, 0) as SaltyHashbrowns,
dc.firstname,
dc.lastname,
dc.statusname,
dc.processingunit,
dc.processingunitnumber
from vwdecrypt_cwa dc
where dc.processingunitnumber in ( 0201301, 0201302, 0201303, 0201308, 0201309, 0201311, 0201312 )
And you run it in tsql with exec PUs__cwa

An inline Table-Valued Function would work here. It is effectively treated by the compiler as a parameterized view.
You cannot place RAND() in a UDF, so we must do this in a stored procedure and pass it through:
CREATE OR ALTER FUNCTION vwPUs__cwa (#salt varchar (25))
RETURNS TABLE
AS RETURN
(
SELECT dc.id,
sys.Fn_varbintohexsubstring(0, Hashbytes('SHA2_512', #salt + dc.decrypt),
1, 0)
AS SaltyHashbrowns,
dc.firstname,
dc.lastname,
dc.statusname,
dc.processingunit,
dc.processingunitnumber
FROM vwdecrypt_cwa dc
WHERE dc.processingunitnumber IN ( 0201301, 0201302, 0201303, 0201308,
0201309, 0201311, 0201312 )
);
GO
CREATE OR ALTER PROCEDURE PUs__cwa
AS
SET NOCOUNT, XACT_ABORT ON;
DECLARE #salt varchar(25);
DECLARE #Seed int = (DATEPART(hh, #Ctime) * 10000000) + (DATEPART(n, #CTime) * 100000) + (DATEPART(s, #CTime) * 1000) + DATEPART(ms, #CTime);
DECLARE #LCV tinyint = 1;
DECLARE #CTime DATETIME = GETDATE();
WHILE (#LCV < 25)
BEGIN
SET #LCV = #LCV + 1;
SET #Salt = CONCAT(#Salt, CHAR(ROUND((RAND() * 94.0) + 32, 3)));
END;
SELECT *
FROM vwPUs__cwa (#salt);
GO
You could also run the salt code in a client application, and pass it through as a parameter to an ad-hoc batch:
SELECT *
FROM vwPUs__cwa (#salt);

Related

Microsoft SQL query to view

I have this complex query that i want to turn into a view.
This query comes from https://snippets.cacher.io/snippet/3e84b01b7d52b4ca7807 and i want to save it in a view or even as a table if possible.
`
/*##=============================================*/
/*## QUERY BODY */
/*##=============================================*/
/* #region QueryBody */
/* Testing variables !! Need to be commented for Production !! */
-- DECLARE #UserSIDs AS NVARCHAR(10) = 'Disabled';
-- DECLARE #CollectionID AS NVARCHAR(10) = 'SMS00001';
-- DECLARE #Locale AS INT = 2;
-- DECLARE #Categories AS NVARCHAR(250) = 'Tools';
-- DECLARE #Compliant AS INT = 0;
-- DECLARE #Targeted AS INT = 1;
-- DECLARE #Superseded AS INT = 0;
-- DECLARE #ArticleID AS NVARCHAR(10) = '';
-- DECLARE #ExcludeArticleIDs AS NVARCHAR(250) = '';
/* Variable declaration */
DECLARE #LCID AS INT = dbo.fn_LShortNameToLCID(#Locale);
DECLARE #HelperFunctionExists AS INT = 0;
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* Check for helper function */
IF OBJECT_ID('[dbo].[ufn_CM_GetNextMaintenanceWindow]') IS NOT NULL
SET #HelperFunctionExists = 1;
/* Initialize HealthState descriptor table */
DECLARE #HealthState TABLE (
BitMask INT
, StateName NVARCHAR(250)
)
/* Populate HealthState table */
INSERT INTO #HealthState (BitMask, StateName)
VALUES
('0', 'Healthy')
, ('1', 'Unmanaged')
, ('2', 'Inactive')
, ('4', 'Health Evaluation Failed')
, ('8', 'Pending Restart')
, ('16', 'Update Scan Failed')
, ('32', 'Update Scan Late')
, ('64', 'No Maintenance Window')
, ('128', 'Distant Maintenance Window')
, ('256', 'Expired Maintenance Window')
/* Initialize ClientState descriptor table */
DECLARE #ClientState TABLE (
BitMask INT
, StateName NVARCHAR(100)
)
/* Populate ClientState table */
INSERT INTO #ClientState (BitMask, StateName)
VALUES
('0', 'No Reboot')
, ('1', 'Configuration Manager')
, ('2', 'File Rename')
, ('4', 'Windows Update')
, ('8', 'Add or Remove Feature')
CREATE TABLE #MaintenanceInfo (
ResourceID INT
, NextServiceWindow DATETIME
)
/* Get maintenance data */
IF #HelperFunctionExists = 1
BEGIN
WITH Maintenance_CTE AS (
SELECT
CollectionMembers.ResourceID
, NextServiceWindow.Duration
, NextServiceWindow.NextServiceWindow
, RowNumber = DENSE_RANK() OVER (PARTITION BY ResourceID ORDER BY NextServiceWindow.NextServiceWindow)
, ServiceWindowType
, ServiceWindow.Enabled
FROM vSMS_ServiceWindow AS ServiceWindow
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.CollectionID = ServiceWindow.SiteID
JOIN fn_rbac_Collection(#UserSIDs) AS Collections ON Collections.CollectionID = CollectionMembers.CollectionID
AND Collections.CollectionType = 2 -- Device Collections
CROSS APPLY ufn_CM_GetNextMaintenanceWindow(ServiceWindow.Schedules, ServiceWindow.RecurrenceType) AS NextServiceWindow
WHERE NextServiceWindow.NextServiceWindow IS NOT NULL
AND ServiceWindowType <> 5 -- OSD Service
)
/* Populate MaintenanceInfo table and remove duplicates */
INSERT INTO #MaintenanceInfo(ResourceID, NextServiceWindow)
SELECT
ResourceID
, NextServiceWindow
FROM Maintenance_CTE
WHERE RowNumber = 1
END
/* Get update data */
;
WITH UpdateInfo_CTE
AS (
SELECT
ResourceID = Systems.ResourceID
, Missing = COUNT(*)
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_UpdateComplianceStatus(#UserSIDs) AS ComplianceStatus ON ComplianceStatus.ResourceID = Systems.ResourceID
AND ComplianceStatus.Status = 2 -- Filter on 'Required' (0 = Unknown, 1 = NotRequired, 2 = Required, 3 = Installed)
JOIN fn_rbac_ClientCollectionMembers(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = ComplianceStatus.ResourceID
JOIN fn_rbac_UpdateInfo(#LCID, #UserSIDs) AS UpdateCIs ON UpdateCIs.CI_ID = ComplianceStatus.CI_ID
AND UpdateCIs.IsSuperseded IN (#Superseded)
AND UpdateCIs.CIType_ID IN (1, 8) -- Filter on 1 Software Updates, 8 Software Update Bundle (v_CITypes)
AND UpdateCIs.ArticleID NOT IN ( -- Filter on ArticleID csv list
SELECT VALUE FROM STRING_SPLIT(#ExcludeArticleIDs, ',')
)
AND UpdateCIs.Title NOT LIKE ( -- Filter Preview updates
'[1-9][0-9][0-9][0-9]-[0-9][0-9]_Preview_of_%'
)
JOIN fn_rbac_CICategoryInfo_All(#LCID, #UserSIDs) AS CICategory ON CICategory.CI_ID = ComplianceStatus.CI_ID
AND CICategory.CategoryTypeName = 'UpdateClassification'
AND CICategory.CategoryInstanceName IN (#Categories) -- Filter on Selected Update Classification Categories
LEFT JOIN fn_rbac_CITargetedMachines(#UserSIDs) AS Targeted ON Targeted.ResourceID = ComplianceStatus.ResourceID
AND Targeted.CI_ID = ComplianceStatus.CI_ID
WHERE CollectionMembers.CollectionID = #CollectionID
AND IIF(Targeted.ResourceID IS NULL, 0, 1) IN (#Targeted) -- Filter on 'Targeted' or 'NotTargeted'
AND IIF(UpdateCIs.ArticleID = #ArticleID, 1, 0) = IIF(#ArticleID <> '', 1, 0)
GROUP BY
Systems.ResourceID
)
/* Get device info */
SELECT
Systems.ResourceID
/* Set Health states. You can find the coresponding values in the HealthState table above */
, HealthStates = (
IIF(CombinedResources.IsClient != 1, POWER(1, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Inactive/Pass'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Unknown'
, POWER(2, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Active/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
, POWER(4, 1), 0
)
+
IIF(CombinedResources.ClientState != 0, POWER(8, 1), 0)
+
IIF(UpdateScan.LastErrorCode != 0, POWER(16, 1), 0)
+
IIF(UpdateScan.LastScanTime < (SELECT DATEADD(dd, -14, CURRENT_TIMESTAMP)), POWER(32, 1), 0)
+
IIF(ISNULL(NextServiceWindow, 0) = 0 AND #HelperFunctionExists = 1, POWER(64, 1), 0)
+
IIF(NextServiceWindow > (SELECT DATEADD(dd, 30, CURRENT_TIMESTAMP)), POWER(128, 1), 0)
+
IIF(NextServiceWindow < (CURRENT_TIMESTAMP), POWER(256, 1), 0)
)
, Missing = ISNULL(Missing, (IIF(CombinedResources.IsClient = 1, 0, NULL)))
, Device = (
IIF(
SystemNames.Resource_Names0 IS NOT NULL, UPPER(SystemNames.Resource_Names0)
, IIF(Systems.Full_Domain_Name0 IS NOT NULL, Systems.Name0 + '.' + Systems.Full_Domain_Name0, Systems.Name0)
)
)
, OperatingSystem = (
CASE
WHEN OperatingSystem.Caption0 != '' THEN
CONCAT(
REPLACE(OperatingSystem.Caption0, 'Microsoft ', ''), -- Remove 'Microsoft ' from OperatingSystem
REPLACE(OperatingSystem.CSDVersion0, 'Service Pack ', ' SP') -- Replace 'Service Pack ' with ' SP' in OperatingSystem
)
ELSE (
/* Workaround for systems not in GS_OPERATING_SYSTEM table */
CASE
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.1%' THEN 'Windows 7'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.2%' THEN 'Windows 8'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.3%' THEN 'Windows 8.1'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 10.0%' THEN 'Windows 10'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.0' THEN 'Windows Server 2008'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.1' THEN 'Windows Server 2008R2'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.2' THEN 'Windows Server 2012'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.3' THEN 'Windows Server 2012 R2'
WHEN Systems.Operating_System_Name_And0 LIKE '%Server 10%' THEN (
CASE
WHEN CAST(REPLACE(Build01, '.', '') AS INTEGER) > 10017763 THEN 'Windows Server 2019'
ELSE 'Windows Server 2016'
END
)
ELSE Systems.Operating_System_Name_And0
END
)
END
)
, LastBootTime = (
CONVERT(NVARCHAR(16), OperatingSystem.LastBootUpTime0, 120)
)
, PendingRestart = (
CASE
WHEN CombinedResources.IsClient = 0
OR CombinedResources.ClientState = 0
THEN NULL
ELSE (
STUFF(
REPLACE(
(
SELECT '#!' + LTRIM(RTRIM(StateName)) AS [data()]
FROM #ClientState
WHERE BitMask & CombinedResources.ClientState <> 0
FOR XML PATH('')
),
' #!',', '
),
1, 2, ''
)
)
END
)
, ClientState = (
CASE CombinedResources.IsClient
WHEN 1 THEN ClientSummary.ClientStateDescription
ELSE 'Unmanaged'
END
)
, ClientVersion = CombinedResources.ClientVersion
, LastUpdateScan = (
CONVERT(NVARCHAR(16), UpdateScan.LastScanTime, 120)
)
, LastScanLocation = NULLIF(UpdateScan.LastScanPackageLocation, '')
, LastScanError = NULLIF(UpdateScan.LastErrorCode, 0)
, NextServiceWindow = IIF(CombinedResources.IsClient != 1, NULL, CONVERT(NVARCHAR(16), NextServiceWindow, 120))
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_CombinedDeviceResources(#UserSIDs) AS CombinedResources ON CombinedResources.MachineID = Systems.ResourceID
LEFT JOIN fn_rbac_RA_System_ResourceNames(#UserSIDs) AS SystemNames ON SystemNames.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_GS_OPERATING_SYSTEM(#UserSIDs) AS OperatingSystem ON OperatingSystem.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_CH_ClientSummary(#UserSIDs) AS ClientSummary ON ClientSummary.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_UpdateScanStatus(#UserSIDs) AS UpdateScan ON UpdateScan.ResourceID = Systems.ResourceID
LEFT JOIN #MaintenanceInfo AS Maintenance ON Maintenance.ResourceID = Systems.ResourceID
LEFT JOIN UpdateInfo_CTE AS UpdateInfo ON UpdateInfo.ResourceID = Systems.ResourceID
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = Systems.ResourceID
WHERE CollectionMembers.CollectionID = #CollectionID
AND (
CASE -- Compliant (0 = No, 1 = Yes, 2 = Unknown)
WHEN Missing = 0 OR (Missing IS NULL AND Systems.Client0 = 1) THEN 1 -- Yes
WHEN Missing > 0 AND Missing IS NOT NULL THEN 0 -- No
ELSE 2 -- Unknown
END
) IN (#Compliant)
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* #endregion */
/*##=============================================*/
/*## END QUERY BODY */
/*##=============================================*/
`
Is there an easy way to achieve this?
I have tried to look at the official Microsoft documentation but are still not able to convert the query to a view. https://learn.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver16
As I am new to SQL language I am not sure where to start.
So I agree with Larnu, that this probably doesn't make a ton of sense. But there are cases where one might want to be able to run multiple batches of queries / procedural code from an object that's as consumable as a view. I've done this once in a case where I needed to maximize my options for performance tuning without losing the consumability of the object. So for the sake of when it does make sense, this is something you could do:
Wrap your code in a stored procedure.
Use OPENQUERY() to call your procedure.
Wrap the OPENQUERY() call in a view.
Limitations with this methodology is it's rather static:
You can't pass parameters to your stored procedure
If you use temp tables in your stored procedure, then you need to use the WITH RESULT SETS clause to explicitly define the shape of your result set
The procedure can only return one result set
The SQL Server Engine puts a hard-coded cardinality estimate of 10,000 against OPENQUERY(). So in cases where your procedure returns a lot more rows (typically an order of magnitude or more) than 10,000, e.g. 1 million rows, then you may experience some performance issues with joining the wrapper view to other objects.
Example:
-- Step 1: Wrap the procedural code in a stored procedure
CREATE PROCEDURE dbo.RunSomeCode
AS
BEGIN
CREATE TABLE #Results (ID INT, SomeValue VARCHAR(100));
DECLARE #SomeVariable INT = 0;
WHILE (#SomeVariable < 5)
BEGIN
SET #SomeVariable = #SomeVariable + 1;
INSERT INTO #Results (ID, SomeValue)
SELECT ID, SomeValue
FROM SomeTable
WHERE ID = #SomeVariable
END
SELECT ID, SomeValue
FROM #Results;
END
GO
-- Step 2 & 3: Wrap an OPENQUERY() call to the procedure in a view.
CREATE VIEW dbo.SomeView
AS
SELECT ID, SomeValue
FROM OPENQUERY
(
'
LocalServerName,
EXEC dbo.RunSomeCode
WITH RESULT SETS
((
ID INT,
SomeValue VARCHAR(100)
))
'
);
Voila, you can now execute the procedure by SELECTing from the view:
SELECT ID, SomeValue
FROM dbo.SomeView;

SSIS OLE DB Command error: Could not deduce type for parameter in position '1' for remote call to module 'sp_executesql'

I have an OLE DB Command task in an SSIS package that received parameters, inserts them into a table in a linked-server, and returns the ID that was created on the linked server.
When I run the query in SSMS it works, but within SSIS I get the error message in the suject line.
The SqlCommand within the OLE DB Command is:
DECLARE
#UserId int = ?,
#ContactUsId int = ?,
#CreateDate datetime = ?,
#Subject nvarchar(500) = ?,
#InteractionClusterId int = null -- We will be testing for this being null
-- removed some irrelevant code here
-- if all else failed, insert a new record to MngInteractionCluster
if #InteractionClusterId is null
begin
declare #RemoteQuery nvarchar(max) = N'
insert into BI_Mng.dbo.MngInteractionCluster
(
UserId,
ContactUsId,
CreateDateTime,
[Subject]
)
values
(
#UserId,
#ContactUsId,
#CreateDate,
#Subject
)
SELECT #InteractionClusterId_OUT = SCOPE_IDENTITY()'
declare #Params nvarchar(1000) = N'
#UserId int,
#ContactUsId int,
#CreateDate datetime,
#Subject nvarchar(500),
#InteractionClusterId_OUT int OUTPUT'
EXEC [BI_WAREHOUSE\BI_GLOBAL].master.dbo.sp_executesql
#RemoteQuery,
#Params,
#UserId = #UserId ,
#ContactUsId = #ContactUsId,
#CreateDate = #CreateDate,
#Subject = #Subject,
#InteractionClusterId_OUT = #InteractionClusterId OUTPUT;
end
select
? = #InteractionClusterId
Any help on getting this to parse in SSIS would be really appreciated!
I ended up moving the heavy lifting into a stored procedure on the local server.
Now the code in the OLE DB command component is a concise
DECLARE
#OriginalMailId int = ?,
#UserId int = ?,
#ContactUsId int = ?,
#CreateDate datetime = ?,
#Subject nvarchar(500) = ?,
#isAutoReply bit = ?,
#BIEnvironmentId int = ?,
#InteractionClusterId int
exec BI_Mng.dbo.GetInteractionClusterId
#OriginalMailId,
#UserId,
#ContactUsId,
#CreateDate,
#Subject,
#isAutoReply,
#BIEnvironmentId,
#InteractionClusterId OUTPUT
select ? = #InteractionClusterId
Inside the stored procedure there's a call to a remote execution of sp_ExecuteSQL on the linked server. In order for SSIS to parse it, it's vital that it include a WITH RESULT SETS clause, like so:
declare #RemoteQuery nvarchar(max) = N'
insert into BI_Mng.dbo.MngInteractionCluster
(
UserId,
ContactUsId,
CreateDateTime,
[Subject]
)
values
(
#UserId,
#ContactUsId,
#CreateDate,
#Subject
)
SELECT #InteractionClusterId_OUT = SCOPE_IDENTITY()'
declare #Params nvarchar(1000) = N'
#UserId int,
#ContactUsId int,
#CreateDate datetime,
#Subject nvarchar(500),
#InteractionClusterId_OUT int OUTPUT'
EXEC [BI_WAREHOUSE\BI_GLOBAL].master.dbo.sp_executesql
#RemoteQuery,
#Params,
#UserId = #UserId ,
#ContactUsId = #ContactUsId,
#CreateDate = #CreateDate,
#Subject = #Subject,
#InteractionClusterId_OUT = #InteractionClusterId OUTPUT
WITH RESULT SETS NONE;

Incorrect syntax near the keyword 'FOR' XML

I get the error
Incorrect syntax near the keyword 'FOR'
when I write '+# SirketNo+' to the dot.
I could not find the error.
Error :
Msg 156, Level 15, State 1, Line 110
Incorrect syntax near the keyword 'FOR'
Msg 102, Level 15, State 1, Line 125
Incorrect syntax near ','
Msg 102, Level 15, State 1, Line 137
Incorrect syntax near ','
My code:
DECLARE #SirketNo AS NVARCHAR(3)= '427',
#AliciAdreslerinDepartmani AS NVARCHAR(MAX) = 'MuhasebeMuduru',
#BilgiAliciAdreslerinDepartmani AS NVARCHAR(MAX) = 'Mudur',
#GizliAliciAdreslerinDepartmani AS NVARCHAR(MAX) = 'FinansKoordinatoru'', ''FinansSorumlusu'', ''Developer';
DECLARE #SqlQuery AS NVARCHAR(max) = N'
BEGIN
DECLARE #KacGunOnce INT= 13, #xml NVARCHAR(MAX), #KasaBakiye NVARCHAR(MAX), #AliciAdresler VARCHAR(MAX), #BilgiAliciAdresler VARCHAR(MAX), #GizliBilgiAliciAdresler VARCHAR(MAX), #vucut NVARCHAR(MAX), #Baslik VARCHAR(100)= '''', #CreatedBy VARCHAR(100)= '''', #Mesaj VARCHAR(250)= '''', #IsYeriNo SMALLINT;
DECLARE #mad TABLE
(logicalref INT IDENTITY(1, 1),
IsYeriNo SMALLINT,
KasaKodu VARCHAR(17),
KasaAdi VARCHAR(51),
AlıcıAdresler NVARCHAR(MAX),
BilgiAlıcıAdresler NVARCHAR(MAX),
GizliBilgiAlıcıAdresler NVARCHAR(MAX)
);
END; --Değişkenleri ve değişken tabloyu oluştur
BEGIN
INSERT INTO #mad
(IsYeriNo,
KasaKodu,
KasaAdi
)
SELECT DISTINCT
ksl.BRANCH,
lk.CODE,
lk.NAME
FROM LG_'+#SirketNo+'_01_KSLINES AS KSL WITH (NOLOCK)
JOIN L_CAPIUSER AS U WITH (NOLOCK) ON U.NR LIKE KSL.CAPIBLOCK_CREATEDBY
JOIN LG_'+#SirketNo+'_KSCARD AS lk ON lk.LOGICALREF = ksl.CARDREF
WHERE ksl.SIGN = 1
AND ksl.AMOUNT >= 300
AND CONVERT(VARCHAR(10), ksl.DATE_, 104) = CONVERT(VARCHAR(10), GETDATE() - #KacGunOnce, 104);
END; --Değişken tabloya verileri insert et
BEGIN
--Döngü için değişken atamaları
DECLARE #s INT= 1, #d INT=
(
SELECT COUNT(logicalref)
FROM #mad
);
--Döngü
WHILE #d >= #s
BEGIN
/**** DÖNGÜ BAŞLANGIÇ ****/
BEGIN
SELECT #KasaBakiye =
(
SELECT KasaKodu
FROM #mad
WHERE logicalref = #s
)+'' kodlu kasanın güncel bakiyesi: ''+FORMAT(SUM(CASHTOT.DEBIT - CASHTOT.CREDIT), ''c2'', ''tr-TR'')
FROM LG_'+#SirketNo+'_KSCARD AS CASHC WITH (NOLOCK),
LG_'+#SirketNo+'_01_CSHTOTS AS CASHTOT WITH (NOLOCK)
WHERE CASHC.CODE LIKE
(
SELECT KasaKodu
FROM #mad
WHERE logicalref = #s
)
AND CASHTOT.CARDREF = CASHC.LOGICALREF
AND CASHTOT.TOTTYPE = 1
AND CASHTOT.DAY_ >= 0
AND CASHTOT.DAY_ <= 365;
END; --Kasa Bakiyesini değişkene ata
BEGIN
-- İş yerini değişkene ata
SELECT #IsYeriNo = IsYeriNo
FROM #mad
WHERE logicalref = #s;
END; -- İş yerini değişkene ata;
BEGIN --Kasa hareketlerini HTML formatında XMLe dönüştür
SET #xml = CAST(
(
SELECT f.DATE_ AS ''td'',
'''',
f.FICHENO AS ''td'',
'''',
f.TRCODE AS ''td'',
'''',
f.CUSTTITLE AS ''td'',
'''',
f.LINEEXP AS ''td'',
'''',
f.AMOUNT AS ''td'',
'''',
f.REPORTRATE AS ''td'',
'''',
f.REPORTNET AS ''td'',
'''',
f.SPECODE AS ''td'',
'''',
f.CYPHCODE AS ''td'',
'''',
f.BRANCH AS ''td'',
'''',
f.NAME AS ''td'',
''''
FROM
(
SELECT ksl.DATE_,
KSL.FICHENO,
CASE TRCODE
WHEN 11
THEN ''CARİ HESAP TAHSİLAT''
WHEN 12
THEN ''CARİ İŞLEM''
WHEN 21
THEN ''BANKA İŞLEMİ''
WHEN 22
THEN ''BANKA İŞLEMİ''
WHEN 31
THEN ''FATURA İŞLEMİ''
WHEN 32
THEN ''FATURA İŞLEMİ''
WHEN 33
THEN ''FATURA İŞLEMİ''
WHEN 34
THEN ''FATURA İŞLEMİ''
WHEN 35
THEN ''FATURA İŞLEMİ''
WHEN 36
THEN ''FATURA İŞLEMİ''
WHEN 37
THEN ''FATURA İŞLEMİ''
WHEN 38
THEN ''FATURA İŞLEMİ''
WHEN 39
THEN ''FATURA İŞLEMİ''
WHEN 61
THEN ''ÇEK-SENET İŞLEMİ''
WHEN 62
THEN ''ÇEK-SENET İŞLEMİ''
WHEN 63
THEN ''ÇEK-SENET İŞLEMİ''
WHEN 64
THEN ''ÇEK-SENET İŞLEMİ''
WHEN 71
THEN ''KASA İŞLEMİ''
WHEN 72
THEN ''KASA İŞLEMİ''
WHEN 73
THEN ''KASA İŞLEMİ''
WHEN 74
THEN ''KASA İŞLEMİ''
ELSE ''TANIMSIZ İŞLEM''
END AS ''TRCODE'',
KSL.CUSTTITLE,
KSL.LINEEXP,
FORMAT(AMOUNT, ''c2'', ''tr-TR'') AS ''AMOUNT'',
CAST(REPORTRATE AS MONEY) AS ''REPORTRATE'',
CAST(REPORTNET AS MONEY) AS ''REPORTNET'',
KSL.SPECODE,
KSL.CYPHCODE,
KSL.BRANCH,
U.NAME
FROM LG_'+#SirketNo+'_01_KSLINES AS KSL WITH (NOLOCK) /**************************************/
JOIN L_CAPIUSER AS U WITH (NOLOCK) ON U.NR LIKE KSL.CAPIBLOCK_CREATEDBY
JOIN LG_427_KSCARD AS lk ON lk.LOGICALREF = ksl.CARDREF
WHERE ksl.SIGN = 1
AND ksl.AMOUNT >= 300
AND CONVERT(VARCHAR(10), ksl.DATE_, 104) = CONVERT(VARCHAR(10), GETDATE() - #KacGunOnce, 104)
AND ksl.BRANCH =
(
SELECT IsYeriNo
FROM #mad
WHERE logicalref = #s
)
AND lk.CODE =
(
SELECT KasaKodu
FROM #mad
WHERE logicalref = #s
)
) AS f
FOR XML PATH(''tr''), ELEMENTS
) AS NVARCHAR(max));
END; --Kasa hareketlerini HTML formatında XML''e dönüştür
BEGIN
UPDATE #mad
SET
[AlıcıAdresler] = ISNULL(
(
SELECT TOP 1 REPLACE(
(
SELECT RTRIM(MAIL) [data()]
FROM mad.dbo.Kullanicilar k
WHERE k.SIRKET = ''427''
AND IS_YERI = #IsYeriNo
AND LEN(MAIL) > 0
AND DEPERTMAN IN('''+#AliciAdreslerinDepartmani+''')
FOR XML PATH('''')
), '' '', ''; '') AS BIRLESIK
), ''''),
[BilgiAlıcıAdresler] = ISNULL(
(
SELECT TOP 1 REPLACE(
(
SELECT RTRIM(MAIL) [data()]
FROM mad.dbo.Kullanicilar k
WHERE k.SIRKET = ''427''
AND IS_YERI = #IsYeriNo
AND LEN(MAIL) > 0
AND DEPERTMAN IN('''+#BilgiAliciAdreslerinDepartmani+''')
FOR XML PATH('''')
), '' '', ''; '') AS BIRLESIK
), ''''),
[GizliBilgiAlıcıAdresler] = ISNULL(
(
SELECT TOP 1 REPLACE(
(
SELECT RTRIM(MAIL) [data()]
FROM mad.dbo.Kullanicilar k
WHERE k.SIRKET = ''427''
AND LEN(MAIL) > 0
AND DEPERTMAN IN('''+#GizliAliciAdreslerinDepartmani+''')
FOR XML PATH('''')
), '' '', ''; '') AS BIRLESIK
), '''')
WHERE IsYeriNo = #IsYeriNo;
END; -- Değişken tabloya mail adreslerini update et
BEGIN
UPDATE #mad
SET
[AlıcıAdresler] = [BilgiAlıcıAdresler]
WHERE [AlıcıAdresler] = '''';
END; -- Değişken tabloda alici adresi boş olanlara bilgideki adresleri alici olarak ekle
BEGIN
SET #Baslik = '''';
SET #Mesaj = '''';
SET #vucut = '''';
SELECT #Baslik+=CONVERT( NVARCHAR, #IsYeriNo)+'' nolu işyerinin kasa hareketleridir. [212]'';
SELECT #Mesaj+='''';
SET #vucut = ''<html>''+''<body>''+''<H3 style = "color:blue;"><i>''+#Mesaj+''</i> </H3>''+''<H2 style="text-align:center; color:orange;"> Kasa Hareketleri </H2>''+''<H4> ''+''<ul style = "list-style-type:disc">''+''<p>''+''<li>''+#KasaBakiye+''</li>''+''</ul>''+''</p>''+''<H4> ''+''
<table border = 1>
<tr>
<th> Tarih </th> <th> Fiş No </th> <th> İşlem Türü </th> <th> Cari Başlığı </th> <th> Açıklama </th> <th> Tutar </th> <th> Kur </th> <th> Döviz Tutar </th> <th> Özel Kodu </th> <th> Yetki Kodu </th> <th> İş Yeri </th> <th> Kaydeden Kullanıcı </th>
</tr>'';
SET #vucut = #vucut+#xml+''</table></body></html>''+''[''+CONVERT(NVARCHAR, #IsYeriNo)+'' nolu işyerinin ''+
(
SELECT KasaKodu
FROM #mad
WHERE logicalref = #s
)+'' kodlu kasanın ''+CONVERT(NVARCHAR, #KacGunOnce)+'' gün öncesine ait hareketleridir.] Bu maile cevap vererek bilgilendirme maili ile alakalı tavsiyenizi yazabilirsiniz.'';
END; --Mail verilerini hazırla
BEGIN
SET #AliciAdresler =
(
SELECT m.[AlıcıAdresler]
FROM #mad m
WHERE m.logicalref = #s
);
SET #BilgiAliciAdresler =
(
SELECT m.[BilgiAlıcıAdresler]
FROM #mad m
WHERE m.logicalref = #s
);
SET #GizliBilgiAliciAdresler =
(
SELECT m.[GizliBilgiAlıcıAdresler]
FROM #mad m
WHERE m.logicalref = #s
);
END; -- Mail adreslerini değişkenlere tanımla
BEGIN
EXEC msdb.dbo.sp_send_dbmail
#body = #vucut,
#body_format = ''HTML'',
#subject = #Baslik,
#importance = ''HIGH'',
#reply_to = ''mustafaalperen#fimar.com.tr'',
#profile_name = ''MAD_Mail'',
--#recipients = #AliciAdresler,
--#copy_recipients = #BilgiAliciAdresler,
#blind_copy_recipients = #GizliBilgiAliciAdresler,
#execute_query_database = ''FIMAR_MHB'';
END; --Mail Gönder
BEGIN
DECLARE #GonderilenMailBilgisi NVARCHAR(MAX)= ''Mail (Id: ''+CONVERT(NVARCHAR, ##IDENTITY)+'') queued.'';
EXEC sp_MAD_Loglar_Ins
#FORM = ''AGENT_MAD_08_00_Mailleri'',
#KULLANICI = ''Sql Agent'',
#TERMINAL = ''427'',
#ASISTAN = #GonderilenMailBilgisi,
#DERECE = ''Bal'';
END; --Loglama yap
BEGIN
SET #s = #s + 1;
END; --Döngü için döngü değişkenini +1 arttır
/**** DÖNGÜ BİTİŞ ****/
END;
END; -- Mail gönderme döngüsü
';
EXECUTE sp_executesql #SqlQuery;
Consider changing your database design and minimize the use of dynamic SQL. Now your dynamic SQL code will allways be prone to mystical errors and SQL injection attacks.
As suggested by #PPP, you have to get the generated code and see what is wrong with it - to debug it in SQL Server Management Studio.
To do it, you have to use this command:
PRINT CAST(#SqlQuery as ntext)
because your dynamically generated sql is longer than 8000 chars. See this question.
Then copy it to a new window and see the syntax errors and fix them and then fix the code that generates it appropriately.

SQL Server: HTML Decode based on the HTML names in a String input

I am trying to convert the HTML names like & " etc to their equivalent CHAR values using the SQL below. I was testing this in SQL Server 2012.
Test 1 (This works fine):
GO
DECLARE #inputString VARCHAR(MAX)= '&testString&'
DECLARE #codePos INT, #codeEncoded VARCHAR(7), #startIndex INT, #resultString varchar(max)
SET #resultString = LTRIM(RTRIM(#inputString))
SELECT #startIndex = PATINDEX('%&%', #resultString)
WHILE #startIndex > 0
BEGIN
SELECT #resultString = REPLACE(#resultString, '&', '&'), #startIndex=PATINDEX('%&%', #resultString)
END
PRINT #resultString
Go
Output:
&testString&
Test 2 (this isn't worked):
Since the above worked, I have tried to extend this to deal with more characters as following:
DECLARE #htmlNames TABLE (ID INT IDENTITY(1,1), asciiDecimal INT, htmlName varchar(50))
INSERT INTO #htmlNames
VALUES (34,'"'),(38,'&'),(60,'<'),(62,'>'),(160,' '),(161,'¡'),(162,'¢')
-- I would load the full list of HTML names into this TABLE varaible, but removed for testing purposes
DECLARE #inputString VARCHAR(MAX)= '&testString&'
DECLARE #count INT = 0
DECLARE #id INT = 1
DECLARE #charCode INT, #htmlName VARCHAR(30)
DECLARE #codePos INT, #codeEncoded VARCHAR(7), #startIndex INT
, #resultString varchar(max)
SELECT #count=COUNT(*) FROM #htmlNames
WHILE #id <=#count
BEGIN
SELECT #charCode = asciiDecimal, #htmlname = htmlName
FROM #htmlNames
WHERE ID = #id
SET #resultString = LTRIM(RTRIM(#inputString))
SELECT #startIndex = PATINDEX('%' + #htmlName + '%', #resultString)
While #startIndex > 0
BEGIN
--PRINT #resultString + '|' + #htmlName + '|' + NCHAR(#charCode)
SELECT #resultString = REPLACE(#resultString, #htmlName, NCHAR(#charCode))
SET #startIndex=PATINDEX('%' + #htmlName + '%', #resultString)
END
SET #id=#id + 1
END
PRINT #resultString
GO
Output:
&testString&
I cannot figure out where I'm going wrong? Any help would be much appreciated.
I am not interested to load the string values into application layer and then apply HTMLDecode and save back to the database.
EDIT:
This line SET #resultString = LTRIM(RTRIM(#inputString)) was inside the WHILE so I was overwriting the result with #inputString. Thank you, YanireRomero.
I like #RichardDeeming's solution too, but it didn't suit my needs in this case.
Here's a simpler solution that doesn't need a loop:
DECLARE #htmlNames TABLE
(
ID INT IDENTITY(1,1),
asciiDecimal INT,
htmlName varchar(50)
);
INSERT INTO #htmlNames
VALUES
(34,'"'),
(38,'&'),
(60,'<'),
(62,'>'),
(160,' '),
(161,'¡'),
(162,'¢')
;
DECLARE #inputString varchar(max)= '&test&quot;<String>"&';
DECLARE #resultString varchar(max) = #inputString;
-- Simple HTML-decode:
SELECT
#resultString = Replace(#resultString COLLATE Latin1_General_CS_AS, htmlName, NCHAR(asciiDecimal))
FROM
#htmlNames
;
SELECT #resultString;
-- Output: &test"<String>"&
-- Multiple HTML-decode:
SET #resultString = #inputString;
DECLARE #temp varchar(max) = '';
WHILE #resultString != #temp
BEGIN
SET #temp = #resultString;
SELECT
#resultString = Replace(#resultString COLLATE Latin1_General_CS_AS, htmlName, NCHAR(asciiDecimal))
FROM
#htmlNames
;
END;
SELECT #resultString;
-- Output: &test"<String>"&
EDIT: Changed to NCHAR, as suggested by #tomasofen, and added a case-sensitive collation to the REPLACE function, as suggested by #TechyGypo.
For the sake of performance, this isn't something you should do write as T-SQL statements, or as a SQL scalar value function. The .NET libraries provide excellent, fast, and, above all, reliable HTML decoding. In my opinion, you should implement this as a SQL CLR, like this:
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Net;
public partial class UserDefinedFunctions
{
[Microsoft.SqlServer.Server.SqlFunction(
IsDeterministic = true,
IsPrecise = true,
DataAccess = DataAccessKind.None,
SystemDataAccess = SystemDataAccessKind.None)]
[return: SqlFacet(MaxSize = 4000)]
public static SqlString cfnHtmlDecode([SqlFacet(MaxSize = 4000)] SqlString input)
{
if (input.IsNull)
return null;
return System.Net.WebUtility.HtmlDecode(input.Value);
}
}
Then in your T-SQL, call it like this:
SELECT clr_schema.cfnHtmlDecode(column_name) FROM table_schema.table_name
Hey it was an assign error:
DECLARE #htmlNames TABLE (ID INT IDENTITY(1,1), asciiDecimal INT, htmlName varchar(50))
INSERT INTO #htmlNames
VALUES (34,'"'),(38,'&'),(60,'<'),(62,'>'),(160,' '),(161,'¡'),(162,'¢')
-- I would load the full list of HTML names into this TABLE varaible, but removed for testing purposes
DECLARE #inputString VARCHAR(MAX)= '&testString&'
DECLARE #count INT = 0
DECLARE #id INT = 1
DECLARE #charCode INT, #htmlName VARCHAR(30)
DECLARE #codePos INT, #codeEncoded VARCHAR(7), #startIndex INT
, #resultString varchar(max)
SELECT #count=COUNT(*) FROM #htmlNames
SET #resultString = LTRIM(RTRIM(#inputString))
WHILE #id <=#count
BEGIN
SELECT #charCode = asciiDecimal, #htmlname = htmlName
FROM #htmlNames
WHERE ID = #id
SELECT #startIndex = PATINDEX('%' + #htmlName + '%', #resultString)
While #startIndex > 0
BEGIN
--PRINT #resultString + '|' + #htmlName + '|' + NCHAR(#charCode)
SET #resultString = REPLACE(#resultString, #htmlName, NCHAR(#charCode))
SET #startIndex=PATINDEX('%' + #htmlName + '%', #resultString)
END
SET #id=#id + 1
END
PRINT #resultString
GO
this line SET #resultString = LTRIM(RTRIM(#inputString)) was inside the while so you were overwriting you result.
Hope it helps.
Some additional help for "Richard Deeming" response, to safe some typing for future visitors trying to upgrade the function with more codes:
INSERT INTO #htmlNames
VALUES
(34,'"'),
(38,'&'),
(60,'<'),
(62,'>'),
(160, ' '),
(161, '¡'),
(162, '¢'),
(163, '£'),
(164, '¤'),
(165, '¥'),
(166, '¦'),
(167, '§'),
(168, '¨'),
(169, '©'),
(170, 'ª'),
(171, '«'),
(172, '¬'),
(173, '­'),
(174, '®'),
(175, '¯'),
(176, '°'),
(177, '±'),
(178, '²'),
(179, '³'),
(180, '´'),
(181, 'µ'),
(182, '¶'),
(183, '·'),
(184, '¸'),
(185, '¹'),
(186, 'º'),
(187, '»'),
(188, '¼'),
(189, '½'),
(190, '¾'),
(191, '¿'),
(192, 'À'),
(193, 'Á'),
(194, 'Â'),
(195, 'Ã'),
(196, 'Ä'),
(197, 'Å'),
(198, 'Æ'),
(199, 'Ç'),
(200, 'È'),
(201, 'É'),
(202, 'Ê'),
(203, 'Ë'),
(204, 'Ì'),
(205, 'Í'),
(206, 'Î'),
(207, 'Ï'),
(208, 'Ð'),
(209, 'Ñ'),
(210, 'Ò'),
(211, 'Ó'),
(212, 'Ô'),
(213, 'Õ'),
(214, 'Ö'),
(215, '×'),
(216, 'Ø'),
(217, 'Ù'),
(218, 'Ú'),
(219, 'Û'),
(220, 'Ü'),
(221, 'Ý'),
(222, 'Þ'),
(223, 'ß'),
(224, 'à'),
(225, 'á'),
(226, 'â'),
(227, 'ã'),
(228, 'ä'),
(229, 'å'),
(230, 'æ'),
(231, 'ç'),
(232, 'è'),
(233, 'é'),
(234, 'ê'),
(235, 'ë'),
(236, 'ì'),
(237, 'í'),
(238, 'î'),
(239, 'ï'),
(240, 'ð'),
(241, 'ñ'),
(242, 'ò'),
(243, 'ó'),
(244, 'ô'),
(245, 'õ'),
(246, 'ö'),
(247, '÷'),
(248, 'ø'),
(249, 'ù'),
(250, 'ú'),
(251, 'û'),
(252, 'ü'),
(253, 'ý'),
(254, 'þ'),
(255, 'ÿ'),
(8364, '€');
EDITED:
If you want the euro symbol working (and in general ASCII codes over 255), you will need to use NCHAR instead CHAR in Richard Deeming code.

SQL function to convert UK OS coordinates from easting/northing to longitude and latitude

Please can someone post a SQL function to convert easting/northing to longitude/latitude. I know it's incredibly complicated but I haven't found anyone who has documented it in T-SQL.
This javascript code works but I'm having trouble converting it to SQL.
I have 16,000 coordinates and need them all converted to lat/long.
This is what I have so far but it's not getting past the while loop.
DECLARE #east real = 482353,
#north real = 213371
DECLARE #a real = 6377563.396,
#b real = 6356256.910,
#F0 real = 0.9996012717,
#lat0 real = 49*PI()/180,
#lon0 real = -2*PI()/180
DECLARE #N0 real = -100000,
#E0 real = 400000,
#e2 real = 1 - (#b*#b)/(#a*#a),
#n real = (#a-#b)/(#a+#b)
DECLARE #n2 real = #n*#n,
#n3 real = #n*#n*#n
DECLARE #lat real = #lat0,
#M real = 0
WHILE (#north-#N0-#M >= 0.00001)
BEGIN
SET #lat = ((#north-#N0-#M)/(#a*#F0)) + #lat
DECLARE #Ma real = (1 + #n + (5/4)*#n2 + (5/4)*#n3) * (#lat-#lat0),
#Mb real = (3*#n + 3*#n*#n + (21/8)*#n3) * SIN(#lat-#lat0) * COS(#lat+#lat0),
#Mc real = ((15/8)*#n2 + (15/8)*#n3) * SIN(2*(#lat-#lat0)) * COS(2*(#lat+#lat0)),
#Md real = (35/24)*#n3 * SIN(3*(#lat-#lat0)) * COS(3*(#lat+#lat0))
SET #M = #b * #F0 * (#Ma - #Mb + #Mc - #Md)
END
DECLARE #cosLat real = COS(#lat),
#sinLat real = SIN(#lat)
DECLARE #nu real = #a*#F0/sqrt(1-#e2*#sinLat*#sinLat)
DECLARE #rho real = #a*#F0*(1-#e2)/POWER(1-#e2*#sinLat*#sinLat, 1.5)
DECLARE #eta2 real = #nu/#rho-1
DECLARE #tanLat real = tan(#lat)
DECLARE #tan2lat real = #tanLat*#tanLat
DECLARE #tan4lat real = #tan2lat*#tan2lat
DECLARE #tan6lat real = #tan4lat*#tan2lat
DECLARE #secLat real = 1/#cosLat
DECLARE #nu3 real = #nu*#nu*#nu
DECLARE #nu5 real = #nu3*#nu*#nu
DECLARE #nu7 real = #nu5*#nu*#nu
DECLARE #VII real = #tanLat/(2*#rho*#nu)
DECLARE #VIII real = #tanLat/(24*#rho*#nu3)*(5+3*#tan2lat+#eta2-9*#tan2lat*#eta2)
DECLARE #IX real = #tanLat/(720*#rho*#nu5)*(61+90*#tan2lat+45*#tan4lat)
DECLARE #X real = #secLat/#nu
DECLARE #XI real = #secLat/(6*#nu3)*(#nu/#rho+2*#tan2lat)
DECLARE #XII real = #secLat/(120*#nu5)*(5+28*#tan2lat+24*#tan4lat)
DECLARE #XIIA real = #secLat/(5040*#nu7)*(61+662*#tan2lat+1320*#tan4lat+720*#tan6lat)
DECLARE #dE real = (#east-#E0)
DECLARE #dE2 real = #dE*#dE
DECLARE #dE3 real = #dE2*#dE
DECLARE #dE4 real = #dE2*#dE2,
#dE5 real = #dE3*#dE2
DECLARE #dE6 real = #dE4*#dE2,
#dE7 real = #dE5*#dE2
SET #lat = #lat - #VII*#dE2 + #VIII*#dE4 - #IX*#dE6
DECLARE #lon real = #lon0 + #X*#dE - #XI*#dE3 + #XII*#dE5 - #XIIA*#dE7
SELECT #lon, #lat
I've been struggling with this one for a while.
I had a lot of northing/easting points in OSGB36 that have to be converted on the fly on a regular basis.
Please note that the UDF below converts northings/eastings in OSGB36 (Ordnance Survey) projection to latitude/longitude in WGS84 projection so they can be used in Google Maps.
/****** Object: UserDefinedFunction [dbo].[NEtoLL] Script Date: 09/06/2012 17:06:39 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE FUNCTION [dbo].[NEtoLL] (#East INT, #North INT, #LatOrLng VARCHAR(3)) RETURNS FLOAT AS
BEGIN
--Author: Sandy Motteram
--Date: 06 September 2012
--UDF adapted from javascript at http://www.bdcc.co.uk/LatLngToOSGB.js
--found on page http://mapki.com/wiki/Tools:Snippets
--Instructions:
--Latitude and Longitude are calculated based on BOTH the easting and northing values from the OSGB36
--This UDF takes both easting and northing values in OSGB36 projection and you must specify if a latitude or longitude co-ordinate should be returned.
--IT first converts E/N values to lat and long in OSGB36 projection, then converts those values to lat/lng in WGS84 projection
--Sample values below
--DECLARE #East INT, #North INT, #LatOrLng VARCHAR(3)
--SELECT #East = 529000, #North = 183650 --that combo should be the corner of Camden High St and Delancey St
DECLARE #Pi FLOAT
, #K0 FLOAT
, #OriginLat FLOAT
, #OriginLong FLOAT
, #OriginX FLOAT
, #OriginY FLOAT
, #a FLOAT
, #b FLOAT
, #e2 FLOAT
, #ex FLOAT
, #n1 FLOAT
, #n2 FLOAT
, #n3 FLOAT
, #OriginNorthings FLOAT
, #lat FLOAT
, #lon FLOAT
, #Northing FLOAT
, #Easting FLOAT
SELECT #Pi = 3.14159265358979323846
, #K0 = 0.9996012717 -- grid scale factor on central meridean
, #OriginLat = 49.0
, #OriginLong = -2.0
, #OriginX = 400000 -- 400 kM
, #OriginY = -100000 -- 100 kM
, #a = 6377563.396 -- Airy Spheroid
, #b = 6356256.910
/* , #e2
, #ex
, #n1
, #n2
, #n3
, #OriginNorthings*/
-- compute interim values
SELECT #a = #a * #K0
, #b = #b * #K0
SET #n1 = (#a - #b) / (#a + #b)
SET #n2 = #n1 * #n1
SET #n3 = #n2 * #n1
SET #lat = #OriginLat * #Pi / 180.0 -- to radians
SELECT #e2 = (#a * #a - #b * #b) / (#a * #a) -- first eccentricity
, #ex = (#a * #a - #b * #b) / (#b * #b) -- second eccentricity
SET #OriginNorthings = #b * #lat + #b * (#n1 * (1.0 + 5.0 * #n1 * (1.0 + #n1) / 4.0) * #lat
- 3.0 * #n1 * (1.0 + #n1 * (1.0 + 7.0 * #n1 / 8.0)) * SIN(#lat) * COS(#lat)
+ (15.0 * #n1 * (#n1 + #n2) / 8.0) * SIN(2.0 * #lat) * COS(2.0 * #lat)
- (35.0 * #n3 / 24.0) * SIN(3.0 * #lat) * COS(3.0 * #lat))
SELECT #northing = #north - #OriginY
, #easting = #east - #OriginX
DECLARE #nu FLOAT
, #phid FLOAT
, #phid2 FLOAT
, #t2 FLOAT
, #t FLOAT
, #q2 FLOAT
, #c FLOAT
, #s FLOAT
, #nphid FLOAT
, #dnphid FLOAT
, #nu2 FLOAT
, #nudivrho FLOAT
, #invnurho FLOAT
, #rho FLOAT
, #eta2 FLOAT
/* Evaluate M term: latitude of the northing on the centre meridian */
SET #northing = #northing + #OriginNorthings
SET #phid = #northing / (#b*(1.0 + #n1 + 5.0 * (#n2 + #n3) / 4.0)) - 1.0
SET #phid2 = #phid + 1.0
WHILE (ABS(#phid2 - #phid) > 0.000001)
BEGIN
SET #phid = #phid2;
SET #nphid = #b * #phid + #b * (#n1 * (1.0 + 5.0 * #n1 * (1.0 + #n1) / 4.0) * #phid
- 3.0 * #n1 * (1.0 + #n1 * (1.0 + 7.0 * #n1 / 8.0)) * SIN(#phid) * COS(#phid)
+ (15.0 * #n1 * (#n1 + #n2) / 8.0) * SIN(2.0 * #phid) * COS(2.0 * #phid)
- (35.0 * #n3 / 24.0) * SIN(3.0 * #phid) * COS(3.0 * #phid))
SET #dnphid = #b * ((1.0 + #n1 + 5.0 * (#n2 + #n3) / 4.0) - 3.0 * (#n1 + #n2 + 7.0 * #n3 / 8.0) * COS(2.0 * #phid)
+ (15.0 * (#n2 + #n3) / 4.0) * COS(4 * #phid) - (35.0 * #n3 / 8.0) * COS(6.0 * #phid))
SET #phid2 = #phid - (#nphid - #northing) / #dnphid
END
SELECT #c = COS(#phid)
, #s = SIN(#phid)
, #t = TAN(#phid)
SELECT #t2 = #t * #t
, #q2 = #easting * #easting
SET #nu2 = (#a * #a) / (1.0 - #e2 * #s * #s)
SET #nu = SQRT(#nu2)
SET #nudivrho = #a * #a * #c * #c / (#b * #b) - #c * #c + 1.0
SET #eta2 = #nudivrho - 1
SET #rho = #nu / #nudivrho;
SET #invnurho = ((1.0 - #e2 * #s * #s) * (1.0 - #e2 * #s * #s)) / (#a * #a * (1.0 - #e2))
SET #lat = #phid - #t * #q2 * #invnurho / 2.0 + (#q2 * #q2 * (#t / (24 * #rho * #nu2 * #nu) * (5 + (3 * #t2) + #eta2 - (9 * #t2 * #eta2))))
SET #lon = (#easting / (#c * #nu))
- (#easting * #q2 * ((#nudivrho + 2.0 * #t2) / (6.0 * #nu2)) / (#c * #nu))
+ (#q2 * #q2 * #easting * (5 + (28 * #t2) + (24 * #t2 * #t2)) / (120 * #nu2 * #nu2 * #nu * #c))
SELECT #lat = #lat * 180.0 / #Pi
, #lon = #lon * 180.0 / #Pi + #OriginLong
--Now convert the lat and long from OSGB36 to WGS84
DECLARE #OGlat FLOAT
, #OGlon FLOAT
, #height FLOAT
SELECT #OGlat = #lat
, #OGlon = #lon
, #height = 24 --London's mean height above sea level is 24 metres. Adjust for other locations.
DECLARE #deg2rad FLOAT
, #rad2deg FLOAT
, #radOGlat FLOAT
, #radOGlon FLOAT
SELECT #deg2rad = #Pi / 180
, #rad2deg = 180 / #Pi
--first off convert to radians
SELECT #radOGlat = #OGlat * #deg2rad
, #radOGlon = #OGlon * #deg2rad
--these are the values for WGS84(GRS80) to OSGB36(Airy)
DECLARE #a2 FLOAT
, #h FLOAT
, #xp FLOAT
, #yp FLOAT
, #zp FLOAT
, #xr FLOAT
, #yr FLOAT
, #zr FLOAT
, #sf FLOAT
, #e FLOAT
, #v FLOAT
, #x FLOAT
, #y FLOAT
, #z FLOAT
, #xrot FLOAT
, #yrot FLOAT
, #zrot FLOAT
, #hx FLOAT
, #hy FLOAT
, #hz FLOAT
, #newLon FLOAT
, #newLat FLOAT
, #p FLOAT
, #errvalue FLOAT
, #lat0 FLOAT
SELECT #a2 = 6378137 -- WGS84_AXIS
, #e2 = 0.00669438037928458 -- WGS84_ECCENTRIC
, #h = #height -- height above datum (from $GPGGA sentence)
, #a = 6377563.396 -- OSGB_AXIS
, #e = 0.0066705397616 -- OSGB_ECCENTRIC
, #xp = 446.448
, #yp = -125.157
, #zp = 542.06
, #xr = 0.1502
, #yr = 0.247
, #zr = 0.8421
, #s = -20.4894
-- convert to cartesian; lat, lon are in radians
SET #sf = #s * 0.000001
SET #v = #a / (sqrt(1 - (#e * (SIN(#radOGlat) * SIN(#radOGlat)))))
SET #x = (#v + #h) * COS(#radOGlat) * COS(#radOGlon)
SET #y = (#v + #h) * COS(#radOGlat) * SIN(#radOGlon)
SET #z = ((1 - #e) * #v + #h) * SIN(#radOGlat)
-- transform cartesian
SET #xrot = (#xr / 3600) * #deg2rad
SET #yrot = (#yr / 3600) * #deg2rad
SET #zrot = (#zr / 3600) * #deg2rad
SET #hx = #x + (#x * #sf) - (#y * #zrot) + (#z * #yrot) + #xp
SET #hy = (#x * #zrot) + #y + (#y * #sf) - (#z * #xrot) + #yp
SET #hz = (-1 * #x * #yrot) + (#y * #xrot) + #z + (#z * #sf) + #zp
-- Convert back to lat, lon
SET #newLon = ATAN(#hy / #hx)
SET #p = SQRT((#hx * #hx) + (#hy * #hy))
SET #newLat = ATAN(#hz / (#p * (1 - #e2)))
SET #v = #a2 / (SQRT(1 - #e2 * (SIN(#newLat) * SIN(#newLat))))
SET #errvalue = 1.0;
SET #lat0 = 0
WHILE (#errvalue > 0.001)
BEGIN
SET #lat0 = ATAN((#hz + #e2 * #v * SIN(#newLat)) / #p)
SET #errvalue = ABS(#lat0 - #newLat)
SET #newLat = #lat0
END
--convert back to degrees
SET #newLat = #newLat * #rad2deg
SET #newLon = #newLon * #rad2deg
DECLARE #ReturnMe FLOAT
SET #ReturnMe = 0
IF #LatOrLng = 'Lat'
SET #ReturnMe = #newLat
IF #LatOrLng = 'Lng'
SET #ReturnMe = #newLon
RETURN #ReturnMe
END
GO
I ended up using the following javascript functions to convert the values. I know it's not a SQL solution but it did the job for me.
function OSGridToLatLong(E, N) {
var a = 6377563.396, b = 6356256.910; // Airy 1830 major & minor semi-axes
var F0 = 0.9996012717; // NatGrid scale factor on central meridian
var lat0 = 49*Math.PI/180, lon0 = -2*Math.PI/180; // NatGrid true origin
var N0 = -100000, E0 = 400000; // northing & easting of true origin, metres
var e2 = 1 - (b*b)/(a*a); // eccentricity squared
var n = (a-b)/(a+b), n2 = n*n, n3 = n*n*n;
var lat=lat0, M=0;
do {
lat = (N-N0-M)/(a*F0) + lat;
var Ma = (1 + n + (5/4)*n2 + (5/4)*n3) * (lat-lat0);
var Mb = (3*n + 3*n*n + (21/8)*n3) * Math.sin(lat-lat0) * Math.cos(lat+lat0);
var Mc = ((15/8)*n2 + (15/8)*n3) * Math.sin(2*(lat-lat0)) * Math.cos(2*(lat+lat0));
var Md = (35/24)*n3 * Math.sin(3*(lat-lat0)) * Math.cos(3*(lat+lat0));
M = b * F0 * (Ma - Mb + Mc - Md); // meridional arc
} while (N-N0-M >= 0.00001); // ie until < 0.01mm
var cosLat = Math.cos(lat), sinLat = Math.sin(lat);
var nu = a*F0/Math.sqrt(1-e2*sinLat*sinLat); // transverse radius of curvature
var rho = a*F0*(1-e2)/Math.pow(1-e2*sinLat*sinLat, 1.5); // meridional radius of curvature
var eta2 = nu/rho-1;
var tanLat = Math.tan(lat);
var tan2lat = tanLat*tanLat, tan4lat = tan2lat*tan2lat, tan6lat = tan4lat*tan2lat;
var secLat = 1/cosLat;
var nu3 = nu*nu*nu, nu5 = nu3*nu*nu, nu7 = nu5*nu*nu;
var VII = tanLat/(2*rho*nu);
var VIII = tanLat/(24*rho*nu3)*(5+3*tan2lat+eta2-9*tan2lat*eta2);
var IX = tanLat/(720*rho*nu5)*(61+90*tan2lat+45*tan4lat);
var X = secLat/nu;
var XI = secLat/(6*nu3)*(nu/rho+2*tan2lat);
var XII = secLat/(120*nu5)*(5+28*tan2lat+24*tan4lat);
var XIIA = secLat/(5040*nu7)*(61+662*tan2lat+1320*tan4lat+720*tan6lat);
var dE = (E-E0), dE2 = dE*dE, dE3 = dE2*dE, dE4 = dE2*dE2, dE5 = dE3*dE2, dE6 = dE4*dE2, dE7 = dE5*dE2;
lat = lat - VII*dE2 + VIII*dE4 - IX*dE6;
var lon = lon0 + X*dE - XI*dE3 + XII*dE5 - XIIA*dE7;
return {
longitude: lon.toDeg(),
latitude: lat.toDeg()
};
}
Number.prototype.toRad = function() { // convert degrees to radians
return this * Math.PI / 180;
}
Number.prototype.toDeg = function() { // convert radians to degrees (signed)
return this * 180 / Math.PI;
}
Number.prototype.padLZ = function(w) {
var n = this.toString();
for (var i=0; i<w-n.length; i++) n = '0' + n;
return n;
}
I needed the same function, and javascript made it difficult to interact with the DB. I have converted your JS to PHP and this could be more useful when updating your database - ie: query table, loop through result set, call function, update table.
function OSGridToLatLong($E, $N) {
$a = 6377563.396;
$b = 6356256.910; // Airy 1830 major & minor semi-axes
$F0 = 0.9996012717; // NatGrid scale factor on central meridian
$lat0 = 49*M_PI/180;
$lon0 = -2*M_PI/180; // NatGrid true origin
$N0 = -100000;
$E0 = 400000; // northing & easting of true origin, metres
$e2 = 1 - ($b*$b)/($a*$a); // eccentricity squared
$n = ($a-$b)/($a+$b);
$n2 = $n*$n;
$n3 = $n*$n*$n;
$lat=$lat0;
$M=0;
do {
$lat = ($N-$N0-$M)/($a*$F0) + $lat;
$Ma = (1 + $n + (5/4)*$n2 + (5/4)*$n3) * ($lat-$lat0);
$Mb = (3*$n + 3*$n*$n + (21/8)*$n3) * sin($lat-$lat0) * cos($lat+$lat0);
$Mc = ((15/8)*$n2 + (15/8)*$n3) * sin(2*($lat-$lat0)) * cos(2*($lat+$lat0));
$Md = (35/24)*$n3 * sin(3*($lat-$lat0)) * cos(3*($lat+$lat0));
$M = $b * $F0 * ($Ma - $Mb + $Mc - $Md); // meridional arc
} while ($N-$N0-$M >= 0.00001); // ie until < 0.01mm
$cosLat = cos($lat);
$sinLat = sin($lat);
$nu = $a*$F0/sqrt(1-$e2*$sinLat*$sinLat); // transverse radius of curvature
$rho = $a*$F0*(1-$e2)/pow(1-$e2*$sinLat*$sinLat, 1.5); // meridional radius of curvature
$eta2 = $nu/$rho-1;
$tanLat = tan($lat);
$tan2lat = $tanLat*$tanLat;
$tan4lat = $tan2lat*$tan2lat;
$tan6lat = $tan4lat*$tan2lat;
$secLat = 1/$cosLat;
$nu3 = $nu*$nu*$nu;
$nu5 = $nu3*$nu*$nu;
$nu7 = $nu5*$nu*$nu;
$VII = $tanLat/(2*$rho*$nu);
$VIII = $tanLat/(24*$rho*$nu3)*(5+3*$tan2lat+$eta2-9*$tan2lat*$eta2);
$IX = $tanLat/(720*$rho*$nu5)*(61+90*$tan2lat+45*$tan4lat);
$X = $secLat/$nu;
$XI = $secLat/(6*$nu3)*($nu/$rho+2*$tan2lat);
$XII = $secLat/(120*$nu5)*(5+28*$tan2lat+24*$tan4lat);
$XIIA = $secLat/(5040*$nu7)*(61+662*$tan2lat+1320*$tan4lat+720*$tan6lat);
$dE = ($E-$E0);
$dE2 = $dE*$dE;
$dE3 = $dE2*$dE;
$dE4 = $dE2*$dE2;
$dE5 = $dE3*$dE2;
$dE6 = $dE4*$dE2;
$dE7 = $dE5*$dE2;
$lat = $lat - $VII*$dE2 + $VIII*$dE4 - $IX*$dE6;
$lon = $lon0 + $X*$dE - $XI*$dE3 + $XII*$dE5 - $XIIA*$dE7;
return array(
'longitude' => $lon * 180 / M_PI,
'latitude' => $lat * 180 / M_PI
);
}
If anyone's interested in non-SQL solution I strongly recommend using this http://www.howtocreate.co.uk/php/gridref.php PHP/JavaScript class.
One important thing to mention here is the library supports Helmert transformation.
PHP
$grutoolbox = Grid_Ref_Utils::toolbox();
$source_coords = Array(54.607720,-6.411990);
//get the ellipsoids that will be used
$Airy_1830 = $grutoolbox->get_ellipsoid('Airy_1830');
$WGS84 = $grutoolbox->get_ellipsoid('WGS84');
$Airy_1830_mod = $grutoolbox->get_ellipsoid('Airy_1830_mod');
//get the transform parameters that will be used
$UK_to_GPS = $grutoolbox->get_transformation('OSGB36_to_WGS84');
$GPS_to_Ireland = $grutoolbox->get_transformation('WGS84_to_Ireland65');
//convert to GPS coordinates
$gps_coords = $grutoolbox->Helmert_transform($source_coords,$Airy_1830,$UK_to_GPS,$WGS84);
//convert to destination coordinates
print $grutoolbox->Helmert_transform($source_coords,$WGS84,$GPS_to_Ireland,$Airy_1830_mod,$grutoolbox->HTML);
JavaScript
var grutoolbox = gridRefUtilsToolbox();
var sourceCoords = [54.607720,-6.411990];
//get the ellipsoids that will be used
var Airy1830 = grutoolbox.getEllipsoid('Airy_1830');
var WGS84 = grutoolbox.getEllipsoid('WGS84');
var Airy1830Mod = grutoolbox.getEllipsoid('Airy_1830_mod');
//get the transform parameters that will be used
var UKToGPS = grutoolbox.getTransformation('OSGB36_to_WGS84');
var GPSToIreland = grutoolbox.getTransformation('WGS84_to_Ireland65');
//convert to GPS coordinates
var gpsCoords = grutoolbox.HelmertTransform(sourceCoords,Airy1830,UKToGPS,WGS84);
//convert to destination coordinates
element.innerHTML = grutoolbox.HelmertTransform(sourceCoords,WGS84,GPSToIreland,Airy1830Mod,grutoolbox.HTML);
I have developed a library in .NET to be called from transact sql Converts WGS84/UTM coordinates to Latitude and Longitude
It does just the opposite, but as it uses CoordinateSharp you can download the code and change it easily to convert from lat/long to wgs84 instead.
You can download it from github:
https://github.com/j-v-garcia/UTM2LATITUDE
usage:
SELECT dbo.UTM2LATITUDE(723399.51,4373328.5,'S',30) AS Latitude, dbo.UTM2LONGITUDE(723399.51,4373328.5,'S',30) AS Longitude
result:
39,4805657453054 -0,402592727245112
<param name="XUTM">pos UTM X</param>
<param name="YUTM">pos UTM Y</param>
<param name="LatBand">Latitude band grid zone designation letter (see http://www.dmap.co.uk/utmworld.htm) </param>
<param name="LongBand">Longitude band grid zone designation number (see http://www.dmap.co.uk/utmworld.htm) </param>