SQL 2008 R2 Recursive Query - sql-server

I have the following SQLtable
[rel_id] [int] NOT NULL,
[rel_type_id] [int] NOT NULL,
[left_object_id] [int] NOT NULL,
[left_object_type] [int] NOT NULL,
[right_object_id] [int] NOT NULL,
[right_object_type] [int] NOT NULL,
[template] [char](1) NOT NULL
Basically this table controls relationships between different objects. I’ need to construct some SQL that given left_object_id can work out recursively all the objects that has relationships to it.
I’m only interested in object where both the left & right object types are 5
In simple terms the SQL would need to do the following
1. Input a left object id
2. Search the table and return all right object ids where associated left object matches the input
3. For each of the right object id found go back repeat from step 1
4. Repeat until no further values are found
Sounds complex, but I’m sure there is some SQL guru out there who can solve this.
Many Thanks.

It's difficult without test data but something like this should do the trick:
I assume a tree, having one parent element left side and a children list right side. This query should come back with entries having their parent of type 5 and at least one child of type 5.
SELECT *
FROM #tbl AS tbl
WHERE (SELECT leftObj.rel_type_id FROM #tbl AS leftObj WHERE leftObj.right_object_id=tbl.rel_id)=5
AND EXISTS(SELECT rightObjList.rel_type_id
FROM #tbl AS rightObjList
WHERE rightObjList.left_object_id=tbl.rel_id
AND rightObjList.rel_type_id=5)
<table><tbody><tr><th>rel_id</th><th>rel_type_id</th><th>left_object_id</th><th>left_object_type</th><th>right_object_id</th><th>right_object_type</th></tr><tr><td>52</td><td>44</td><td>4</td><td>5</td><td>1</td><td>5</td></tr><tr><td>53</td><td>44</td><td>4</td><td>5</td><td>3</td><td>5</td></tr><tr><td>54</td><td>44</td><td>5</td><td>5</td><td>4</td><td>5</td></tr><tr><td>55</td><td>44</td><td>5</td><td>5</td><td>6</td><td>5</td></tr></tbody></table>

Related

Points inside polygons - SQL Server T-SQL + geo

I have 2 tables, one containing a bunch of polygons like this
CREATE TABLE [dbo].[GeoPolygons](
[OID] [int] IDENTITY(1,1) NOT NULL,
[Version] [int] NULL,
[entity_id] [varchar](200) NULL,
[Geometry] [geometry] NULL
)
and one containing a bunch of nodes (places) like this
CREATE TABLE [dbo].[GeoPoints](
[point_id] [int] NOT NULL,
[point_name] [varchar](40) NULL,
[latitude] [decimal](8, 6) NULL,
[longitude] [decimal](9, 6) NULL,
[point_geometry] [geography] NULL
)
These tables came from disparate sources, so one has a geography field type and the other has a geometry field type.
What I want to do - which is very possible within my GIS, but I want to do it in T-SQL for a variety of reasons - is find out which nodes are inside which polygons.
Is the first step to match the geo-field types? geometry-to-geometry or geography-to-geography?
Or can that be done on the fly?
My ideal output would be a 3 field table
CREATE TABLE [dbo].[Points2Polygons](
[match_id] [int] IDENTITY(1,1) NOT NULL,
[entity_id] [varchar](200) NOT NULL,
[point_id] [int] NOT NULL
)
and be able to update that on the fly (or perhaps daily) when new records are added to each table.
I found this post but it seems to deal with a single point and a single polygon, as well as WKT definitions. I don't have WKT, and I have thousands of polys and thousands of points. I want to do it at a larger scale.
How do I do it in a T-SQL query?
Server is running SQL-Server Web 64-bit V15.0.4138.2 on Server 2019 Datacenter.
TIA
From the comments above, here's a proposal to convert the Geometry column in your GeoPolygons table. As with anything like this where you have data one way and you want it to look a different way on an ongoing basis, the high level steps are:
Start writing the data in both formats
Convert the old format into the new format
Convert all read paths to the new format
Drop the old format
I'll be focusing on "Convert the old format into the new format". Create a new column in your table (I'll call it Polygon).
alter table [dbo].[GeoPolygons] add
Polygon geography null;
Note that this is a prerequisite for the "Start writing the data in both formats" phase and so should already be done by the time you're ready to convert data.
The most straightforward method to do that looks like this:
update [dbo].[GeoPolygons]
set [Polygon] = geography::STGeomFromText(
[Geometry].STAsText(),
[Geometry].STSrid
)
where [Geometry] is not null
and [Polygon] is null;
I'm making the assumption that the SRID on your Geometry column is set properly. If not, you'll have to find the SRID that is appropriate given the WKT that was used to create t

SQL Pivot with multiple joins

The SQL Pivot command seems difficult at least. I've read a lot about it, and been tinkering with this query for a while, but all I get are really obscure error messages that don't help, like "The column name 'Id' was specified multiple times.." or "The multi-part identifier X could not be bound."
Our database collects client answers to questions. I'd like to create a table which contains a row for each client, and columns for each question (ID) they've answered and the AVG ResponseTime across all times that user has logged in. This is made more difficult as the UserId isn't directly stored in the UserSessionData table, it's stored in the UserSession table, so I have to do a join first, which seems to complicate the issue.
The tables I'm trying to pivot are roughly of the following form:
CREATE TABLE [dbo].[UserSessionData](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserSessionId] [int] NOT NULL,
[UserWasCorrect] [bit] NULL,
[ResponseTime] [float] NULL,
[QuestionId] [int] NULL)
--This table contains user answers to a number of questions.
CREATE TABLE [dbo].[UserSession](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [int] NOT NULL,
[SessionCode] [nvarchar](50) NOT NULL)
--This table contains details of the user's login session.
CREATE TABLE [dbo].[Question](
[Id] [int] IDENTITY(1,1) NOT NULL,
[QuestionText] [nvarchar](max) NOT NULL,
[GameId] [int] NOT NULL,
[Description] [nvarchar](max) NULL)
--This table contains question details
I'll continue trying to mangle a solution, but if anyone can shed any light (or suggest an easier method than PIVOT to achieve the desired result), then that would be great.
Cheers
It's because you've got the same column names in multiple tables so after you've done the join the pivot sees multiple columns all the same name. Have a look at my example below:
SELECT
*
FROM (
SELECT
usd.Id AS usdId
,UserSessionId
,UserWasCorrect
,ResponseTime
,QuestionId
,us.Id AS usId
,SessionCode
,UserId
,Description
,GameId
,qu.Id AS quId
,QuestionText
FROM #UserSessionData usd
LEFT JOIN #UserSession us
ON usd.UserSessionId = us.Id
LEFT JOIN #Question qu
ON usd.QuestionId = qu.Id
) AS tbl PIVOT (
-- As no example data was provided 'quest' & 'voyage are just random values I put in. Change the pivot statement to match what you want
MIN(ResponseTime) FOR SessionCode IN (quest, voyage)
) AS pvt
'quest' and 'voyage' are example data of the rows contents in the Column SessionCode. This will need to be changed to your columns contents. In PIVOTs and UNPIVOTs you cannot use a query to get these values and they have to be statically put in. You could use dynamic SQL to generate the values however this is usually heavily advised against

SQL Server - Order Identity Fields in Table

I have a table with this structure:
CREATE TABLE [dbo].[cl](
[ID] [int] IDENTITY(1,1) NOT NULL,
[NIF] [numeric](9, 0) NOT NULL,
[Name] [varchar](80) NOT NULL,
[Address] [varchar](100) NULL,
[City] [varchar](40) NULL,
[State] [varchar](30) NULL,
[Country] [varchar](25) NULL,
Primary Key([ID],[NIF])
);
Imagine that this table has 3 records. Record 1, 2, 3...
When ever I delete Record number 2 the IDENTITY Field generates a Gap. The table then has Record 1 and Record 3. Its not correct!
Even if I use:
DBCC CHECKIDENT('cl', RESEED, 0)
It does not solve my problem becuase it will set the ID of the next inserted record to 1. And that's not correct either because the table will then have a multiple ID.
Does anyone has a clue about this?
No database is going to reseed or recalculate an auto-incremented field/identity to use values in between ids as in your example. This is impractical on many levels, but some examples may be:
Integrity - since a re-used id could mean records in other systems are referring to an old value when the new value is saved
Performance - trying to find the lowest gap for each value inserted
In MySQL, this is not really happening either (at least in InnoDB or MyISAM - are you using something different?). In InnoDB, the behavior is identical to SQL Server where the counter is managed outside of the table, so deleted values or rolled back transactions leave gaps between last value and next insert. In MyISAM, the value is calculated at time of insertion instead of managed through an external counter. This calculation is what is giving the perception of being recalcated - it's just never calculated until actually needed (MAX(Id) + 1). Even this won't insert inside gaps (like the id = 2 in your example).
Many people will argue if you need to use these gaps, then there is something that could be improved in your data model. You shouldn't ever need to worry about these gaps.
If you insist on using those gaps, your fastest method would be to log deletes in a separate table, then use an INSTEAD OF INSERT trigger to perform the inserts with your intended keys by first looking for records in these deletions table to re-use (then deleting them to prevent re-use) and then using the MAX(Id) + 1 for any additional rows to insert.
I guess what you want is something like this:
create table dbo.cl
(
SurrogateKey int identity(1, 1)
primary key
not null,
ID int not null,
NIF numeric(9, 0) not null,
Name varchar(80) not null,
Address varchar(100) null,
City varchar(40) null,
State varchar(30) null,
Country varchar(25) null,
unique (ID, NIF)
)
go
I added a surrogate key so you'll have the best of both worlds. Now you just need a trigger on the table to "adjust" the ID whenever some prior ID gets deleted:
create trigger tr_on_cl_for_auto_increment on dbo.cl
after delete, update
as
begin
update dbo.cl
set ID = d.New_ID
from dbo.cl as c
inner join (
select c2.SurrogateKey,
row_number() over (order by c2.SurrogateKey asc) as New_ID
from dbo.cl as c2
) as d
on c.SurrogateKey = d.SurrogateKey
end
go
Of course this solution also implies that you'll have to ensure (whenever you insert a new record) that you check for yourself which ID to insert next.

Composite increment column

I have a situation where I need to have a secondary column be incremented by 1, assuming the value of another is the same.
Table schema:
CREATE TABLE [APP].[World]
(
[UID] [uniqueidentifier] ROWGUIDCOL NOT NULL,
[App_ID] [bigint] NOT NULL,
[id] [bigint] NOT NULL,
[name] [varchar](255) NOT NULL,
[descript] [varchar](max) NULL,
[default_tile] [uniqueidentifier] NOT NULL,
[active] [bit] NOT NULL,
[inactive_date] [datetime] NULL
)
First off, I have UID which is wholly unique, no matter what App_ID is.
In my situation, I would like to have id be similar to Increment(1,1), only for the same App_ID.
Assumptions:
There are 3 App_Id: 1, 2, 3
Scenario:
App_ID 1 has 3 worlds
App_ID 2 has 5 worlds
App_ID 3 has 1 world
Ideal outcome:
App_ID id
1 1
2 1
3 1
1 2
2 2
1 3
2 3
2 4
2 5
Was thinking of placing the increment logic in the Insert stored procedure but wanted to see if there would be an easier or different way of producing the same result without a stored procedure.
Figure the available option(s) are triggers or stored procedure implementation but wanted to make sure there wasn't some edge-case pattern I am missing.
Update #1
Lets rethink this a little.
This is about there being a PK UID and ultimately a Partitioned Column id, over App_ID, that is incremented by 1 with each new entry for the associated App_id.
This would be similar to how you would do Row_Number() but without all the overhead of recalculating the value each time a new entry is inserted.
As well App_ID and id both have the space and potential for being BIGINT; therefore the combination number of possible combinations would be: BIGINT x BIGINT
This is not possible to implement the way you are asking for. As others have pointed out in comments to your original post, your database design would be a lot better of split up in multiple tables, which all have their own identities and utilizes foreign key constraint where necessary.
However, if you are dead set on proceeding with this approach, I would make app_id an identity column and then increment the id column by first querying it for
MAX(identity)
and then increment the response by 1. This kind of logic is suitable to implement in a stored procedure, which you should implement for inserts anyway to prevent from direct sql injections and such. The query part of such a procedure could look like this:
INSERT INTO
[db].dbo.[yourtable]
SET
(
app_id
, id
)
VALUES
(
#app_id
, (
SELECT
MAX(id)
FROM
[db].dbo.[table]
WHERE
App_id = #app_id
)
)
The performance impact for doing so however, is up to you to assess.
Also, you need to consider how to properly handle when there is no previous rows for that app_id.
Simplest Solution will be as below :
/* Adding Leading 0 to [App_ID] */
[SELECT RIGHT(CONCAT('0000', (([App_ID] - (([App_ID] - 1) % 3)) / 3) + 1), 4) AS [App_ID]
I did the similar thing in my recent code, please find the below image.
Hope the below example will help you.
Explanation part - In the below Code, used the MAX(Primary_Key Identity column) and handled first entry case with the help of ISNULL(NULL,1). In All other cases, it will add up 1 and gives unique value. Based on requirements and needs, we can made changes and use the below example code. WHILE Loop is just added to show demo(Not needed actually).
IF OBJECT_ID('dbo.Sample','U') IS NOT NULL
DROP TABLE dbo.Sample
CREATE TABLE [dbo].[Sample](
[Sample_key] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
[Student_Key] [int] UNIQUE NOT NULL,
[Notes] [varchar](100) NULL,
[Inserted_dte] [datetime] NOT NULL
)
DECLARE #A INT,#N INT
SET #A=1
SET #N=10
WHILE(#A<=#N)
BEGIN
INSERT INTO [dbo].[Sample]([Student_Key],[Notes],[Inserted_dte])
SELECT ISNULL((MAX([Student_Key])+1),1),'NOTES',GETDATE() FROM [dbo].[Sample]
SET #A+=1
END
SELECT * FROM [dbo].[Sample]

Update DateUsed by trigger

I'm trying to update a table with a trigger from another table. I thought this would be a very simple query but the query I first came up with does not work and I don't understand why.
CREATE TABLE [dbo].[Vehicle](
[id] [int] IDENTITY(1,1) NOT NULL,
[plate] [nvarchar](50) NOT NULL,
[name] [nvarchar](50) NOT NULL,
[dateUsed] [datetime] NULL
)
CREATE TABLE [dbo].[Transaction](
[id] [int] IDENTITY(1,1) NOT NULL,
[vehicleId] [int] NOT NULL,
[quantity] [float] NOT NULL,
[dateTransaction] [datetime] NOT NULL,
)
When a transaction is added, I wish to update the Vehicle table. If the added dateTransaction is later then dateUsed it should be updated so the dateUsed field always contains the latest date of that specific vehicle.
I would think that this trigger should do the trick.. but it does not:
UPDATE [Vehicle]
SET [dateUsed] =
CASE
WHEN [dateUsed] < [Transaction].[dateTransaction]
OR [dateUsed] IS NULL
THEN [Transaction].[dateTransaction]
ELSE [dateUsed]
END
FROM [Transaction]
WHERE [Vehicle].[id]=[Transaction].[vehicleId]
It looks good to me... It should go over all newly inserted records and update the dateUsed field. If the dateTransaction is newer, use that one.. if not.. use the current. But I seem to missing something because it's not updating to the latest date. It does match one of the transactions of that specific vehicle but not the latest one.
A query that does work:
UPDATE [Vehicle]
SET [dateUsed] = InsertedPartitioned.[dateTransaction]
FROM [Vehicle]
LEFT JOIN (
SELECT
[vehicleId],
[dateTransaction],
ROW_NUMBER() OVER(PARTITION BY [VehicleId] ORDER BY [dateTransaction] DESC) AS RC
FROM [Inserted]) AS InsertedPartitioned
ON InsertedPartitioned.RC=1
AND InsertedPartitioned.[vehicleId]=[Vehicle].[id]
WHERE InsertedPartitioned.[vehicleId] IS NOT NULL
AND ([Vehicle].[dateUsed] IS NULL
OR InsertedPartitioned.[dateTransaction] > [Vehicle].[dateUsed]);
So I have a working solution and it may even be for the better (haven't timed it with a large insert) but it bugs the hell out of my not knowing why the first it not working!
Can anyone 'enlighten me'?
why the first it not working
Because of a wonderful aspect of the Microsoft extension to UPDATE that uses a FROM clause:
Use caution when specifying the FROM clause to provide the criteria for the update operation. The results of an UPDATE statement are undefined if the statement includes a FROM clause that is not specified in such a way that only one value is available for each column occurrence that is updated, that is if the UPDATE statement is not deterministic.
(My emphasis).
That is, if more than one row from inserted matches the same row in Vehicle then it's undefined which row will be used to apply the update - and all computations within the SET clause are computed "as if" they're all evaluated in parallel - so it's not as if a second attempt to update the same row will observe the results on the first attempt - the current value of the DateUsed column that can be observed is always the original value.
In ANSI standard SQL, you'd have to write the UPDATE without using the FROM extension and would thus have to write a correlated subquery, something like:
UPDATE [Vehicle]
SET [dateUsed] = COALESCE((SELECT dateUsed FROM inserted i
WHERE i.VehicleId = Vehicle.Id and
(i.dateUsed > Vehicle.DateUsed or
Vehicle.DateUsed IS NULL),
dateUsed)
WHERE [id] IN (select [vehicleId] FROM inserted)
Which, under the same circumstances, would nicely give you an error about a subquery returning more than one value (for the one inside the COALESCE, not the one in the IN) and thus give you a clue to why it's not working.
But, undeniably, the FROM extension is useful - I just wish it triggered a warning for this kind of situation.

Resources