Query table records with points (geometry) within area - sql

I have a table (locations) that has a field called Point (geometry). I wrote a query that passes the top and bottom latitude coordinates and the bottom and top longitude coordinates. I want to retrieve all records that are within the area of the coordinates I pass the stored procedure. When I run this it returns zero records even though I know there is a record that matches the criteria. Any ideas what I might be doing wrong?
DECLARE #categoryid AS int,#leftlong AS float,#rightlong AS float,#toplat AS float,#bottomlat AS float
DECLARE #searcharea geometry, #polygon AS varchar(500);
SET #leftlong = -85.605469
SET #toplat = 42.303468
SET #rightlong = -85.594912
SET #bottomlat = 42.297564
SET #polygon = CAST(#leftlong AS varchar(20)) + ' ' + CAST(#toplat AS varchar(20)) + ',' +
CAST(#leftlong AS varchar(20)) + ' ' + cast(#bottomlat AS varchar(20)) + ',' +
cast(#rightlong AS varchar(20)) + ' ' + cast(#bottomlat AS varchar(20)) + ',' +
cast(#rightlong AS varchar(20)) + ' ' + cast(#toplat AS varchar(20)) + ',' +
CAST(#leftlong AS varchar(20)) + ' ' + CAST(#toplat AS varchar(20))
SET #searcharea = geometry::STGeomFromText('POLYGON ((' + #polygon + '))', 0);
SELECT *
FROM locations l
WHERE l.point.STWithin(#searcharea) = 1

There are two likely potential sources of the problem:
Usage of geometry instead of geography type. When working with latitude and longitude data, you generally want to use the geography type in order to do calculations on the ellipsoid instead of on the plane. These calculations can different considerably - this is a lot of information available on this distinction in various articles such as this whitepaper.
Mismatching SRIDs - is the srid of the data in your locations table also 0 for all rows? If these do not match between your data and your #searcharea, no results will be returned.

Related

Convert string coordinates to geography

I have string coordinates in my table but I want to do some geographical functionalities, So I need first to convert this string value to geography.like this:
geography::STGeomFromText('POINT([location])', 4326).MakeValid().STDistance(#p)
but for sure this code didn't work as it needs here point not string coordinates.
The full code:
geography::STGeomFromText('POINT([location])', 4326).MakeValid().STDistance(#p);
DECLARE #p geography;
SET #p = geography::STGeomFromText('POINT({$Lon} {$Lat})', 4326);
Select TOP 1 id, location from branches where {$location} <= {$this->radius} order by {$location}
It's a little difficult to provide a perfect solution without seeing how the code is interporlating the variables, but SQL could be having issues recognizing your long/lat as strings with the STGeomFromText method.
Could you try something like this:
SELECT geography::STGeomFromText('POINT(' + CAST([$Long] AS VARCHAR(20)) + ' ' + CAST([$Lat] AS VARCHAR(20)) + ')', 4326)
Or more succinctly:
SELECT geography::Point([$Lat], [$Long], 4326)

Using varables in sql spatial data

I am attempting to write an sql stored procedure that will determine if a point is within a buffer. So far I have been able to hard code the values in the query and they work as expected but when I attempt to replace the hard coded values with variables then I get problems.
This works:
DECLARE #g geography;
Declare #Latitude float;
declare #Longitude float;
Set #g = geography::STPointFromText('POINT(' + CAST(#longitude as VARCHAR(16)) + ' ' + CAST(#latitude as VARCHAR(16)) + ')', 4326)
declare #bufloc geography = geography::Point(52.677777, -1.280786,
4326).STBuffer(1000)
SELECT CASE #g.STIntersects(#bufloc) WHEN 1 THEN '#g intersects #h'
ELSE '#g does not intersect #h'
END;
But if I declare a second set of coordinates and the try to pass variables into the #bufloc I break the query.
Declare #BufLongitude float; Declare #DistanceInMeteres int;
declare #bufloc geography = geography::STPointFromText('POINT(' +
CAST(#BufLatitude as VARCHAR(16)) + ' ' + CAST(#BufLongitude as
VARCHAR(16)) + ')', 4326).STBuffer(1000)
How would I write this to get things working? Ultimatly I would like to pass two coordinates into a stored procedure along with a distance parameter to use in the construction of the buffer and return true or false if the point can be located in the resulting buffer.
The query returns a false value it returns '#g does not intersect #h' when the hard coded query returns '#g intersects #h' for the same value.

UPDATE for add spaces in specific places | SQL Server

I am trying to add spaces in specific places of my string if it's < 143(the normal length of a row Regist) , if it's less of 143 then take the difference and add that quantity of ' ' Spaces. The update doesn't work, when I execute, it appears like affect rows but when i look for the LEN of the row that change it doesn't do anything. I am using this:
UPDATE TABLE
SET Regist = (SUBSTRING(Regist,1,(57 - (143 - LEN(Regist)))) +
REPLICATE(' ',(143 - LEN(Regist))) + SUBSTRING(Regist,(58 - (143 - LEN(Regist))),(LEN(Regist) - (58 - (143 - LEN(Regist))))))
WHERE
(LEN(Regist) - 143) < 0
AND Name = 'SQL SERVER V2008'
Example:
[Name]
Excel
--This row has length 142 (including the spaces after v2008)
[Regist]
ABCDEF12345678910111213411121341SQL SERVER V2008 A1111111111111 1111111111111111111111111111111111111111111111111111111111111111111111
So, i did that update to add a space (Just 1 in this case 143 - 142) in the place 56 (because i know that the name finish in place 57 but the row length is less 143, then 57 - (143-142 ) = 56 and that is the position of where is going to be the space.
LEN won't be a good tell of this. It doesn't count trailing blank space.
Returns the number of characters of the specified string expression, excluding trailing blanks.
Documentation
You could do something like:
SELECT LEN(Regist + 'x') - 1
Which would then count your blanks. (Note, you should do this for your length checks in your update too. Otherwise you may be adding way more blank space then you intend, especially if you run multiple times.)
Edit:
Try this:
DECLARE #SEARCHSTRING VARCHAR(100) = 'SQL SERVER V2008'
SELECT REGIST,
LEFT(REGIST,CHARINDEX(#SEARCHSTRING,REGIST,1) + LEN(#SEARCHSTRING) -1) + REPLICATE(' ',143 - LEN(REGIST)) + RIGHT(REGIST,143 - LEN(LEFT(REGIST,CHARINDEX(#SEARCHSTRING,REGIST,1) + LEN(#SEARCHSTRING) -1)) - (143 - LEN(REGIST)))
FROM yourTable
WHERE Name = #SearchString
AND LEN(REGIST) < 143
--UPDATE yourTable
--SET Regist = LEFT(REGIST,CHARINDEX(#SEARCHSTRING,REGIST,1) + LEN(#SEARCHSTRING) -1) + REPLICATE(' ',143 - LEN(REGIST)) + RIGHT(REGIST,143 - LEN(LEFT(REGIST,CHARINDEX(#SEARCHSTRING,REGIST,1) + LEN(#SEARCHSTRING) -1)) - (143 - LEN(REGIST)))
--WHERE Name = #SEARCHSTRING --Or whatever conditions you want
--AND LEN(Regist) < 143
Here is a breakdown you can run to see what's happening:
DECLARE #REGIST VARCHAR(200) = 'ABCDEF12345678910111213411121341SQL SERVER V2008 A1111111111111 1111111111111111111111111111111111111111111111111111111111111111111111'
DECLARE #REGISTLENGTH INT = LEN(#REGIST)
DECLARE #NUMSPACES INT = 143 - LEN(#REGIST)
DECLARE #SEARCHSTRING VARCHAR(100) = 'SQL SERVER V2008'
DECLARE #LOCATION INT = CHARINDEX(#SEARCHSTRING,#REGIST,1) + LEN(#SEARCHSTRING) -1
DECLARE #LEFTPART VARCHAR(200) = LEFT(#REGIST,#LOCATION)
DECLARE #RIGHTPART VARCHAR(200) = RIGHT(#REGIST,143 - LEN(#LEFTPART) - #NUMSPACES)
DECLARE #NEWREGIST VARCHAR(200) = #LEFTPART + REPLICATE(' ',#NUMSPACES) + #RIGHTPART
DECLARE #NEWREGISTLENGTH INT = LEN(#NEWREGIST)
PRINT 'Original string: ' + #REGIST
PRINT 'Original length: ' + CAST(#REGISTLENGTH AS VARCHAR(10))
PRINT 'Spaces to add: ' + CAST(#NUMSPACES AS VARCHAR(10))
PRINT 'Searchstring: ' + #SEARCHSTRING
PRINT 'Left of searchstring: ' + #LEFTPART
PRINT 'Right of searchstring: ' + #RIGHTPART
PRINT 'New string: ' + #NEWREGIST
PRINT 'New string length: ' + CAST(#NEWREGISTLENGTH AS VARCHAR(10))
If you take the final #NEWREGIST line and break it's parts back down, you'll be left with the update above.
The main goal here was is to make the select/update more scale-able. You can change #SEARCHSTRING to anything you want and this will add the correct number of spaces after that string.
Try it like this...
UPDATE yt SET
yt.Regist = LEFT(yt.Regist + REPLICATE(' ', 143), 143)
FROM
dbo.YourTable yt
WHERE
yt.name = 'Excel'
AND LEN(yt.Regist) < 143;

Averaging out Lat/longs in SQL Server database

I'm new to SQL Server. I'm trying to figure out how I can get the below one done:
I have thousands of lat/long positions pointing to the same OR very close by locations. It's all stored flat in a SQL Server table as LAT & LONG columns.
Now to cluster the lat/longs and pick one representation per cluster, what I must be doing?
I read through a method called "STCentroid" :
https://msdn.microsoft.com/en-us/library/bb933847.aspx
But is it worth letting the Server do a polygon with all these million rows and find the center point? Which would implicitly mean a single representation for all the near by duplicates. Might be an in efficient/wrong way?
Only points around few meters must be considered as duplicate entries.
I'm thinking how I can pick the right representation.
In better words:
If there's a group of points G1{} (GPS positions) trying to point to a location L1. (Physical loc). & There's a group of points G2{}, trying to point to a location L2. How do I derive Center Point CP1 from G1{}. & CP2 from G2{}, such that CP1 is very close to L1 & CP2 is very close to L2.
And the fact is, L1 & L2 could be very near to each other say, 10 feet.
Just thinking how do I approach this problem. Any help please?
Clustering points will be problematic. You are going to have issues if you have two potential clusters close together, and if you need precision or optimization, then you will need to do some research on your implementation. Try: Wiki-Cluster Analysis
However, if the points clusers are fairly far apart, then you could try a fairly simple cluster and then find the envelopes.
Something like this may work, although you would be well served to actually make a spatial column and add a spatial index.
ALTER TABLE Recordset ADD (ClusterID INT) -- Add a grouping ID
GO
DECLARE #i INT --Group Counter
DECLARE #g GEOGRAPHY --Point from which the cluster will be made
DECLARE #Limit INT --Distance limitation
SET #Limit = 10
SET #i = 0
WHILE (SELECT COUNT(*) FROM Recordset R WHERE ClusterID IS NULL) > 0 --Loop until all points are clustered
BEGIN
SET #g = (SELECT TOP 1 GEOGRAPHY::STPointFromText('POINT(' + CAST(LAT AS VARCHAR(20)) + ' ' + CAST(LONG AS VARCHAR(20)) + ')', 4326) WHERE ClusterID IS NULL) --Point to cluster on
UPDATE Recordset SET ClusterID = #i WHERE GEOGRAPHY::STPointFromText('POINT(' + CAST(LAT AS VARCHAR(20)) + ' ' + CAST(LONG AS VARCHAR(20)) + ')', 4326).STDistance(#g) < #Limit AND ClusterID IS NULL--update all points within the limit circle
SET #i = #i + 1
END
SELECT --Clustered centers
ClusterID,
GEOGRAPHY::ConvexHullAggregate(GEOGRAPHY::STPointFromText('POINT(' + CAST(LAT AS VARCHAR(20)) + ' ' + CAST(LONG AS VARCHAR(20)) + ')', 4326)).EnvelopeCenter().Lat AS 'LatCenter',
GEOGRAPHY::ConvexHullAggregate(GEOGRAPHY::STPointFromText('POINT(' + CAST(LAT AS VARCHAR(20)) + ' ' + CAST(LONG AS VARCHAR(20)) + ')', 4326)).EnvelopeCenter().Long AS 'LatCenter',
FROM
RecordSet
GROUP BY
ClusterID

Text column not storing more than 8000 characters

I researched this and found that a text column in SQL Server can store a lot more than 8000 characters. But when I run the following insert in the text column, it only inserts 8000 characters:
UPDATE a
SET [File] = b.Header + CHAR(13) + CHAR(10) + d.Detail + c.Trailer + CHAR(13) + CHAR(10) + CHAR(26)
FROM Summary a
JOIN #Header b ON b.SummaryId = a.SummaryId
JOIN #Trailer c ON c.SummaryId = a.SummaryId
JOIN #Detail d ON d.SummaryId = a.SummaryId
WHERE
a.SummaryId = #SummaryId
I am trying to generate a fixed width flat file and every row should be 3900 characters long, and they are in the respective temp tables. But when I do the insert in the permanent table, the Trailer data gets truncated.
I am adding char(10) + char(13) to add carriage return and line feed and char(26) for end of file, and it seems like they are adding characters to the fixed width layout.
According to http://msdn.microsoft.com/en-us/library/ms187993.aspx TEXT fields are deprecated. Use VARCHAR(MAX) fields instead. They should support 2GB in text.
The problem with your code is not the data type of the field that you store the value in, it's the type of the value that you put together to store in it.
The type of b.Header is not text but varchar, which is used as type for the whole expression. When the strings are concatenated, the result will be truncated to fit in a varchar value.
If you cast the first string to text, the whole expression gets that type, and can become longer than 8000 characters:
SET [File] = cast(b.Header as text) + CHAR(13) + CHAR(10) + d.Detail + c.Trailer + CHAR(13) + CHAR(10) + CHAR(26)
Naturally you should transition into using the new type varchar(max) instead of text, but that is not the reason for your problem.
Your source fields aren't VARCHAR(MAX), so there is an 8000 character limit when concatenating them together, you can fix this by casting the first source field in the concat list as VARCHAR(MAX):
UPDATE a
SET [File] = CAST(b.Header AS VARCHAR(MAX)) + CHAR(13) + CHAR(10) + d.Detail + c.Trailer + CHAR(13) + CHAR(10) + CHAR(26)
FROM Summary a
JOIN #Header b ON b.SummaryId = a.SummaryId
JOIN #Trailer c ON c.SummaryId = a.SummaryId
JOIN #Detail d ON d.SummaryId = a.SummaryId
WHERE a.SummaryId = #SummaryId
If you concat a thousand VARCHAR(25) fields together, the length of the resulting string would be 8000, as that's the limit of the VARCHAR() type when supplied a numeric length. VARCHAR(MAX) does not share this limit, but the concat list inherets the type of the first string supplied. It's an interesting behavior, but that's how it works.
TEXT is deprecated - don't use it! Use VARCHAR(MAX) instead!
I think you need to explicitly cast all columns that you use in your UPDATE statement to VARCHAR(MAX) in order for this to work:
UPDATE a
SET [File] = CAST(b.Header AS VARCHAR(MAX)) + CHAR(13) + CHAR(10) +
CAST(d.Detail AS VARCHAR(MAX)) + CAST(c.Trailer AS VARCHAR(MAX))) + CHAR(13) + CHAR(10) + CHAR(26)
FROM Summary a
JOIN #Header b ON b.SummaryId = a.SummaryId
JOIN #Trailer c ON c.SummaryId = a.SummaryId
JOIN #Detail d ON d.SummaryId = a.SummaryId
WHERE
a.SummaryId = #SummaryId