I have some data which I bulk import into this table structure:
CREATE TABLE #Temp
(
WellKnownText NVARCHAR(MAX)
)
Some of the entries are not valid. So something like this:
SELECT geometry::STPolyFromText(WellKnownText,4326) FROM #Temp
does not work for all rows and thus falls over.
What is the best way to detect which WellKnownText are not valid? I have used MakeValid in the past - so ideally I would like to fix entries as much as possible.
PS:
This does not work:
SELECT * FROM #Temp
WHERE geometry::STPolyFromText(WellKnownText,4326).STIsValid() = 0
PPS:
I chose a loop based approach in the end along those lines:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp;
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL DROP TABLE #Temp1;
DECLARE #LoopCounter INT = 1;
DECLARE #MaxCounter INT;
DECLARE #Valid BIT;
DECLARE #ValidCounter INT;
DECLARE #WellKnownText NVARCHAR(MAX);
CREATE TABLE #Temp
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
CREATE TABLE #Temp1
(
Guid UNIQUEIDENTIFIER,
PostcodeFraction NVARCHAR(50),
WellKnownText NVARCHAR(MAX),
GeoJson NVARCHAR(MAX)
);
BULK INSERT #Temp FROM 'D:\PolygonData.txt' WITH (FIELDTERMINATOR = '\t', FIRSTROW = 2, ROWTERMINATOR = '\n');
ALTER TABLE #Temp ADD Id INT IDENTITY(1,1);
SELECT #MaxCounter = MAX(Id) FROM #Temp
SET #ValidCounter = 0;
WHILE(#LoopCounter <= #MaxCounter)
BEGIN
BEGIN TRY
SELECT #WellKnownText = WellKnownText FROM #Temp WHERE Id = #LoopCounter;
SET #Valid = GEOMETRY::STGeomFromText(#WellKnownText,4326).STIsValid();
SET #ValidCounter = #ValidCounter + 1;
END TRY
BEGIN CATCH
SET #Valid = 0;
END CATCH
IF(#Valid = 1)
BEGIN
INSERT INTO #TEMP1
SELECT Guid, PostcodeFraction, WellKnownText, GeoJson FROM #Temp WHERE Id = #LoopCounter;
END
SET #LoopCounter = #LoopCounter + 1;
END
PRINT #ValidCounter;
SELECT * FROM #TEMP1;
As requested in the comments, some possible solutions
I guess you're really looking for a function that can be CROSS APPLYed, something like
SELECT * FROM #Temp T
CROSS APPLY IsWKTValidFunc(T.WellKnownText, 4326) F
WHERE F.IsValid = <somecondition>
(Or even added to as computed column to give you a flag that's set on inserting your WKT)
Stored Proc
https://gis.stackexchange.com/questions/66642/detecting-invalid-wkt-in-text-column-in-sql-server has a simple SP that wraps GEOMETREY::STGeomFromText in a try catch block.
However, stored procs cannot be CROSS APPLYed (or called from a UDF that can be) so this would result in a cursor based solution.
UDF
A UDF can be cross applied, but can't have a TRY-CATCH block. You also can't call the above SP from a UDF. So not much use there.
CLR UDF
Wrap the GEOMETREY::STGeomFromText call in a CLR UDF that can be CROSS APPLIED, can have try catch and other error checking, rules etc, and return a flag indicating valid text. I haven't tried this one out but this sounds like the best option if CLR is enabled in your environment.
Hope this gives you some ideas. Feedback in the comments to these suggestions appreciated.
Related
I have a scalar value function that returns a VarChar(MAX) In my stored procedure I do this
declare #p_emailAddr varchar(MAX) = (select db.dbo.GetEmails(10))
If I do print #p_emailAddr it shows me it was populated with the correct information but the rest of the code doesn't work correctly using it. (I have no clue why, it doesn't make sense!)
Now if I change it like this
declare #p_emailAddr varchar(MAX) = 'test#email.com;'
The rest of my code works perfect as it should!
What is the difference between the two methods of setting #p_emailAddr that is breaking it?
This is get emails code
ALTER FUNCTION [dbo].[GetEmails](#p_SubID int)
RETURNS varchar(max)
AS
BEGIN
DECLARE #p_Emails varchar(max)
SELECT #p_Emails = COALESCE(#p_Emails + ';', '') + E.EmailAddress
FROM db.dbo.UserEmailAddr E JOIN
db.dbo.EmailSubscriptionUsers S on e.ClockNumber = s.Clock AND S.SubID = #p_SubID
SET #p_Emails = #p_Emails + ';'
RETURN #p_Emails
END
What's coming back from GetEmails(10)? varchar(max) is a string value and is expecting a single value. you could have a table variable or if dbo.getemails(10) is a table just join it where you're expecting to use #p_emailaddr
best
select *
from table1 t1
join dbo.GetEmails(10) e
on e.email = t1.email
alternative
create table #GetEmails (emails varchar(max))
insert into #GetEmails values ('email#test.com'), ('test#email.com')
declare #p_emailAddr table (emails varchar(max))
insert into #p_emailAddr(emails)
select *
from #GetEmails
select *
from #p_emailAddr
I am working on C# project which needs a stored procedure which will take two table names as inputs.
First table will copy data to a temp table which has two columns URL & channelID. This URL column is then matched with other input table's URL column & if match is found then it will update channel id from temp table to other tables channel ID.
I have written stored procedure as
CREATE PROCEDURE [dbo].[UpdateTables]
#excelTable NVARCHAR(128) ,
#TableName NVARCHAR(128)
AS
Declare #channel_Id nvarchar(50)
Declare #url varchar(400)
BEGIN
Select *
Into #Temp
From QUOTENAME(#excelTable)
END
While EXISTS(SELECT * From #Temp ) > 0
Begin
Select Top 1
#channel_Id = channel_Id, #url = url
From #Temp
update QUOTENAME(#TableName)
set channelid = #channelid
where pagefullurl like '%'+ #url + '%'
Delete #Temp
Where channelid = #channelid
End
I don't have much knowledge in TSQL and my above code has errors.
Incorrect syntax near '>'.
Msg 137, Level 15, State 2, Procedure UpdateTables, Line 20
Must declare the scalar variable "#channelid".
Msg 137, Level 15, State 2, Procedure UpdateTables, Line 22
Must declare the scalar variable "#channelid".
Please suggest what changes needs to done
I don't have MS SQL server handy to test it, but you declare your variable as #channel_Id, and later try to use it as #channelid (without the underscore) so you get errors about the undeclared variable.
I've corrected your SP and this is how it should look
CREATE PROCEDURE [dbo].[UpdateTables]
#excelTable NVARCHAR(128) ,
#TableName NVARCHAR(128)
AS
Declare #channel_Id nvarchar(50)
Declare #url varchar(400)
BEGIN
Select *
Into #Temp
From QUOTENAME(#excelTable)
While EXISTS(SELECT * From #Temp )
Begin
Select Top 1
#channel_Id = channel_Id, #url = url
From #Temp
update QUOTENAME(#TableName)
set channelid = #channel_Id
where pagefullurl like '%'+ #url + '%'
Delete #Temp
Where channelid = #channel_Id
End
END
I need to delete (read/update) some values in some tables, and would like to use a stored procedure to reduce security issues.
Since tables are many and records even more and it is not reasonable to write a stored procedure for each combination, and since everybody has this kind of need, I thought could have been easy to find a stored procedure to do this.. but googling a lot I did not find a simple and short answer, so I tried to build my own stored procedure. But I'm afraid it could have some security issues: principally when I declare #Table as nvarchar(30).. I tried to declare as TABLE but it returns error..
Can suggest what is not acceptable and suggest a solution?
Thanks
Here the stored procedure for deleting.. but for other action could be similar:
CREATE PROCEDURE dbo.spDeleteRecord
(
#UID nvarchar(20) = NOT NULL,
#PWD nvarchar(30) = NOT NULL,
#Table sysname,
#WhereField sysname,
#WhereValue nvarchar(150) = NOT NULL
)
AS
SET NOCOUNT ON;
DECLARE #S nvarchar(max) = '',
#P nvarchar(max) = ''
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_Table sysname,
#_WhereField sysname,
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #UID, #PWD, #Table, #WhereField, #WhereValue
SET NOCOUNT OFF
Thanks for reading
Joe
You would need to do it like this:
SET #S = 'DELETE t
from dbo.'+quotename(#Table)+' t
join dbo.subUsers su on t.UID = su.UID
where ' + quotename(#WhereField) + ' = #_WhereValue
and su.SUID = #_UID
and su.PWD = #_PWD'
SET #P = '#_UID nvarchar(50),
#_PWD nvarchar(50),
#_WhereValue nvarchar(150)'
--PRINT #S
EXEC sp_executesql #S, #P, #_WhereValue = #WhereValue, #_UID = #UID, #_PWD = #PWD
Basically, the parameter list can only refer to parameters that are actually embedded in the SQL string.
Also, note that #Table and #WhereField would be more correct as datatype sysname. I would also probably have restricted #UID, #PWD, and #WhereValue to NOT NULL because I hate unhandled nulls.
However, you really need to consider if you want to do this. To me this feels like leaving a loaded gun lying around. What happens when you call this with a table that happens to have a UID field that happens to coincide to the values in the dbo.subUsers table even though no relation exists there? I don't see any significant advantage of this method over just running the parameterized query from your application, and the fact that the query changes between executions means that you may run into problems with parameter sniffing so you may end up with suboptimal execution plans.
I'm trying to use cursors to dynamically produce a result set. following is the code
DECLARE # MilestoneName VARCHAR(100),
#MilestoneSts VARCHAR(100),
#ProjectPre VARCHAR(10),
#ProjectID VARCHAR(10),
#Center VARCHAR(20),
#CenterPre VARCHAR(20),
#Source VARCHAR(20),
#Actual INT;
SET #MilestoneName = null;
SET #MilestoneSts = null;
SET #ProjectPre = null;
SET #CenterPre = null;
DECLARE s_cursor CURSOR FOR
SELECT ProjectID, Center, Source, Actual
FROM #MILESTONE
OPEN s_cursor
FETCH NEXT FROM s_cursor INTO #ProjectID, #Center, #Source, #Actual
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT ##FETCH_STATUS sts, #ProjectID PID, #Center Center, #Source Source, #Actual Actual
FETCH NEXT FROM s_cursor INTO #ProjectID, #Center, #Source, #Actual
END
CLOSE s_cursor
DEALLOCATE s_cursor
However using that I'm able to produce 79 results of single rows but I want to union all those rows into one result.. any possible solution will be highly appreciated..
just checking, why are you using a cursor for this?
This sproc could be replaced by just saying
SELECT ProjectID, Center, Source, Actual
FROM #MILESTONE
But maybe I'm missing somehting here?
If there's logic you left out in your code look at this post: Multi-statement Table Valued Function vs Inline Table Valued Function
GJ
In my stored procedure I have multiple similar variables #V1, #V2 ... #V20 (let's say 20 of them) FETCHED from a record. How would I use dynamic SQL to make 20 calls to another stored procedure using those variables as parameters?
Of course #V[i] syntax is incorrect, but it expresses the intent
fetch next from maincursor into #status, #V1, #V2, ...
while #i<21
begin
-- ??? execute sp_executesql 'SecondSP', '#myParam int', #myParam=#V[i]
-- or
-- ??? execute SecondSP #V[i]
set #i = #i+1
end
As others have said, set up a temporary table, insert the values that you need into it. Then "iterate" through it executing the necessary SQL from those values. This will allow you to have 0 to MANY values to be executed, so you don't have to set up a variable for each.
The following is a complete sample of how you may go about doing that without cursors.
SET NOCOUNT ON
DECLARE #dict TABLE (
id INT IDENTITY(1,1), -- a unique identity column for reference later
value VARCHAR(50), -- your parameter value to be passed into the procedure
executed BIT -- BIT to mark a record as being executed later
)
-- INSERT YOUR VALUES INTO #dict HERE
-- Set executed to 0 (so that the execution process will pick it up later)
-- This may be a SELECT statement into another table in your database to load the values into #dict
INSERT #dict
SELECT 'V1Value', 0 UNION ALL
SELECT 'V2Value', 0
DECLARE #currentid INT
DECLARE #currentvalue VARCHAR(50)
WHILE EXISTS(SELECT * FROM #dict WHERE executed = 0)
BEGIN
-- Get the next record to execute
SELECT
TOP 1 #currentid = id
FROM #dict
WHERE executed = 0
-- Get the parameter value
SELECT #currentvalue = value
FROM #dict
WHERE id = #currentid
-- EXECUTE THE SQL HERE
--sp_executesql 'SecondSP', '#myParam int', #myParam =
PRINT 'SecondSP ' + '#myParam int ' + '#myParam = ' + #currentvalue
-- Mark record as having been executed
UPDATE d
SET executed = 1
FROM #dict d
WHERE id = #currentid
END
Use a #TempTable
if you are at SQL Server 2005 you can create a #TempTable in the parent stored procedure, and it is available in the child stored procedure that it calls.
CREATE TABLE #TempTable
(col1 datatype
,col2 datatype
,col3 datatype
)
INSERT INTO #TempTable
(col1, col2, col3)
SELECT
col1, col2, col3
FROM ...
EXEC #ReturnCode=YourOtherProcedure
within the other procedure, you have access to #TempTable to select, delete, etc...
make that child procedure work on a set of data not on one element at a time
remember, in SQL, loops suck performance away!
Why not just use the table variable instead, and then just loop through the table getting each value.
Basically treat each row in a table as your array cell, with a table that has one column.
Just a thought. :)
This seems like an odd request - will you always have a fixed set of variables? What if the number changes from 20 to 21, and so on, are you constantly going to have to be declaring new variables?
Is it possible, instead of retrieving the values into separate variables, to return them each as individual rows and just loop through them in a cursor?
If not, and you have to use the individual variables as explained, here's one solution:
declare #V1 nvarchar(100)
set #V1 = 'hi'
declare #V2 nvarchar(100)
set #V2 = 'bye'
declare #V3 nvarchar(100)
set #V3 = 'test3'
declare #V4 nvarchar(100)
set #V4 = 'test4'
declare #V5 nvarchar(100)
set #V5 = 'end'
declare aCursor cursor for
select #V1
union select #V2 union select #V3
union select #V4 union select #V5
open aCursor
declare #V nvarchar(100)
fetch next from aCursor into #V
while ##FETCH_STATUS = 0
begin
exec TestParam #V
fetch next from aCursor into #V
end
close aCursor
deallocate aCursor
I don't really like this solution, it seems messy and unscalable. Also, as a side note - the way you phrased your question seems to be asking if there are arrays in T-SQL. By default there aren't, although a quick search on google can point you in the direction of workarounds for this if you absolutely need them.