Spatial Query: Polygon Attribute to be added to point data - sql-server

I am trying to get to grips with a spatial query. I have a table of polygons, that are Zones within my county. there are 38 of them. I then have premise data. I would like to run a query to show all the premises that fall within/intersect with these polygons.
My tables are as follows
AB_BLPU (geometry address point data from Ordnance Survey)
AB_Classification (Ordnance Surveys data)
AB_Class_Schema (a lookup table for the classification table)
Zones (this table is made up of 38 polygons, these are labelled by the Division they belong to e.g., East, North etc
I would like my results to be a list of premises with the Zone and Division they belong to but only giving me Residential premises i.e. "R".
I have tried this SQL code:
SELECT
B.UPRN, B.X_COORDINATE, B.Y_COORDINATE, B.GEOMETRY, SA.Division, SA.Zone_Name
FROM dbo.AB_BLPU AS B
LEFT OUTER JOIN dbo.AB_CLASSIFICATION AS C ON B.UPRN = C.UPRN
LEFT OUTER JOIN dbo.AB_CLASS_SCHEMA AS S ON C.CLASSIFICATION_CODE = S.Concatenated
LEFT OUTER JOIN GEO_DATA.Zones AS SA ON B.GEOMETRY.STIntersects(SA.GEOMETRY) = 1
WHERE (C.CLASS_SCHEME LIKE 'Address%') AND (S.Primary_Code LIKE 'R')
But I get the following error:
Msg 6522, Level 16, State 1, Line 2
A .NET Framework error occurred during execution of user-defined routine or aggregate "geometry":
System.ArgumentException: 24144: This operation cannot be completed because the instance is not valid. Use MakeValid to convert the instance to a valid instance. Note that MakeValid may cause the points of a geometry instance to shift slightly.
System.ArgumentException:
Yet when I run the same SQL code but with the Division polygon table (which only has three polygons in it, it works and I get 350,000 ish results. See below:
SELECT
B.UPRN, A.DPADDRESS, B.X_COORDINATE, B.Y_COORDINATE, A.POST_TOWN, A.POSTCODE, B.GEOMETRY, O.ID
FROM dbo.AB_BLPU AS B
LEFT OUTER JOIN dbo.AB_CLASSIFICATION AS C ON B.UPRN = C.UPRN
INNER JOIN dbo.AB_DPADDRESS AS A ON B.UPRN = A.UPRN
LEFT OUTER JOIN dbo.AB_CLASS_SCHEMA AS S ON C.CLASSIFICATION_CODE = S.Concatenated
LEFT OUTER JOIN GEO_DATA.Division AS O ON B.GEOMETRY.STIntersects(O.GEOMETRY) = 1
WHERE (C.CLASS_SCHEME LIKE 'Address%') AND (S.Primary_Code LIKE 'R')
and O.ID is not null
order by b.UPRN asc
Can anyone suggest how to fix the issue? Why is it working for one polygon table but not one with 38 polygons?
Thanks

Related

Issue when joining 3 temp tables

I have an issue joining 3 temp tables.
I am going to write the whole code but the thing that doesn't work is a join when selecting all three temp tables. Everything else works just fine (type just in case if someone wants to see the whole picture).
I need to join all three tables on dates to make sure I select the same inventory during the same time period. Whenever I join those three tables I either get Forecast or Actuals right, but never both.
When I type this I get Forecast correct, but Actuals incorrect a.[DMDPostDate]=u.[STARTDATE] and f.[STARTDATE]=a.[DMDPostDate] (Forecast correct 6998.649, Actuals are not correct 826)
-- AND u.[STARTDATE]=f.[STARTDATE] and f.[STARTDATE]=u.[STARTDATE] (Actuals are correct 10369, Forecast not correct 8322.315)
-- and a.[DMDPostDate]=f.[STARTDATE] (Forecast correct 6998.649, Actuals not correct)
-- AND u.[STARTDATE]=a.[DMDPostDate] (Forecast correct 6998.649, Actuals not correct)
-- AND u.[STARTDATE]=f.[STARTDATE] (Actuals are correct 10369, Forecast not correct)
-- and u.[STARTDATE]=f.[STARTDATE] and u.[STARTDATE] = a.[DMDPostDate] (Forecast correct 6998.649, Actuals not correct)
From your post - it seems fairly clear that joining STARTDATEs gets you the correct actuals, and joining DMDPostDate to either start date gets the right forecast.
Something thing to consider - u.[STARTDATE]=f.[STARTDATE] - this clause should have no impact to the A table join, what if you added this to the F table ON clause, and then u.[STARTDATE] = a.[DMDPostDate] to the A table clause
The way it's working now, you're left joining everything from table F to everything it can join to in table A based on the U.[UPC]=F.[DMDUNIT] AND U.[MASTERCHAINNAME]=F.[LOC], without any dates. It could be a challenge with the way it's evaluating the clauses as a result.
Dark horse answer - you don't need to join on any dates - actuals are correct when you aren't joining dates for the A table - when you use AND u.[STARTDATE]=f.[STARTDATE] it may be applying to the U/F join which could lower the forecast number.
You could also troubleshoot by joining the F and A tables separately to the U table to see if you're getting the expected values then.
Best guess -
SELECT
U.[UPC] AS 'Item',
U.[MASTERCHAINNAME] AS 'Chain',
U.[STARTDATE] AS 'Start Date',
U.[EVENT_TYPE] ,
U.[EVENT_NAME],
SUM(F.Forecast) AS 'Forecast',
SUM(A.HistoryQuantity) AS 'Actuals'
FROM
UDT_CKB_SNAPSHOT U
LEFT OUTER JOIN
FCSTPERFSTATIC F ON U.[UPC] = F.[DMDUNIT]
AND U.[MASTERCHAINNAME] = F.[LOC]
AND f.[STARTDATE] = u.[STARTDATE]
LEFT OUTER JOIN
HISTWIDE_CHAIN A ON U.[UPC] = a.[DMDUNIT]
AND U.[MASTERCHAINNAME] = a.[LOC]
AND a.[DMDPostDate] = u.[STARTDATE]
GROUP BY
U.[UPC], U.[MASTERCHAINNAME], U.[STARTDATE], U.[EVENT_TYPE] , U.[EVENT_NAME]

Calculate Nearest Neighbor in SQL Server

I have 2 datasets, Fire Hydrants (3381 records) and Street Centerlines (6636 records). I would like to identify the Street nearest to each Hydrant record.
Following some SQL Server resources and tutorials I put together a script, but it has been running for over an hour now. I understand nearest neighbor may take awhile to run but it seems like something might be wrong in the logic.
select
h.OBJECTID,
st.FULL_ST_NAME
from WATER_HYDRANTS as h LEFT OUTER JOIN
STREET_CENTERLINES as st
on st.Shape.STDistance(h.SHAPE) is NOT NULL
ORDER BY st.Shape.STDistance(h.SHAPE) ASC
The reason I think something is wrong in the logic is because when I add a WHERE clause to select only one record with an ID, the query returns a list of the entire dataset. In the ObjectID column it is all the same value (e.g., 13992) and in the FULL_ST_NAME column is (I assume) a list of every street in ordered by proximity to the feature.
select
h.OBJECTID,
st.FULL_ST_NAME
from WATER_HYDRANTS as h LEFT OUTER JOIN
STREET_CENTERLINES as st
on st.Shape.STDistance(h.SHAPE) is NOT NULL
where h.OBJECTID = '13992'
ORDER BY st.Shape.STDistance(h.SHAPE) ASC
Ideally, each record in the objectID column will be unique and the FULL_ST_NAME column will have the street that is closest to each hydrant.
Please let me know if I can provide any other information. I tried to be thorough in my explanation and made an attempt at due diligence and research before coming to SO.
Thanks
Instead of LEFT OUTER JOIN to StreetCenterLines you need CROSS APPLY.
The TOP 1 with ORDER BY STDistance inside the CROSS APPLY gives you the nearest street for each Hydrant. (Your original query was giving ALL the streets for each hydrant.)
Easier to show than to explain; it's like this:
select
h.OBJECTID,
st2.FULL_ST_NAME
from WATER_HYDRANTS as h
CROSS APPLY (SELECT TOP 1 st.FULL_ST_NAME
FROM STREET_CENTERLINES as st
WHERE st.Shape.STDistance(h.SHAPE) IS NOT NULL
ORDER BY st.Shape.STDistance(h.SHAPE) ASC) as st2
It might take a long time to run though, since for every hydrant it has to calculate the distance to every street and then find the shortest one.

How do i merge multiple views in SQL with one observation per record?

I have three views that i need to merge so i can only have one record per beneficiary ID. See the views below:
![Views][1]
Farmers View
(Service_Farmer,Beneficiary_ID,Household_ID,FirstName,LastName,FullName,Sex
,Status)
Lead Farmers View
(Service_Lead_Farmer,Household_ID,Beneficiary_ID,FirstName,LastName,Sex,Status)
SILC Members View
(Service_SILC,Beneficiary_ID,Household_ID,FirstName,LastName,Sex)
How can i write a SQL Server code to merge these views so i can tell if someone is a farmer, lead farmer or silc member,etc?
Assuming you have another table, Beneficiaries perhaps, something like the following:
select b.Beneficiary_ID, f.Service_Farmer, lf.Service_Lead_Farmer, sm.Service_SILC, other_columns_as_needed
from Beneficiaries b
left join Farmers f on b.Beneficiary_ID = f.BeneficiaryID
left join LeadFarmers lf on b.Beneficiary_ID = lf.Beneficiary_ID
left join SILCMembers sm on b.Beneficiary_ID = sm.Beneficiary_ID
where whatever_conditions_are_relevant

How can I convert a view containing a START WITH...CONNECT BY sub-query to SQL Server?

I am trying to convert a view from an Oracle RDBMS to SQL Server. The view looks like:
create or replace view user_part_v
as
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id)
Having read about recursive CTE's and also about their use in sub-queries, my best guess at translating the above into SQL Server syntax is:
create view user_part_v
as
with region_structure(region_id, parent_region_id) as (
select region_id
, parent_region_id
from region_relation
where parent_region_id = users.region_id
union all
select r.region_id
, r.parent_region_id
from region_relation r
join region_structure rs on rs.parent_region_id = r.region_id
)
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(select region_id from region_structure)
Obviously this gives me an error about the reference to users.region_id in the CTE definition.
How can I achieve the same result in SQL Server as I get from the Oracle view?
Background
I am working on the conversion of a system from running on an Oracle 11g RDMS to SQL Server 2008. This system is a relatively large Java EE based system, using JPA (Hibernate) to query from the database.
Many of the queries use the above mentioned view to restrict the results returned to those appropriate for the current user. If I cannot convert the view directly then the conversion will be much harder as I will need to change all of the places where we query the database to achieve the same result.
The tables referenced by this view have a structure similar to:
USERS
ID
REGION_ID
REGION
ID
NAME
REGION_RELATIONSHIP
PARENT_REGION_ID
REGION_ID
PART
ID
PARTNO
DESCRIPTION
PART_REGION
PART_ID
REGION_ID
So, we have regions, arranged into a hierarchy. A user may be assigned to a region. A part may be assigned to many regions. A user may only see the parts assigned to their region. The regions reference various geographic regions:
World
Europe
Germany
France
...
North America
Canada
USA
New York
...
If a part, #123, is assigned to the region USA, and the user is assigned to the region New York, then the user should be able to see that part.
UPDATE: I was able to work around the error by creating a separate view that contained the necessary data, and then have my main view join to this view. This has the system working, but I have not yet done thorough correctness or performance testing yet. I am still open to suggestions for better solutions.
I reformatted your original query to make it easier for me to read.
create or replace view user_part_v
as
select part_region.part_id, users.id as users_id
from part_region, users
where part_region.region_id in(
select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id
);
Let's examine what's going on in this query.
select part_region.part_id, users.id as users_id
from part_region, users
This is an old-style join where the tables are cartesian joined and then the results are reduced by the subsequent where clause(s).
where part_region.region_id in(
select region_id
from region_relation
start with region_id = users.region_id
connect by parent_region_id = prior region_id
);
The sub-query that's using the connect by statement is using the region_id from the users table in outer query to define the starting point for the recursion.
Then the in clause checks to see if the region_id for the part_region is found in the results of the recursive query.
This recursion follows the parent-child linkages given in the region_relation table.
So the combination of doing an in clause with a sub-query that references the parent and the old-style join means that you have to consider what the query is meant to accomplish and approach it from that direction (rather than just a tweaked re-arrangement of the old query) to be able to translate it into a single recursive CTE.
This query also will return multiple rows if the part is assigned to multiple regions along the same branch of the region heirarchy. e.g. if the part is assigned to both North America and USA a user assigned to New York will get two rows returned for their users_id with the same part_id number.
Given the Oracle view and the background you gave of what the view is supposed to do, I think what you're looking for is something more like this:
create view user_part_v
as
with user_regions(users_id, region_id, parent_region_id) as (
select u.users_id, u.region_id, rr.parent_region_id
from users u
left join region_relation rr on u.region_id = rr.region_id
union all
select ur.users_id, rr.region_id, rr.parent_region_id
from user_regions ur
inner join region_relation rr on ur.parent_region_id = rr.region_id
)
select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
Note that I've added the users_id to the output of the recursive CTE, and then just done a simple inner join of the part_region table and the CTE results.
Let me break down the query for you.
select u.users_id, u.region_id, rr.parent_region_id
from users u
left join region_relation rr on u.region_id = rr.region_id
This is the starting set for our recursion. We're taking the region_relation table and joining it against the users table, to get the starting point for the recursion for every user. That starting point being the region the user is assigned to along with the parent_region_id for that region. A left join is done here and the region_id is pulled from the user table in case the user is assigned to a top-most region (which means there won't be an entry in the region_relation table for that region).
select ur.users_id, rr.region_id, rr.parent_region_id
from user_regions ur
inner join region_relation rr on ur.parent_region_id = rr.region_id
This is the recursive part of the CTE. We take the existing results for each user, then add rows for each user for the parent regions of the existing set. This recursion happens until we run out of parents. (i.e. we hit rows that have no entries for their region_id in the region_relationship table.)
select pr.part_id, ur.users_id
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
This is the part where we grab our final result set. Assuming (as I do from your description) that each region has only one parent (which would mean that there's only one row in region_relationship for each region_id), a simple join will return all the users that should be able to view the part based on the part's region_id. This is because there is exactly one row returned per user for the user's assigned region, and one row per user for each parent region up to the heirarchy root.
NOTE:
Both the original query and this one do have a limitation that I want to make sure you are aware of. If the part is assigned to a region that is lower in the heirarchy than the user (i.e. a region that is a descendent of the user's region like the part being assigned to New York and the user to USA instead of the other way around), the user won't see that part. The part has to be assigned to either the user's assigned region, or one higher in the region heirarchy.
Another thing is that this query still exhibits the case I mentioned above about the original query, where if a part is assigned to multiple regions along the same branch of the heirarchy that multiple rows will be returned for the same combination of users_id and part_id. I did this because I wasn't sure if you wanted that behavior changed or not.
If this is actually an issue and you want to eliminate the duplicates, then you can replace the query below the CTE with this one:
select p.part_id, u.users_id
from part p
cross join users u
where exists (
select 1
from part_region pr
inner join user_regions ur on pr.region_id = ur.region_id;
where pr.part_id = p.part_id
and ur.users_id = u.users_id
);
This does a cartesian join between the part table and the users table and then only returns rows where the combination of the two has at least one row in the results of the subquery, which are the results that we are trying to de-duplicate.

Tough Sql Server Join Query

I am building an application for my semestre exam which will take place in 20 days. My app should assist teachers to build more easily our faculty timetable.
I am working with a database approach (Sql Server 2008 and Delphi XE2). I have a few tables that describe the student formations structure. So I have Years, Series, Specializations, Groups and SemiGroups. Like in the image Years contains Series, Series contain Specializations, Specializations contains Groups and Groups may contain or not SemiGroups. I have also tables with Courses, Teachers, ClassRooms, Days and HourlyIntervals.
There are a few conditions:
A teacher may take a course with one or more Specializations, OR with one or more Groups, OR with one or more Semigroups.
The second condition is that Courses are of 3 types: TeachingCourse, Seminary, Laboratory (only two from three possible for each CourseName).(stored in the column Scheduler.CourseType char(3) )
The third one: Courses can be kept in all weeks of a semester, or in oddweek numbers,or in weeks number dividable by 2.(stored in the column Scheduler.Week char(3))
So I am storing the correlations in a SchedulerTable.
So if a Group has a course with a certain teacher I will introduce only the corresponding IDs.
I built almost all the data introducing forms and now I am at the Reporting part of the application. I am using Report Services from MSSQL 2008.
I want to list a scheduler that will include all the correlations for a certain Specialization (that one includes Groups and/or Semigroups). I have managed to show all correlations for Groups belonging to that certain Specializations but I can't manage to show alse the Specialization and Semigroups courses.
This is the query that returns me Groups correlations from a certain Specialization.
SELECT Days.DayName, HourlyIntervals.HourlyIntervalName, Scheduler.Week, Scheduler.CourseType, Courses.CourseName, ClassRooms.ClassRoomName, Teachers.TeacherName,Specializations.SpecName, Groups.GroupsName
FROM Scheduler INNER JOIN
Groups ON Scheduler.GroupID = Groups.GroupID INNER JOIN
Days ON Scheduler.DayID = Days.DayID INNER JOIN
HourlyIntervals ON Scheduler.HourlyIntervalID = HourlyIntervals.HourlyIntervalID INNER JOIN
Teachers ON Scheduler.TeacherID = Teachers.TeacherID INNER JOIN
Courses ON Scheduler.CourseID = Courses.CourseID INNER JOIN
ClassRooms ON Scheduler.ClassRoomID = ClassRooms.ClassRoomID INNER JOIN
Specializations ON Groups.IDSpec = Specializations.IDSpec
WHERE (Specializations.ID = #SpecID)
ORDER BY Days.DayID, HourlyIntervals.HourlyIntervalID
But I want it to return corelations for SemiGroups,Groups and Specialization for that certain Specialization. I tried to add a join with this one Scheduler.SemiGroupID=Semigroups.SemigroupID but the query returns 0 results then. I don't know if it can be done what I want but I will be thankful to anyone that points me an idea. Or should I use another structure for my correlations table (SchedulerTable).
This is a sample report (a PDF file) which I would like to get:
Edit Reason: Better explaining of the issue
So why isn't this the solution?
SELECT Days.DayName, HourlyIntervals.HourlyIntervalName, Scheduler.Week, Scheduler.CourseType, Courses.CourseName, ClassRooms.ClassRoomName, Teachers.TeacherName,Specializations.SpecName, SemiGroups.GroupsName
FROM Scheduler
INNER JOIN SemiGroups On Scheduler.SemiGroupId = SemiGroups.SemiGroupId
INNER JOIN Groups ON SemiGroups.GroupID = SemiGroups.Groups
INNER JOIN Days ON Scheduler.DayID = Days.DayID
INNER JOIN HourlyIntervals ON Scheduler.HourlyIntervalID = HourlyIntervals.HourlyIntervalID
INNER JOIN Teachers ON Scheduler.TeacherID = Teachers.TeacherID
INNER JOIN Courses ON Scheduler.CourseID = Courses.CourseID
INNER JOIN ClassRooms ON Scheduler.ClassRoomID = ClassRooms.ClassRoomID
INNER JOIN Specializations ON Groups.IDSpec = Specializations.IDSpec
WHERE (Specializations.ID = #SpecID)
ORDER BY Days.DayID, HourlyIntervals.HourlyIntervalID
Though I'll admit to a furrowed brow about whether GroupID in Schedules was the GroupID of the SemiGroupID in that row. Looks likes that bit isn't normalised.

Resources