Intereting Posts
строка num не отображает строки при использовании между ключевым словом Рекурсивный запрос в DB2 для получения всех элементов в цепочке Восстановить базу данных «Сбой восстановления для сервера» xxx » Как копировать запись, меняя только идентификатор? Шаблон проектирования для динамического добавления столбцов в таблицу базы данных SQL – Как проверить, находится ли элемент в списке в записи Бланк FROM в JPQL? Получение последнего идентификатора вставки с SQLite.NET в C # В базе данных нет таблиц SQL: выбор строк, где значение столбца изменилось с предыдущей строки Показывать отношение от одного до многих как 2 столбца – 1 уникальная строка (список идентификаторов и разделенных запятыми) FILESTREAM / FILETABLE Разъяснения для реализации Postgresql выберите, пока не будет достигнута определенная общая сумма Препятствует ли спецификация JDBC '?' от использования в качестве оператора (вне кавычек)? org.dbunit.dataset.NoSuchTableException, но таблица существует

Иерархия данных SQL

Я просмотрел несколько учебников по иерархии SQL, но ни один из них не имел большого смысла для моего приложения. Возможно, я просто не понимаю их правильно. Я пишу приложение C # ASP.NET, и я хотел бы создать иерархию древовидных представлений из данных SQL.

Так будет работать иерархия:

 SQL TABLE

 ID |  Идентификационный номер |  имя
 _______ |  __________ | _____________
 1331 |  1331 |  дом
 1321 |  1331 |  Комната
 2141 |  1321 |  Постель
 1251 |  2231 |  Гимнастический зал

Если идентификатор и идентификатор местоположения совпадают, это будет определять верхний родитель. У всех детей этого родителя будет такой же идентификатор местоположения, что и у родителя. Любые внуки этого ребенка имеют идентификатор местоположения, равный ID ребенка, и так далее.

Для приведенного выше примера:

 - Дом
    -- Комната
        --- Кровать

Любая помощь или руководство, чтобы легко следовать учебникам было бы с благодарностью.

РЕДАКТИРОВАТЬ:

Код, который у меня есть до сих пор, но он получает только Родитель и Дети, а не Грандхидрен. Я не могу понять, как заставить его рекурсивно получить все узлы.

using System; using System.Data; using System.Collections.Generic; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using System.Configuration; using System.Data.SqlClient; namespace TreeViewProject { public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { PopulateTree(SampleTreeView); } public void PopulateTree(Control ctl) { // Data Connection SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["AssetWhereConnectionString1"].ConnectionString); connection.Open(); // SQL Commands string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations"; SqlDataAdapter adapter = new SqlDataAdapter(getLocations, connection); DataTable locations = new DataTable(); // Fill Data Table with SQL Locations Table adapter.Fill(locations); // Setup a row index DataRow[] myRows; myRows = locations.Select(); // Create an instance of the tree TreeView t1 = new TreeView(); // Assign the tree to the control t1 = (TreeView)ctl; // Clear any exisiting nodes t1.Nodes.Clear(); // BUILD THE TREE! for (int p = 0; p < myRows.Length; p++) { // Get Parent Node if ((Guid)myRows[p]["ID"] == (Guid)myRows[p]["LocationID"]) { // Create Parent Node TreeNode parentNode = new TreeNode(); parentNode.Text = (string)myRows[p]["Name"]; t1.Nodes.Add(parentNode); // Get Child Node for (int c = 0; c < myRows.Length; c++) { if ((Guid)myRows[p]["LocationID"] == (Guid)myRows[c]["LocationID"] && (Guid)myRows[p]["LocationID"] != (Guid)myRows[c]["ID"] /* Exclude Parent */) { // Create Child Node TreeNode childNode = new TreeNode(); childNode.Text = (string)myRows[c]["Name"]; parentNode.ChildNodes.Add(childNode); } } } } // ALL DONE BUILDING! // Close the Data Connection connection.Close(); } } } 

Вот фрагмент фактической таблицы SQL: Locations

 Идентификатор ID ID
 ____________________________________ ____________________________________ ______________
 DEAF3FFF-FD33-4ECF-910B-1B07DF192074 48700BC6-D422-4B26-B123-31A7CB704B97 Капля F
 48700BC6-D422-4B26-B123-31A7CB704B97 7EBDF61C-3425-46DB-A4D5-686E91FD0832 Olway
 06B49351-6D18-4595-8228-356253CF45FF 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Капля E 5
 E98BC1F6-4BAE-4022-86A5-43BBEE2BA6CD DEAF3FFF-FD33-4ECF-910B-1B07DF192074 Капля F 6
 F6A2CF99-F708-4C61-8154-4C04A38ADDC6 7EBDF61C-3425-46DB-A4D5-686E91FD0832 Pree
 0EC89A67-D74A-4A3B-8E03-4E7AAAFEBE51 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Капля E 4
 35540B7A-62F9-487F-B65B-4EA5F42AD88A 48700BC6-D422-4B26-B123-31A7CB704B97 Распределение Olway
 5000AB9D-EB95-48E3-B5C0-547F5DA06FC6 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Выход 1
 53CDD540-19BC-4BC2-8612-5C0663B7FDA5 6E8C65AC-CB22-42DA-89EB-D81C5ED0BBD0 Капля E 3
 7EBDF61C-3425-46DB-A4D5-686E91FD0821 B46C7305-18B1-4499-9E1C-7B6FDE786CD6 ИСПЫТАНИЕ 1
 7EBDF61C-3425-46DB-A4D5-686E91FD0832 7EBDF61C-3425-46DB-A4D5-686E91FD0832 HMN

Благодарю.

Вы ищете рекурсивный запрос, используя общее табличное выражение, или CTE для краткости. Подробную запись для этого в SQL Server 2008 можно найти в MSDN .

В общем, они имеют структуру, похожую на следующее:

 WITH cte_name ( column_name [,...n] ) AS ( –- Anchor CTE_query_definition UNION ALL –- Recursive portion CTE_query_definition ) -- Statement using the CTE SELECT * FROM cte_name 

Когда это будет выполнено, SQL Server сделает что-то похожее на следующее (перефразируемое на более простой язык из MSDN):

  1. Разделите выражение CTE на элементы привязки и рекурсии.
  2. Запустите якорь, создав первый результирующий набор.
  3. Запустите рекурсивную часть с предыдущим шагом в качестве ввода.
  4. Повторите шаг 3 до тех пор, пока не будет возвращен пустой набор.
  5. Верните результат. Это СОЕДИНЕНИЕ ВСЕХ якорей и всех рекурсивных шагов.

Для этого конкретного примера попробуйте что-то вроде этого:

 With hierarchy (id, [location id], name, depth) As ( -- selects the "root" level items. Select ID, [LocationID], Name, 1 As depth From dbo.Locations Where ID = [LocationID] Union All -- selects the descendant items. Select child.id, child.[LocationID], child.name, parent.depth + 1 As depth From dbo.Locations As child Inner Join hierarchy As parent On child.[LocationID] = parent.ID Where child.ID != parent.[Location ID]) -- invokes the above expression. Select * From hierarchy 

Учитывая данные вашего примера, вы должны получить что-то вроде этого:

 ID | Location ID | Name | Depth _______| __________ |______ | _____ 1331 | 1331 | House | 1 1321 | 1331 | Room | 2 2141 | 1321 | Bed | 3 

Обратите внимание, что «Тренажерный зал» исключается. Исходя из ваших данных образца, его идентификатор не соответствует его [Идентификатору местоположения], поэтому он не будет элементом уровня root. Идентификатор местоположения, 2231, не отображается в списке действительных идентификаторов родителей.


Изменить 1:

Вы спросили, как получить это в структуре данных C #. Существует множество различных способов представления иерархии на C #. Вот один пример, выбранный по своей простоте. Разумеется, реальный образец кода будет более обширным.

Первый шаг – определить, как выглядит каждый узел в иерархии. Помимо содержания свойств для каждой точки привязки в узле, я включил свойства « Parent и Children , а также « Add ребенка» и « Get ребенка». Метод Get будет искать всю ось потомков узла, а не только собственные дочерние узлы.

 public class LocationNode { public LocationNode Parent { get; set; } public List<LocationNode> Children = new List<LocationNode>(); public int ID { get; set; } public int LocationID { get; set; } public string Name { get; set; } public void Add(LocationNode child) { child.Parent = this; this.Children.Add(child); } public LocationNode Get(int id) { LocationNode result; foreach (LocationNode child in this.Children) { if (child.ID == id) { return child; } result = child.Get(id); if (result != null) { return result; } } return null; } } 

Теперь вы захотите заполнить свое дерево. У вас есть проблема: трудно заполнить дерево в неправильном порядке. Прежде чем добавить дочерний узел, вам действительно нужна ссылка на родительский узел. Если вам нужно сделать это не по порядку, вы можете уменьшить проблему, выполнив два прохода (один для создания всех узлов, затем другой для создания дерева). Однако в этом случае это не требуется.

Если вы возьмете SQL-запрос, указанный выше, и упорядочите его по столбцу depth , вы можете быть математически уверены, что никогда не столкнетесь с дочерним узлом, прежде чем столкнетесь с его родительским узлом. Поэтому вы можете сделать это за один проход.

Вам все равно понадобится узел, который будет служить «корнем» вашего дерева. Вы можете решить, будет ли это «Дом» (из вашего примера), или это вымышленный узел-заполнитель, который вы создаете именно для этой цели. Я предлагаю позже.

Итак, к коду! Опять же, это оптимизировано для простоты и удобочитаемости. Существуют некоторые проблемы с производительностью, которые вы можете захотеть решить в производственном коде (например, на самом деле нет необходимости постоянно искать «родительский» узел). Я избегал этих оптимизаций здесь, потому что они увеличивают сложность.

 // Create the root of the tree. LocationNode root = new LocationNode(); using (SqlCommand cmd = new SqlCommand()) { cmd.Connection = conn; // your connection object, not shown here. cmd.CommandText = "The above query, ordered by [Depth] ascending"; cmd.CommandType = CommandType.Text; using (SqlDataReader rs = cmd.ExecuteReader()) { while (rs.Read()) { int id = rs.GetInt32(0); // ID column var parent = root.Get(id) ?? root; parent.Add(new LocationNode { ID = id, LocationID = rs.GetInt32(1), Name = rs.GetString(2) }); } } } 

Та-да! root LocationNode теперь содержит всю вашу иерархию. Кстати, я на самом деле не выполнил этот код, поэтому, пожалуйста, дайте мне знать, если вы заметите какие-либо вопиющие проблемы.


Изменить 2

Чтобы исправить код примера, внесите следующие изменения:

Удалите эту строку:

 // Create an instance of the tree TreeView t1 = new TreeView(); 

Эта строка на самом деле не проблема, но ее нужно удалить. Ваши комментарии здесь неточны; вы на самом деле не назначаете дерево элементу управления. Вместо этого вы создаете новый TreeView, назначая его t1 , а затем сразу назначая другой объект t1 . Созданный TreeView теряется, как только выполняется следующая строка.

Исправьте инструкцию SQL

 // SQL Commands string getLocations = "SELECT ID, LocationID, Name FROM dbo.Locations"; 

Замените эту инструкцию SQL оператором SQL, который я предложил ранее, с предложением ORDER BY. Прочтите мои предыдущие изменения, которые объясняют, почему важна «глубина»: вы действительно хотите добавить узлы в определенном порядке. Вы не можете добавить дочерний узел до тех пор, пока не получите родительский узел.

По желанию, я думаю, что вам не нужны накладные расходы SqlDataAdapter и DataTable. Первоначально предлагаемое нами решение DataReader было проще, проще в работе и более эффективно с точки зрения ресурсов.

Кроме того, большинство объектов C # SQL реализуют IDisposable , поэтому вы захотите убедиться, что используете их правильно. Если что-то реализует IDisposable , обязательно заверните его в using операторов (см. Пример предыдущего кода C #).

Исправьте цикл построения дерева

Вы получаете только родительский и дочерний узлы, потому что у вас есть петля для родителей и внутренний цикл для детей. Как вы уже должны знать, вы не получаете внуков, потому что у вас нет кода, который их добавляет.

Вы могли бы добавить внутренний внутренний цикл, чтобы получить внуков, но ясно, что вы просите о помощи, потому что вы поняли, что это приведет только к безумию. Что произойдет, если вы тогда захотите правнуков? Внутренняя внутренняя внутренняя петля? Этот метод не является жизнеспособным.

Вы, наверное, подумали о рекурсии. Это идеальное место для этого, и если вы имеете дело с древовидными структурами, это в конечном итоге наступит. Теперь, когда вы отредактировали свой вопрос, ясно, что ваша проблема имеет мало, если вообще что-либо, отношение к SQL . Ваша настоящая проблема заключается в рекурсии. Кто-то может в конце концов прийти и разработать для этого рекурсивное решение. Это был бы совершенно правильный и, возможно, предпочтительный подход.

Однако мой ответ уже рассмотрел рекурсивную часть – он просто переместил ее в слой SQL. Поэтому я сохраню свой предыдущий код, поскольку я считаю, что это подходящий общий ответ на вопрос. Для вашей конкретной ситуации вам нужно сделать еще несколько изменений.

Во-первых, вам не понадобится класс LocationNode который я предложил. TreeNode этого вы используете TreeNode , и это будет работать нормально.

Во-вторых, TreeView.FindNode похож на метод LocationNode.Get который я предложил, за исключением того, что FindNode требует полного пути к узлу. Чтобы использовать FindNode , вы должны изменить SQL, чтобы предоставить вам эту информацию.

Поэтому вся ваша функция PopulateTree должна выглядеть так:

 public void PopulateTree(TreeView t1) { // Clear any exisiting nodes t1.Nodes.Clear(); using (SqlConnection connection = new SqlConnection()) { connection.ConnectionString = "((replace this string))"; connection.Open(); string getLocations = @" With hierarchy (id, [location id], name, depth, [path]) As ( Select ID, [LocationID], Name, 1 As depth, Cast(Null as varChar(max)) As [path] From dbo.Locations Where ID = [LocationID] Union All Select child.id, child.[LocationID], child.name, parent.depth + 1 As depth, IsNull( parent.[path] + '/' + Cast(parent.id As varChar(max)), Cast(parent.id As varChar(max)) ) As [path] From dbo.Locations As child Inner Join hierarchy As parent On child.[LocationID] = parent.ID Where child.ID != parent.[Location ID]) Select * From hierarchy Order By [depth] Asc"; using (SqlCommand cmd = new SqlCommand(getLocations, connection)) { cmd.CommandType = CommandType.Text; using (SqlDataReader rs = cmd.ExecuteReader()) { while (rs.Read()) { // I guess you actually have GUIDs here, huh? int id = rs.GetInt32(0); int locationID = rs.GetInt32(1); TreeNode node = new TreeNode(); node.Text = rs.GetString(2); node.Value = id.ToString(); if (id == locationID) { t1.Nodes.Add(node); } else { t1.FindNode(rs.GetString(4)).ChildNodes.Add(node); } } } } } } 

Пожалуйста, дайте мне знать, если вы найдете дополнительные ошибки!

Я также рекомендую вам взглянуть на тип данных HierarchyId, введенный в SQL Server 2008, который дает вам множество возможностей для перемещения и управления древовидной структурой. Вот учебник:

Работа с типом данных HierarchyId SQL Server в приложении .NET

Извините, я просто просматриваю StackOverflow. Я видел ваш вопрос, и мне кажется, что я написал статью, отвечая на ваш вопрос три года назад. Пожалуйста, дайте мне знать, если это поможет.
http://www.simple-talk.com/dotnet/asp.net/rendering-hierarchical-data-with-the-treeview/

В SQl 2008 появилась новая функция. Это иерархия . Эта функция облегчает мою жизнь.

Есть полезный метод для типа данных hierarchyid , GetAncestor (), GetRoot () … Он уменьшит сложность запроса, когда я буду работать над иерархией.