根据前任排序 SQL 结果

Posted

技术标签:

【中文标题】根据前任排序 SQL 结果【英文标题】:Ordering SQL results based on predecessor 【发布时间】:2011-06-09 18:38:09 【问题描述】:

我的数据库中有下表:

create table (
    name        varchar(25),
    value       int,
    predecessor varchar(25)
);

带有样本数据:

name           | value | predecessor
---------------+-------+---------------
buyingredients | 10    | null
eat            |  3    | cook
cook           | 12    | mixingredients
mixingredients |  5    | buyingredients

我想要一个选择namevalue 的SQL 查询,排序后前任null 的项目排在第一位,然后前任与该行名称相同的行排在后面,依此类推(即购买食材、混合食材、烹饪、食用)。

排序是严格线性的 - 如果两行具有相同的前任值,则行为未定义。

我正在努力寻找能够产生我想要的排序的 SQL 查询。我真的不知道从哪里开始。

我使用的是 Informix 数据库,尽管任何 SQL 变体都是一个有用的起点。

更新以反映排序不是按字母顺序的事实

【问题讨论】:

@ICR - 嗨。如果您的示例给出了前导列未按字母顺序排列的情况,这将有所帮助 - 因为它目前使我们相信这就是您所追求的。 @ICR 如果记录有一个数字键,你的目标会容易得多。 @WillA:是的,没错,谢谢。我假设字母顺序不是预期的效果,但更新的示例将是澄清的最佳方式。 @Fosco:我实际上不明白如果记录有数字键,目标会变得更容易;你能解释一下吗? @Fosco 我同意,如果订单是数字指定的会容易得多,但我有上级的心血来潮要满足。 【参考方案1】:

在带有公用表表达式的 Transact-SQL 中:

WITH LinkedList (name, value, predecessor, ordering)
AS
(
    SELECT a.name, a.value, a.predecessor, 0
    FROM   YourTable a
    WHERE  a.predecessor IS NULL
    UNION ALL
    SELECT b.name, b.value, b.predecessor, c.ordering + 1
    FROM   YourTable b
    INNER JOIN LinkedList c
    ON     b.predecessor = c.name
)
SELECT d.name, d.value
FROM   LinkedList d
ORDER BY d.ordering, d.name

我不知道 Informix 是否有这种构造,但您要问的本质上是一个递归查询,在 Transact-SQL 中为您提供公用表表达式。

【讨论】:

这正是我想要的。不幸的是,我在 Informix 中找不到“with”构造。我会花点时间看看是否有人可以在 Informix 特定部分提供帮助,但如果没有,我会接受这个作为答案。【参考方案2】:

Informix 不支持 WITH 子句,更不用说 WITH RECURSIVE。这使得这些方法无法操作。没有一种特别干净的方法。如果您有两个或三个列表会发生什么,并不完全清楚,也不清楚您是在处理简单列表(示例是列表)还是更一般的树结构。

但是,您可以创建一个临时表并在存储过程中使用循环填充该表,然后以适当的顺序从临时表中进行选择。这是繁琐的,而不是非常困难的。您对表进行广度优先搜索。我注意到您没有给您的表命名 - 因此在下文中称为“匿名”。

CREATE TEMP TABLE Flattened
(
    Hierarchy   SERIAL,
    Level       INTEGER,
    Name        VARCHAR(25),
    Value       INTEGER,
    Predecessor VARCHAR(25)
);

CREATE TEMP TABLE Intermediate
(
    Hierarchy   SERIAL,
    Level       INTEGER,
    Name        VARCHAR(25),
    Value       INTEGER,
    Predecessor VARCHAR(25)
);

INSERT INTO Flattened(Hierarchy, Level, Name, Value, Predecessor)
   SELECT 0, 0, name, value, predecessor
     FROM Anonymous
    WHERE Predecessor IS NULL;

WHILE ...any rows were inserted into table...
    INSERT INTO Intermediate(Hierarchy, Level, Name, Value, Predecessor)
        SELECT F.Hierarchy, F.Level + 1, A.Name, A.Value, A.Predecessor
          FROM Flattened AS F, Anonymous AS A
         WHERE F.Name = A.Predecessor
           AND F.Level = (SELECT MAX(Level) FROM Flattened);
    INSERT INTO Flattened SELECT * FROM Intermediate;
    DELETE FROM Intermediate;
END WHILE

DROP TABLE Intermediate;

现在你可以使用了:

SELECT Name, Value, Predecessor
  FROM Flattened
 ORDER BY Hierarchy, Level, Name;

唯一剩下的棘手问题是计算插入了多少行。在存储过程中,这可能是最简单的:

SELECT COUNT(*) INTO n_count FROM Flattened;

LET o_count = n_count - 1;
WHILE o_count != n_count
    ...as above - two INSERT operations and a DELETE operation
    LET o_count = n_count;
    SELECT COUNT(*) INTO n_count FROM Flattened;
END WHILE;

把它们放在一起,这对我有用(在记录的数据库中)。

BEGIN;

CREATE TABLE Anonymous
(
     Name        VARCHAR(25),
     Value       INTEGER,
     Predecessor VARCHAR(25)
);

INSERT INTO Anonymous VALUES("buyingredients", 10, NULL);
INSERT INTO Anonymous VALUES("eat", 3, "cook");
INSERT INTO Anonymous VALUES("cook", 12, "mixingredients");
INSERT INTO Anonymous VALUES("mixingredients", 5, "buyingredients");

CREATE PROCEDURE Flatten_Anonymous()

    DEFINE old_count INTEGER;
    DEFINE new_count INTEGER;

    CREATE TEMP TABLE Flattened
    (
        Hierarchy   SERIAL,
        Level       INTEGER,
        Name        VARCHAR(25),
        Value       INTEGER,
        Predecessor VARCHAR(25)
    );

    CREATE TEMP TABLE Intermediate
    (
        Hierarchy   SERIAL,
        Level       INTEGER,
        Name        VARCHAR(25),
        Value       INTEGER,
        Predecessor VARCHAR(25)
    );

    INSERT INTO Flattened(Hierarchy, Level, Name, Value, Predecessor)
       SELECT 0, 0, name, value, predecessor
         FROM Anonymous
        WHERE Predecessor IS NULL;

    SELECT COUNT(*) INTO new_count FROM Flattened;
    LET old_count = new_count - 1;
    WHILE old_count != new_count
        INSERT INTO Intermediate(Hierarchy, Level, Name, Value, Predecessor)
            SELECT F.Hierarchy, F.Level + 1, A.Name, A.Value, A.Predecessor
              FROM Flattened AS F, Anonymous AS A
             WHERE F.Name = A.Predecessor
               AND F.Level = (SELECT MAX(Level) FROM Flattened);
        INSERT INTO Flattened SELECT * FROM Intermediate;
        DELETE FROM Intermediate;
        LET old_count = new_count;
        SELECT COUNT(*) INTO new_count FROM Flattened;
    END WHILE

    DROP TABLE Intermediate;

END PROCEDURE;

EXECUTE PROCEDURE Flatten_Anonymous();

SELECT Name, Value, Predecessor
  FROM Flattened
 ORDER BY Hierarchy, Level, Name;

DROP TABLE Flattened;

ROLLBACK;

输出是:

buyingredients     10
mixingredients      5  buyingredients
cook               12  mixingredients
eat                 3  cook

测试平台:在 MacOS X 10.6.7 上运行的 Informix 11.70.FC2。

未使用多个独立的层次结构或使用树结构层次结构而不是简单列表进行正式测试。

【讨论】:

这不是很清楚,但是“排序是严格线性的 - 如果两行具有相同的前任值的行为是未定义的。”是为了表明它不是一棵树。【参考方案3】:

不过,我使用的是 Informix 数据库 SQL 的任何变体都是有用的 起点。

我对 Informix 一无所知,所以这里有一个 SQL Server 版本。 @T 是我用作测试表的表变量。为了链接行,我使用递归 CTE。

declare @T table
(
  name varchar(25),
  value int,
  predecessor varchar(25)
)  

insert into @T
select 'buyingredients', 10, null union all
select 'cook',           12, 'mixingredients' union all
select 'mixingredients',  5, 'buyingredients' union all
select 'eat',             3, 'cook'

;with cte as 
(
  select T.name,
         T.value,
         T.predecessor,
         1 as sortorder
  from @T as T
  where T.predecessor is null
  union all
  select T.name,
         T.value,
         T.predecessor,
         C.sortorder+1 as sortorder
  from @T as T
    inner join cte as C
      on T.predecessor = C.name
)
select C.name,
       C.value,
       C.predecessor
from cte as C
order by C.sortorder

您可以在这里试用:https://data.stackexchange.com/***/q/102534/recursive-cte-to-build-a-chain

编辑

我对 Informix 仍然一无所知,但也许您可以使用 Node DataBlade Module 做一些事情

【讨论】:

【参考方案4】:

我认为做你想做的事情的方法是为每个项目构建一个带有“深度”字段的临时表,然后使用该深度字段对结果进行排序。

【讨论】:

这不是 OP 想要的。 @Denis:你能证明这一点吗? 我已经在您对我自己的答案的评论中回答了这个问题(并编辑了答案)。他对订购深度不感兴趣。他对按前任排序很感兴趣,简单明了。或者然后他以一种非常奇怪的方式表达了这个问题。 @Denis:我认为措辞可能有点奇怪,但我认为他的意思是他希望顺序是绝对父级(null 前任)首先出现的顺序,然后是项目接下来是那个父母(那个前任),等等。 @Denis 深度搜索实际上是一种非常合乎逻辑的处理方式。 OP 想要的是类似于链表的行为——每条记录都指向另一条记录。这可以通过深度场来实现——虽然很麻烦,但它会起作用。

以上是关于根据前任排序 SQL 结果的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 sql 中的匹配数针对关键字数组和排序结果搜索字符串?

根据第二个查询中的时间对两个查询的结果进行排序[关闭]

报表也可以根据单元格计算后结果进行排序

SQL对查询结果进行分组

按查询的where子句中的字段顺序对sql查询的结果进行排序

《SQL Cookbook》 - 第二章 查询结果排序