I have some tree-like data that is implemented using hierarchyid. Some nodes in that tree can be "locked" and when that happens, all the subnodes of the locked nodes become locked too.
Whether the node is locked or not is calculated with a relatively expensive join, but I need that data available everywhere I deal with my tree in my app. So ideally I was thinking of creating an indexed (aka materialised) view that has the list of all the locked nodes and subnodes. Unfortunately I can only come up with the query that joins the Tree table twice, which isn't allowed for indexed views. Here's my pseudocode:
with Tree as (
-- this is my real Tree table
select 1 Id, cast('/1/' as hierarchyid) Ord
union all
select 2, '/1/1/'
union all
select 3, '/1/2/'
union all
select 4, '/1/2/1/'
union all
select 5, '/1/2/1/1/'
union all
select 6, '/2/'
union all
select 7, '/3/'
union all
select 8, '/3/5/'
), Locked as (
-- this is the list of locked nodes (in reality obtained by joining several tables together)
select 3 Id
union all
select 7
)
-- this is what I would like to capture in an indexed view
select t2.Id
from Tree t
join Locked ld on t.Id = ld.Id
join Tree t2 on t2.Ord.IsDescendantOf(t.Ord) = 1
This produces a result set that I could have left join-ed to my Tree table to check if each row is "locked" or not, but unfortunately I cannot make it into an indexed view because of the "self join" on Tree.
Is there a better way of doing something like this?
PS: Should I ask this on DBA SE instead?
Related
Our County recently purchased a new real-estate system. I have created a view that joins over a dozen tables. (assessment, taxpayer, collections, etc...)
Every table has a last change data and last change user field.
We would like to know when a parcel number was last changed in the system and what login made those changes.
I was able to pull the most recent change data by using the following nested query...
SELECT
... ,
(SELECT MAX(v)
FROM (VALUES (table1.last_chg_datetime), (table2.last_chg_datetime),
(table3.last_chg_datetime), (table4.last_chg_datetime), ... ,
(tableN.last_chg_datetime)) AS value(v)) AS last_chg_datetime
FROM table1
LEFT OUTER JOIN table2 ...
What is the best way to also pull the last changed user from the table that corresponds with the last changed date?
I am sure this could be done with a stored procedure. I was just wondering if this could also be extracted by altering the nested query?
We are running SQL Server 2016.
The query could be rewritten as:
SELECT ... ,sub. *
FROM table1
LEFT OUTER JOIN table2 ...
OUTER APPLY (SELECT TOP 1 *
FROM (VALUES (table1.last_chg_datetime, table1.user), (table2.last_chg_datetime, table2.user),
(table3.last_chg_datetime, table3.user), (table4.last_chg_datetime, table4.user), ... ,
(tableN.last_chg_datetime, tableN.user)) AS value(v, user_name)
ORDER BY v DESC
) sub;
It could be also extended by table name:
(VALUES ('table1', table1.last_chg_datetime, table1.user),
('table2', table2.last_chg_datetime, table2.user), ...
) AS value(table_name, v, user_name)
I have a tricky bit of sql query I need to write. To best explain it, I will post some pictures to show three tables. The first two are tables which already contain data, the last table will be the table I need created using data from the first two:
You can use JOIN for each column you want to get in final table:
SELECT
Width.itemNumber,
Width.itemValue as 'Width',
Height.itemValue as 'Height',
[Type].valueID as 'Type',
Frame.valueID as 'Frame',
Position.valueID as 'Position'
INTO third_table_name
FROM itemMaster_itemValue Width
JOIN itemMaster_itemValue Height ON Width.itemNumber=Height.itemNumber AND Height.itemPropertyID='Height'
JOIN itemMaster_EnumValue 'Type' ON Width.itemNumber=[Type].itemNumber AND [Type].itemPropertyID='Type'
JOIN itemMaster_EnumValue Frame ON Width.itemNumber=Frame.itemNumber AND Frame.itemPropertyID='Frame'
JOIN itemMaster_EnumValue Position ON Width.itemNumber=Position.itemNumber AND Position.itemPropertyID='Position'
WHERE Width.itemPropertyID='Width'
I'm not sure if you actually are wanting to create a table for the third view or just a query (in access) / view (in MS SQL Server). Here is how I would do it:
In MS-Access:
Step 1 (Which can end here if all you need is a way to see the data in this format)
TRANSFORM Max(P.vid) AS MaxOfvid
SELECT P.inum
FROM (SELECT itemNumber as inum, itemPropertyID as ival, itemValue as vid
FROM itemMaster_itemValue
UNION
SELECT Enum.itemNumber AS inum, Enum.itemPropertyID AS ival, Enum.valueID AS vid
FROM itemMaster_EnumValue AS Enum) AS P
GROUP BY P.inum
PIVOT P.ival;
Step 2 (If you need to actually create an additional table)
Select * INTO tableName FROM previousPivotQueryName;
That will get you what you need in Access.
The SQL Server part is a little different and can all be done in one T-SQL Statement dbo.test is the name of the table you will create... If you are creating a table for performance reasons then this statement can be put into a job and run nightly to create the table. The Drop Table line will have to be removed before the first time you run it or it will fail because the table will not exist yet:
Drop Table dbo.Test
Select * INTO dbo.Test FROM (
/*just use the following part if you only need a view*/
SELECT *
FROM (SELECT itemNumber as inum, itemPropertyID as ival, itemValue as vid
FROM dbo.itemMaster_itemValue
UNION
SELECT Enum.itemNumber AS inum, Enum.itemPropertyID AS ival, Enum.valueID AS vid
FROM dbo.itemMaster_EnumValue AS Enum) P
PIVOT (max(vid) FOR ival IN([Width],[Height],[Type],[Frame],[Position])) PV)PVT;
And that should get you what you need in the most efficient way possible without using a bunch of joins. :)
I have a table Person that has 3 columns: Id, Name, ParentId where ParentId is the Id of the parent row.
Currently, to display the entire tree, it would have to loop through all child elements until there's no more child elements. It doesn't seem too efficient.
Is there a better and more efficient way to query this data?
Also, is there a better way to represent this tree like structure in a SQL Server database? An alternative design for my table/database?
I don't think there's anything wrong with the design, assuming you have a limited level of parent-child relationships. Here is a quick example of retrieving the relationship using a recursive CTE:
USE tempdb;
GO
CREATE TABLE dbo.tree
(
ID INT PRIMARY KEY,
name VARCHAR(32),
ParentID INT FOREIGN KEY REFERENCES dbo.tree(ID)
);
INSERT dbo.tree SELECT 1, 'grandpa', NULL
UNION ALL SELECT 2, 'dad', 1
UNION ALL SELECT 3, 'me', 2
UNION ALL SELECT 4, 'mom', 1
UNION ALL SELECT 5, 'grandma', NULL;
;WITH x AS
(
-- anchor:
SELECT ID, name, ParentID, [level] = 0
FROM dbo.tree WHERE ParentID IS NULL
UNION ALL
-- recursive:
SELECT t.ID, t.name, t.ParentID, [level] = x.[level] + 1
FROM x INNER JOIN dbo.tree AS t
ON t.ParentID = x.ID
)
SELECT ID, name, ParentID, [level] FROM x
ORDER BY [level]
OPTION (MAXRECURSION 32);
GO
Don't forget to clean up:
DROP TABLE dbo.tree;
This might be a useful article. An alternative is hierarchyid but I find it overly complex for most scenarios.
Aaron Bertrands answer is very good for the general case. If you only ever need to display the whole tree at once, you can just query the whole table and perform the tree-building in-memory. This is likely to be more convenient and flexible. Performance also will be slightly better (the whole table needs to be downloaded anyway and C# is faster for such calculations than SQL Server).
If you only need a part of the tree this method is not recommended because you'd be downloading more data than needed.
I have the requirement to search several different tables in my SQL Server database. And I need to sort the results based on in which table the match occurred.
The approach I've taken is shown below. However, this doesn't seem very efficient as the amount of data grows.
Can anyone suggests any tricks to optimize this?
-- Full-text query
DECLARE #FtsQuery nvarchar(100)
SET #FtsQuery = 'FORMSOF(INFLECTIONAL, detail)'
-- Maximum characters in description column
DECLARE #MaxDescription int
SET #MaxDescription = 250
SELECT 1 AS RankGroup, FTS.Rank, Id, Title, LEFT([Description], #MaxDescription) AS Description FROM Table1
INNER JOIN CONTAINSTABLE(Table1, *, #FtsQuery) AS FTS ON FTS.[KEY] = Table1.Id
UNION SELECT 2, FTS.Rank, Id, Title, NULL FROM Table2
INNER JOIN CONTAINSTABLE(Table2, *, #FtsQuery) AS FTS ON FTS.[KEY] = Table2.Id
UNION SELECT 3, FTS.Rank, Id, Title, LEFT([Description], #MaxDescription) FROM Table3
INNER JOIN CONTAINSTABLE(Table3, *, #FtsQuery) AS FTS ON FTS.[KEY] = Table3.Id
UNION SELECT 4, FTS.Rank, Id, Title, LEFT([Description], #MaxDescription) FROM Table4
INNER JOIN CONTAINSTABLE(Table4, *, #FtsQuery) AS FTS ON FTS.[KEY] = Table4.Id
UNION SELECT 5, FTS.Rank, Id, Title, LEFT([Description], #MaxDescription) FROM Table5
INNER JOIN CONTAINSTABLE(Table5, *, #FtsQuery) AS FTS ON FTS.[KEY] = Table5.Id
ORDER BY RankGroup, Rank DESC
One idea I'd considered is to create an indexed view and then perform the search on the view. But since the view would need these UNIONs, it's hard to see how that would be any more efficient.
This is a difficult issue, because CONTAINSTABLE can only search a single table's FTS index at a time. Your UNION solution above is fine as long as your performance is acceptable.
We faced the same issue of needing to efficiently search many columns from many tables in a single query. What we did was aggregate all of the data from these columns and tables into a single read-only table. Our query then only needed a single CONTAINSTABLE call
CONTAINSTABLE(AggregatedTable, AggregatedColumn, #FtsQuery)
We have a scheduled job that runs every 5-10 minutes and incrementally aggregates any modified content from our source table into our single read-only aggregated content table.
In general it seems that using FTS in any reasonably-sized database and user load means you are always battling with performance. If you find that no matter what you do you cannot get the performance to be acceptable, you may need to investigate other technologies such as
Lucene.
I have a table in SQL server that has the normal tree structure of Item_ID, Item_ParentID.
Suppose I want to iterate and get all CHILDREN of a particular Item_ID (at any level).
Recursion seems an intuitive candidate for this problem and I can write an SQL Server function to do this.
Will this affect performance if my table has many many records?
How do I avoid recursion and simply query the table? Please any suggestions?
With the new MS SQL 2005 you could use the WITHkeyword
Check out this question and particularly this answer.
With Oracle you could use CONNECT BY keyword to generate hierarchical queries (syntax).
AFAIK with MySQL you'll have to use the recursion.
Alternatively you could always build a cache table for your records parent->child relationships
As a general answer, it is possible to do some pretty sophisticated stuff in SQL Server that normally needs recursion, simply by using an iterative algorithm. I managed to do an XHTML parser in Transact SQL that worked surprisingly well. The the code prettifier I wrote was done in a stored procedure. It aint elegant, it is rather like watching buffalo doing Ballet. but it works .
Are you using SQL 2005?
If so you can use Common Table Expressions for this. Something along these lines:
;
with CTE (Some, Columns, ItemId, ParentId) as
(
select Some, Columns, ItemId, ParentId
from myTable
where ItemId = #itemID
union all
select a.Some, a.Columns, a.ItemId, a.ParentId
from myTable as a
inner join CTE as b on a.ParentId = b.ItemId
where a.ItemId <> b.ItemId
)
select * from CTE
The problem you will face with recursion and performance is how many times it will have to recurse to return the results. Each recursive call is another separate call that will have to be joined into the total results.
In SQL 2k5 you can use a common table expression to handle this recursion:
WITH Managers AS
(
--initialization
SELECT EmployeeID, LastName, ReportsTo
FROM Employees
WHERE ReportsTo IS NULL
UNION ALL
--recursive execution
SELECT e.employeeID,e.LastName, e.ReportsTo
FROM Employees e INNER JOIN Managers m
ON e.ReportsTo = m.employeeID
)
SELECT * FROM Managers
or another solution is to flatten the hierarchy into a another table
Employee_Managers
ManagerId (PK, FK to Employee table)
EmployeeId (PK, FK to Employee table)
All the parent child relation ships would be stored in this table, so if Manager 1 manages Manager 2 manages employee 3, the table would look like:
ManagerId EmployeeId
1 2
1 3
2 1
This allows the hierarchy to be easily queried:
select * from employee_managers em
inner join employee e on e.employeeid = em.employeeid and em.managerid = 42
Which would return all employees that have manager 42. The upside will be greater performance, but downside is going to be maintaining the hierarchy
Joe Celko has a book (<- link to Amazon) specifically on tree structures in SQL databases. While you would need recursion for your model and there would definitely be a potential for performance issues there, there are alternative ways to model a tree structure depending on what your specific problem involves which could avoid recursion and give better performance.
Perhaps some more detail is in order.
If you have a master-detail relationship as you describe, then won't a simple JOIN get what you need?
As in:
SELECT
SOME_FIELDS
FROM
MASTER_TABLE MT
,CHILD_TABLE CT
WHERE CT.PARENT_ID = MT.ITEM_ID
You shouldn't need recursion for children - you're only looking at the level directly below (i.e. select * from T where ParentId = #parent) - you only need recursion for all descendants.
In SQL2005 you can get the descendants with:
with AllDescendants (ItemId, ItemText) as (
select t.ItemId, t.ItemText
from [TableName] t
where t.ItemId = #ancestorId
union
select sub.ItemId, sub.ItemText
from [TableName] sub
inner join [TableName] tree
on tree.ItemId = sub.ParentItemId
)
You don't need recursion at all....
Note, I changed columns to ItemID and ItemParentID for ease of typing...
DECLARE #intLevel INT
SET #intLevel = 1
INSERT INTO TempTable(ItemID, ItemParentID, Level)
SELECT ItemID, ItemParentID, #intLevel
WHERE ItemParentID IS NULL
WHILE #intLevel < #TargetLevel
BEGIN
SET #intLevel = #intLevel + 1
INSERT INTO TempTable(ItemID, ItemParentID, Level)
SELECt ItemID, ItemParentID, #intLevel
WHERE ItemParentID IN (SELECT ItemID FROM TempTable WHERE Level = #intLevel-1)
-- If no rows are inserted then there are no children
IF ##ROWCOUNT = 0
BREAK
END
SELECt ItemID FROM TempTable WHERE Level = #TargetLevel