【问题标题】:How to design a SQL lookup table where order matters如何设计一个顺序很重要的 SQL 查找表
【发布时间】:2017-01-17 17:31:13
【问题描述】:

我正在尝试创建一个查找表,其中元素的顺序很重要。我的查找表具有以下结构

id table1id table2id 
1     1      1
2     1      2
3     1      3
4     2      2
5     2      1
6     2      3

我的目标是根据 table2ids 找到 table1id。所以我运行的示例查询是

Select table1id 
from junctionTable 
where table2ids in (1,2,3)
group by table1id
Having count(table1id) = 3

这可行,但是,它将返回 Table1Ids 1 和 2。我只想要具有 1、2、3 的特定顺序的 Table1ID,因此它应该只返回 TableId =1。

在表上放置一个订单列是可行的,但是随着项目数量的增加,它会使查询变得更加困难。

select table1id from junctionTable 
where table2id =1 and order =1 and    
table2id = 2 and order =2 
etc...

还有什么我没有想到的可以做的吗?或者处理这种情况的最佳方法是什么?

这个问题源于我试图规范化表格。参考Normalize a table with tightly coupled data

【问题讨论】:

  • 你知道having是做什么的吗?
  • id 列是否反映了所需的顺序?如果不是,是的,您需要一些其他列来定义排序。表格本身没有内在的顺序。
  • @Ven 是的,我愿意。我已经更新了我的问题,以包括我忘记的小组。我包括有,因为我可以有 1 个或只有 2 个 id,我只想要有 3 个的,或者 in 子句中有很多。

标签: sql sql-server junction-table


【解决方案1】:

此查询检查Id和Table2Id的顺序是否相同。

WITH CTE AS (
    SELECT table1id, table2id
    ,orderIsOK = ROW_NUMBER() OVER(PARTITION BY table1id ORDER BY id) 
               - ROW_NUMBER() OVER(PARTITION BY table1id ORDER BY table2id)   
    FROM JunctionTable 
    WHERE table2id IN (1, 2, 3)
) 
SELECT table1id 
FROM CTE
WHERE orderIsOK = 0
GROUP BY table1id
HAVING COUNT(DISTINCT table2id) = 3

或者,您可能希望明确指定所需的顺序。在下面的示例中,所需的顺序指定为 (2,1,3)

WITH desiredOrder(rowNumber, value) as (
    select * 
    from (
        values (1,2)
        ,(2,1)
        ,(3,3)
        ) t (rowNumber, value)
),  
CTE AS (
    SELECT table1id, table2id
    ,orderIsOK = ROW_NUMBER() OVER(PARTITION BY table1id ORDER BY id)
            - do.rowNumber
    FROM @JunctionTable t
    JOIN desiredOrder do ON t.table2id = do.value
) 
SELECT table1id 
FROM CTE
WHERE orderIsOK=0
GROUP BY table1id
HAVING COUNT(DISTINCT table2id) = 3

【讨论】:

    【解决方案2】:

    为了解决这个问题,您必须创建一个查找表来定义您想要使用的目标 table2id 的所需顺序,因为INclause 在匹配这两个集合时不会强制执行顺序。

    如果您有 SQL Server 2012 或更高版本,则可以使用LAG 窗口函数来帮助解决问题,如下所示:

    DECLARE @table2IdInOrder TABLE
    (
        OrderID  INT IDENTITY(1,1)
        ,table2id INT
    );
    
    INSERT INTO @table2IdInOrder
    VALUES(2), (1), (3);
    
    
    WITH CTE_JT AS (
        SELECT   JT.id
                ,JT.table1id
                ,JT.table2id
                ,JT.table2id - LAG(JT.table2id, 1, 0) OVER(PARTITION BY JT.table1id ORDER BY JT.id) AS DeltaID
                ,COUNT(JT.table2id) OVER(PARTITION BY JT.table1id) AS RecordCount
        FROM @junctionTable JT
        WHERE   JT.table2id IN (SELECT table2id FROM @table2IdInOrder)
    )
    , CTE_OT
    AS
    (
        SELECT   OT.OrderID
                ,OT.table2id
                ,OT.table2id - LAG(OT.table2id, 1, 0) OVER(ORDER BY OT.OrderID) AS DeltaID
                ,COUNT(OT.table2id) OVER(PARTITION BY (SELECT 1 AS ID)) AS RecordCount
        FROM    @table2IdInOrder OT
    )
    SELECT      DISTINCT JT.table1id
    FROM        CTE_JT JT
    INNER JOIN  CTE_OT OT ON OT.table2id = JT.table2id AND OT.DeltaID = JT.DeltaID AND OT.RecordCount = JT.RecordCount;
    

    【讨论】:

      【解决方案3】:
      select table1id 
      from 
      (
      select table1id, (table2id - id) as diff
      from junctionTable 
      where table2ids in (1,2,3)
      )
      group by table1id, as diff 
      having count(*) = 3 
      

      【讨论】:

        【解决方案4】:

        如果排序很重要,则必须通过使其成为数据的属性来显式跟踪排序。在您当前的模型中,排序是隐式存储的,但这是行不通的,因为您永远不应该假设 SQL 存储和返回数据的顺序。

        鉴于您的问题中的基表,您可以添加一个 varchar 排序“正确的”table2id 排序,类似于

        Id  table1id  table2id  table2order
        1       1         1        1,2,3
        2       1         2        1,2,3
        3       1         3        1,2,3
        4       2         2        2,1,3
        5       2         1        2,1,3
        6       2         3        2,1,3
        

        这当然不是标准化的,所以你也可以有第二张表:

        table1id  table2order
            1        1,2,3
            2        2,1,3
        

        使用其中一种或另一种应该可以支持您的目标。

        【讨论】:

        • @Phillip 创建一个列出订单的 varChar 列会比创建一个 int 的 Order 列更有效/更好的数据库实践吗?
        • 最终,这取决于数据的处理方式。什么标识 table1id 的“正确顺序”?如果这是作为表中的一组行定期定义、管理和使用的,那么列解决方案可能是最好的(尽管这样的查询确实很难看)。如果它作为某种形式的简单分隔列表定期存储和使用,那么“单一属性”标签的想法是合适的(恕我直言,使用起来会简单得多)。
        • @Phillip 感谢您的帮助。这个问题起源于试图规范化一个表,其中我有 3 个 id 列,其中跟踪订单,因为所有数据都在一行中。但是,我想让它更具动态性,而不必在每次我们想要跟踪另一种材料时都创建一个新列。想一想,这应该是我开始的问题。
        【解决方案5】:

        下面的查询可以返回你预期的结果:

        WITH CTE AS (
            SELECT table1id, table2id, ROW_NUMBER() OVER(PARTITION BY table1id ORDER BY id) AS CustomOrder
            FROM JunctionTable 
            WHERE table2id IN (1, 2, 3)
        ) 
        SELECT table1id 
        FROM CTE
        WHERE table2id = CustomOrder
        GROUP BY table1id
        HAVING COUNT(DISTINCT table2id) = 3
        

        使用给定的示例数据进行演示:

        DECLARE @JunctionTable TABLE (id INT, table1id INT, table2id INT);
        
        INSERT INTO @JunctionTable (id, table1id, table2id) VALUES
        (1, 1, 1),
        (2, 1, 2),
        (3, 1, 3),
        (4, 2, 2),
        (5, 2, 1),
        (6, 2, 3);
        
        WITH CTE AS (
            SELECT table1id, table2id, ROW_NUMBER() OVER(PARTITION BY table1id ORDER BY id) AS CustomOrder
            FROM @JunctionTable 
            WHERE table2id IN (1, 2, 3)
        ) 
        SELECT table1id 
        FROM CTE
        WHERE table2id = CustomOrder
        GROUP BY table1id
        HAVING COUNT(DISTINCT table2id) = 3
        

        【讨论】:

        • 这个解决方案依赖于 ids 被查询为具体为 1,2 和 3。它不适用于任意值,也不适用于所寻求的集合是例如2,1,3。
        • @Damien_The_Unbeliever 这里table1id = 2的当前顺序是2,1,3,结果不考虑table1id = 2
        • 但它在 234 处中断。我指望 1 - x 并且不丢失。嘿,它适用于 123 +1
        猜你喜欢
        • 1970-01-01
        • 2021-03-10
        • 1970-01-01
        • 2016-11-03
        • 1970-01-01
        • 1970-01-01
        • 2015-06-02
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多