So, I have a table named "lines" that has a field named "WKT" with the corresponding geography.
I builded a query that gives me the name of the line that was intersected, like this:
DECLARE #DF GEOGRAPHY
Set #DF=GEOGRAPHY::STLineFromText('LINESTRING(-9.564498 52.237100,-9.564906 52.243924,-9.565699 52.245563,-9.568173 52.251014,-9.567142 52.257567,-9.564291 52.262366,-9.563453 52.262972,-9.563447 52.262980,-9.563447 52.262980,-9.563447 52.262980)', 4326)
select name, #DF.STIntersects(WKT) AS inters
from lines WHERE #DF.STIntersects(WKT)=1
And it works very well. The problematic case is when this #DF line is a "go and return by the same path". In this case I wanted to know the names of the repeated lines that were intersected.
Is this possible to do?
Sure. Assuming that you've got a tally table (i.e. a table with numbers 1 through some large number):
DECLARE #DF GEOGRAPHY
Set #DF=GEOGRAPHY::STLineFromText('LINESTRING(-9.564498 52.237100,-9.564906 52.243924,-9.565699 52.245563,-9.568173 52.251014,-9.567142 52.257567,-9.564291 52.262366,-9.563453 52.262972,-9.563447 52.262980,-9.563447 52.262980,-9.563447 52.262980)', 4326)
WITH cte AS (
SELECT n, #DF.STCurveN(n) AS curve
FROM dbo.Numbers
WHERE n <= #DF.STNumCurves()
)
select name, #DF.STIntersects(WKT) AS inters
from lines AS l
JOIN cte AS c
ON l.WKT.STIntersects(c.curve) = 1
I'm sure that you could get away without using the CTE, but that was what made the most sense in my head.
Related
So, I'd like to grab a record from a table of results. Let's say that this is our "sample" record.
Once I have the sample record, I'd like to grab 10 results down the table, and check to see if the sample is sequential within this list of 10 results.
So, if our sample record was 124, I'd like to grab the 10 records before it, and check to see if they follow the sequence of 123, 122, 121, 120, etc.
Once I know that the sample result is in fact sequential down to 10 records, I would like to insert that record into a different table for keeping.
I am using SQL Server and T-SQL to do this, and pulling my hair out trying to do so. If anyone could offer any advice, I would GREATLY appreciate it. Here's what I have so far (with some data removed), with no idea if I'm on the right track.
declare #TestTable as table (a char(15), RowNumber integer)
declare #SampleNumber as char(15)
insert into #TestTable (a, RowNumber)
select top 10
[NUMBERS],
ROW_NUMBER() over (order by a) as RowNumber
from [TABLE]
where
[NUMBERS] like [CONDITIONS]
order by [NUMBERS] desc
With this, I'm trying to grab the result and also a set of row numbers, allowing me to iterate through them based on that row number. But, I'm getting an "Invalid column name 'a'" error when running. Feel free to forget about that error and write something totally new though, because I don't even know if I'm on the right track.
Again, any help would be appreciated.
I am not sure how well this would perform on a larger dataset, but as Peter Smith mentioned, this is possible by using lag to see what the value of the row x rows prior in an ordered window was, though be aware this will run for all rows in your table and return all those that meet the criteria, rather than randomly sampling:
-- Create a not quite sequential dataset
declare #t table(n int);
with n as
(
select row_number() over (order by (select null)) as n
,abs(checksum(newid())) % 14 as r
from sys.all_objects
)
insert into #t
select n
from n
where r > 2;
-- Output the original dataset
select *
from #t;
-- Only return rows that come after a certain number of sequential numbers
declare #seq int = 10;
with l as
(
select n
,n - lag(n,#seq,null) over (order by n) as l
from #t
)
select n
from l
where l = #seq;
new to this type of request in Microsoft SQL Server. I'm using a full outer join on two tables looking for records that are not matching in the right table (New) vs left table (Old). I'm trying to find the new scores from the new table so I can update a production table with the most recent scores, while still holding onto old scores that have not been updated yet. This is my set up
Select
a.customer
,a.date
,a.Cat score
,a.Dog score
,a.Mouse score
,b.customer
,b.date
,b.Cat score
,b.Dog score
,b.Mouse score
From Old Table a
Full Outer Join New Table b
ON a.customer = b.customer
AND a.date = b.date
AND a.Cat score = Cast(b.Cat score as Varchar)
AND a.Dog score = Cast(b.Dog score as Varchar)
AND a.Mouse score = Cast(b.Mouse score as Varchar)
Note--- Have to cast the scores as Varchar or else I could not get the join to work. "Conversion failed when converting the varchar value '9.0000' to data type int."
Results:
Both lists are 100% different without any matches
This can't be true because I can search the records in both tables manually and find the exact same result in both tables. Maybe there is a better way to do this type of update?
Your problem is the strings 9 and 9.0000 are not equal. They do not join. These table variables will be used to demo this:
DECLARE #TableA TABLE
(
CatScore Int
)
;
DECLARE #TableB TABLE
(
CatScore VARCHAR(10)
)
;
INSERT INTO #TableA (CatScore) VALUES (9);
INSERT INTO #TableB (CatScore) VALUES ('9.0000');
The first example highlights the mismatch.
Mismatching Join Example
SELECT
*
FROM
#TableA AS a
FULL OUTER JOIN #TableB AS b ON b.CatScore = CAST(a.CatScore AS VARCHAR(50))
Returned Value
CatScore CatScore
9 NULL
NULL 9.0000
What you need to do is match the data types and then the values. This example assumes:
Table A stores cat score as a integer.
Table B stores the same as a varchar.
Table B always includes 4 zeros after a full stop.
Matching Example
SELECT
*
FROM
#TableA AS a
FULL OUTER JOIN #TableB AS b ON b.CatScore = CAST(a.CatScore AS VARCHAR(50)) + '.0000'
Returns
CatScore CatScore
9 9.0000
Here the integer 9 has been cast into a varchar. The full stop and trailing zeros have then been added. It's not a decimal place, as these aren't really numbers.
The lesson to takeaway from this exercise is; always use the correct data type. Storing numbers in strings will cause problems further down the line.
UPDATE
It would make more sense to CAST both fields into a DECIMAL. Both integers and varchars, containing numeric like data, can be converted into decimals. When casting fields for matching you want to find the smallest data type that will hold all input from both source fields.
What I'm looking for is a way in MSSQL to create a complex IN or LIKE clause that contains a SET of values, some of which will be ranges.
Sort of like this, there are some single numbers, but also some ranges of numbers.
EX: SELECT * FROM table WHERE field LIKE/IN '1-10, 13, 24, 51-60'
I need to find a way to do this WITHOUT having to specify every number in the ranges separately AND without having to say "field LIKE blah OR field BETWEEN blah AND blah OR field LIKE blah.
This is just a simple example but the real query will have many groups and large ranges in it so all the OR's will not work.
One fairly easy way to do this would be to load a temp table with your values/ranges:
CREATE TABLE #Ranges (ValA int, ValB int)
INSERT INTO #Ranges
VALUES
(1, 10)
,(13, NULL)
,(24, NULL)
,(51,60)
SELECT *
FROM Table t
JOIN #Ranges R
ON (t.Field = R.ValA AND R.ValB IS NULL)
OR (t.Field BETWEEN R.ValA and R.ValB AND R.ValB IS NOT NULL)
The BETWEEN won't scale that well, though, so you may want to consider expanding this to include all values and eliminating ranges.
You can do this with CTEs.
First, create a numbers/tally table if you don't already have one (it might be better to make it permanent instead of temporary if you are going to use it a lot):
;WITH Numbers AS
(
SELECT
1 as Value
UNION ALL
SELECT
Numbers.Value + 1
FROM
Numbers
)
SELECT TOP 1000
Value
INTO ##Numbers
FROM
Numbers
OPTION (MAXRECURSION 1000)
Then you can use a CTE to parse the comma delimited string and join the ranges with the numbers table to get the "NewValue" column which contains the whole list of numbers you are looking for:
DECLARE #TestData varchar(50) = '1-10,13,24,51-60'
;WITH CTE AS
(
SELECT
1 AS RowCounter,
1 AS StartPosition,
CHARINDEX(',',#TestData) AS EndPosition
UNION ALL
SELECT
CTE.RowCounter + 1,
EndPosition + 1,
CHARINDEX(',',#TestData, CTE.EndPosition+1)
FROM CTE
WHERE
CTE.EndPosition > 0
)
SELECT
u.Value,
u.StartValue,
u.EndValue,
n.Value as NewValue
FROM
(
SELECT
Value,
SUBSTRING(Value,1,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)-1 ELSE LEN(Value) END) AS StartValue,
SUBSTRING(Value,CASE WHEN CHARINDEX('-',Value) > 0 THEN CHARINDEX('-',Value)+1 ELSE 1 END,LEN(Value)- CHARINDEX('-',Value)) AS EndValue
FROM
(
SELECT
SUBSTRING(#TestData, StartPosition, CASE WHEN EndPosition > 0 THEN EndPosition-StartPosition ELSE LEN(#TestData)-StartPosition+1 END) AS Value
FROM
CTE
)t
)u INNER JOIN ##Numbers n ON n.Value BETWEEN u.StartValue AND u.EndValue
All you would need to do once you have that is query the results using an IN statement, so something like
SELECT * FROM MyTable WHERE Value IN (SELECT NewValue FROM (/*subquery from above*/)t)
I am trying to pass a Table Type into a stored procedure and would like the sproc to look up each row of lat/longs and return to me the nearest point for that row.
Type:
CREATE TYPE dbo.LatLongRoadLinkType AS TABLE
(
Id INT NOT NULL,
Latitude FLOAT NOT NULL,
Longitude FLOAT NOT NULL
);
Stored Proc:
ALTER PROCEDURE [dbo].[BatchNearestRoadNodes]
#Input dbo.LatLongRoadLinkType READONLY
AS
BEGIN
-- do stuff here
-- return a table of id from input, nodeid and distance
END
It needs to do for the whole table what is done here for a single lat/long:
DECLARE #g geography = 'POINT(13.5333414077759 54.549524307251)';
DECLARE #region geography = #g.STBuffer(5000)
SELECT TOP 1 NodeID, Point.STDistance(#g) as 'Distance'
FROM Location
WHERE Point.Filter(#region) = 1
ORDER BY Point.STDistance(#g)
The Location table has the important column Point of type Geography, which is spatially indexed and is what the comparisons are done against.I am sending the table of lat/longs from code into the sproc, and the code is expecting a return of :
Id (original point passed in)
NodeID (of nearest point in location table)
Distance
How should I approach this? To perhaps make it a bit easier I could simply pass in a SqlGeography from my code into the sproc instead of Lat/Long, however that would kill the performance since its very expensive to convert to that.
EDIT:
This works, don't know if its the most optimal solution however.
ALTER PROCEDURE [dbo].[BatchNearestRoadNodes]
#Input dbo.LatLongRoadLinkType READONLY
AS
BEGIN
SELECT x.Id, x.LocationName, x.NodeID, x.Distance
FROM (SELECT I.Id,
L.LocationName,
L.NodeId,
L.Point.STDistance(geography::Point(I.Latitude, I.Longitude, 4326)) AS Distance,
ROW_NUMBER () OVER (PARTITION BY I.Id ORDER BY L.Point.STDistance(geography::Point(I.Latitude, I.Longitude, 4326)) ASC) AS Ranking
FROM #Input AS I
JOIN Location AS L
ON L.Point.STIntersects(geography::Point(I.Latitude, I.Longitude, 4326).STBuffer(5000)) = 1
) AS x WHERE Ranking = 1
END
Performance - V1 vs Jon's Edit
V1
============
original:643 found:627 in:1361 ms
original:1018 found:999 in:1700 ms
original:1801 found:1758 in:2628 ms
original:4098 found:3973 in:5271 ms
original:16388 found:15948 in:19624 ms
Jon's Edit
==========
original:643 found:627 in:1333 ms
original:1018 found:999 in:1689 ms
original:1801 found:1758 in:2559 ms
original:4098 found:3973 in:5114 ms
original:16388 found:15948 in:19054 ms
The difference is minimal. Need to get the last figure down.
Try something like this to get partial results:
WITH PreQuery AS
(
I.Id,
GEOGRAPHY::STPointFromText(I.PointAsWKT).STBuffer(5000) AS Geog,
L.NodeId,
L.Point
FROM
#Input AS I
JOIN
Location AS L ON L.Point.STIntersects(I.Geog) = 1
)
SELECT
P.Id,
P.NodeId,
P.Geog.STDistance(P.Point) AS Distance
FROM
PreQuery P
I've written it from off the head and without any test data so there may be small bugs but in the main it will give you every node and it's distance (within 5000 metres) from every point. You'll still need to filter them to get only the one with the minimum distance for each id - shouldn't be too hard ;-)
Hope it helps somewhat, even if not complete.
EDIT (2nd Dec)
I already see the problem with my first solution, you can't get the distance because it's pre-buffered (to note the main thing). However, this amalgamation should be the most efficient combination of both attempts.
WITH PreQuery AS
(
SELECT
I.Id,
geography::Point(I.Latitude, I.Longitude, 4326) AS InputGeography
FROM
#input AS I
)
SELECT x.Id, x.LocationName, x.NodeId, x.Distance
FROM
(
SELECT
PQ.Id,
L.LocationName,
L.NodeId,
L.Point.STDistance(PQ.InputGeography) AS Distance,
ROWNUMBER() OVER (PARTITION BY I.Id ORDER BY L.Point.Distance(PQ.InputGeography) ASC) AS Ranking
FROM
Prequery AS PQ
JOIN
Location AS L
-- ON L.Point.STIntersects(PQ.InputGeography.STBuffer(5000)) = 1 -- Slower
ON L.Point.STDistance(PQ.InputGeography) <= 5000 -- Faster
) AS X WHERE Ranking = 1
This way, you pre-create the input geography only once, not three times as per your attempt. Again this is untested but should prove the most efficient.
I have a Transact SQL function SimpleSplit, which splits a string according to the delimiter. I can use it as follows:
DECLARE #DelimitedString NVARCHAR(128)
SET #DelimitedString = 'Processor,RAM,SSD,Ethernet'
SELECT * FROM [dbo].[SimpleSplit](#DelimitedString, ',')
This results in:
Processor
RAM
SSD
Ethernet
As expected.
I now have a Table called PROD_TABLE with a column Descr. I would like to apply this function to each value in column Descr. I attempted the following and it does not work:
SELECT p.[ID], p.[Descr]
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.Descr, '-') d
In the output I only see the ID and the Descr Columns i.e. no results from the SimpleSplit function. However, if I attempt
SELECT *
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.Descr, '-') d
I see the results of the SimpleSplit function in the last column. Why does this query apply the function, but the previous query does not?
Answer
Thanks to mr.Rebands answer below, I realized that I needed to name the results. Hence * worked, but to explicitly name the columns I needed to do something like:
SELECT p.[ID], p.[Descr], d.[Data]
FROM [Amazon].[dbo].[PROD_TABLE] p
OUTER APPLY [dbo].[SimpleSplit](p.[Descr], '-') d
Your function returns a table - what is the column name of the SimpleSplit result table? You will have to include that column name in your select statement.
OUTER APPLY is applied but the results are not selected.