【问题标题】:Mapping fields of weakly related tables in SQLSQL中弱相关表的映射字段
【发布时间】:2014-04-27 17:03:34
【问题描述】:

我正在寻找一个 SQL 查询,它可以将一组不同大小的项目映射到一组不同大小的存储桶。

我想满足以下条件:

  • 桶的大小必须大于或等于项目的大小。
  • 每个存储桶只能包含一个项目,或者它是空的。
  • 每件物品只能放在一个桶中。
  • 任何项目都不能拆分到多个存储桶中。
  • 我想以某种方式填充存储桶,首先填充最小的未使用存储桶。
  • 然后初始项和存储桶集可以按大小或 id 排序,但不是增量的
  • 初始存储桶和项目集的大小和 ID 可以是任意的,并且不会从已知的最小值开始
  • 当存在有效映射时,结果必须始终正确
  • 如果没有有效的映射(例如,如果项目多于存储桶),则允许结果不正确,但如果结果为空集或具有指示不正确的另一个属性/信号,我将不胜感激结果。

举个例子,假设我的存储桶和项目表如下所示:

Bucket:                     Item:
+---------------------+     +---------------------+
| BucketID | Size     |     | ItemID   | Size     |
+---------------------+     +---------------------+
| 1        | 2        |     | 1        | 2        |
| 2        | 2        |     | 2        | 2        |
| 3        | 2        |     | 3        | 5        |
| 4        | 4        |     | 4        | 11       |
| 5        | 4        |     | 5        | 12       |
| 6        | 7        |     +---------------------+
| 7        | 9        |
| 8        | 11       |
| 9        | 11       |
| 10       | 12       |
+---------------------+

然后,我想要一个返回以下结果表的映射:

Result:
+---------------------+
| BucketID | ItemID   |
+---------------------+
| 1        | 1        |
| 2        | 2        |
| 3        | NULL     |
| 4        | NULL     |
| 5        | NULL     |
| 6        | 3        |
| 7        | NULL     |
| 8        | 4        |
| 9        | NULL     |
| 10       | 5        |
+---------------------+

由于没有外键关系或其他东西,我可以将列修复到它们对应的存储桶(但只有关系 Bucket.Size >= Item.Size)我在使用有效 SQL 描述结果时遇到了很多麻烦询问。每当我使用连接或子选择时,我都会在存储桶中获得很大的项目(例如在 12 号存储桶中有一个 2 号项目,而 2 号存储桶仍然可用)或者我在其中得到相同的项目多个桶。

我现在花了一些时间自己寻找解决方案,我几乎可以说,最好不要在 SQL 中声明问题,而是在应用程序中声明问题,即只是获取表。

你认为这个任务在 SQL 中是可能的吗?如果是这样,如果您能帮我解决一个有效的查询,我将不胜感激。

编辑:查询应该至少兼容 Oracle、Postgres 和 SQLite 数据库

编辑 II:一个 SQL Fiddle 与示例查询上方的给定测试集,返回错误的结果,但接近结果可能看起来像 http://sqlfiddle.com/#!15/a6c30/1

【问题讨论】:

    标签: sql database sorting mapping bucket-sort


    【解决方案1】:

    试试这个... 我能够使用recursive CTE 实现这一点,所有这些都在 1 条 SQL 语句中

    我唯一的假设是 Bucket 和 Item 数据集已排序。

    DECLARE @BUCKET TABLE
        (
         BUCKETID INT
         , SIZE INT
        )
    
        DECLARE @ITEM TABLE
        (
         ITEMID INT
         , SIZE INT
        )
        ;  
        INSERT INTO @BUCKET
        SELECT 1,2 UNION ALL
        SELECT 2,2 UNION ALL
        SELECT 3,2 UNION ALL
        SELECT 4,4 UNION ALL
        SELECT 5,4 UNION ALL
        SELECT 6,7 UNION ALL
        SELECT 7,9 UNION ALL
        SELECT 8, 11 UNION ALL
        SELECT 9, 11 UNION ALL
        SELECT 10,12 
    
        INSERT INTO @ITEM
        SELECT 1,2 UNION ALL
        SELECT 2,2 UNION ALL
        SELECT 3,5 UNION ALL
        SELECT 4,11 UNION ALL
        SELECT 5,12;
    
        WITH TOTAL_BUCKETS
        AS (
            SELECT MAX(BUCKETID) CNT
            FROM @BUCKET
            ) -- TO GET THE TOTAL BUCKETS COUNT TO HALT THE RECURSION
            , CTE
        AS (
            --INVOCATION PART
            SELECT BUCKETID
                , (
                    SELECT MIN(ITEMID)
                    FROM @ITEM I2
                    WHERE I2.SIZE <= (
                            SELECT SIZE
                            FROM @BUCKET
                            WHERE BUCKETID = (1)
                            )
                    ) ITEMID --PICKS THE FIRST ITEM ID MATCH FOR THE BUCKET SIZE
                , BUCKETID + 1 NEXT_BUCKETID --INCREMENT FOR NEXT BUCKET ID 
                , (
                    SELECT ISNULL(MIN(ITEMID), 0)
                    FROM @ITEM I2
                    WHERE I2.SIZE <= (
                            SELECT SIZE
                            FROM @BUCKET
                            WHERE BUCKETID = (1)
                            )
                    ) --PICK FIRST ITEM ID MATCH
                + (
                    CASE 
                        WHEN (
                                SELECT ISNULL(MIN(ITEMID), 0)
                                FROM @ITEM I3
                                WHERE I3.SIZE <= (
                                        SELECT SIZE
                                        FROM @BUCKET
                                        WHERE BUCKETID = (1)
                                        )
                                ) IS NOT NULL
                            THEN 1
                        ELSE 0
                        END
                    ) NEXT_ITEMID --IF THE ITEM IS PLACED IN THE BUCKET THEN INCREMENTS THE FIRST ITEM ID
                , (
                    SELECT SIZE
                    FROM @BUCKET
                    WHERE BUCKETID = (1 + 1)
                    ) NEXT_BUCKET_SIZE --STATES THE NEXT BUCKET SIZE
            FROM @BUCKET B
            WHERE BUCKETID = 1
    
            UNION ALL
    
            --RECURSIVE PART
            SELECT NEXT_BUCKETID BUCKETID
                , (
                    SELECT ITEMID
                    FROM @ITEM I2
                    WHERE I2.SIZE <= NEXT_BUCKET_SIZE
                        AND I2.ITEMID = NEXT_ITEMID
                    ) ITEMID -- PICKS THE ITEM ID IF IT IS PLACED IN THE BUCKET
                , NEXT_BUCKETID + 1 NEXT_BUCKETID --INCREMENT FOR NEXT BUCKET ID 
                , NEXT_ITEMID + (
                    CASE 
                        WHEN (
                                SELECT I3.ITEMID
                                FROM @ITEM I3
                                WHERE I3.SIZE <= NEXT_BUCKET_SIZE
                                    AND I3.ITEMID = NEXT_ITEMID
                                ) IS NOT NULL
                            THEN 1
                        ELSE 0
                        END
                    ) NEXT_ITEMID --IF THE ITEM IS PLACED IN THE BUCKET THEN INCREMENTS THE CURRENT ITEM ID
                , (
                    SELECT SIZE
                    FROM @BUCKET
                    WHERE BUCKETID = (NEXT_BUCKETID + 1)
                    ) NEXT_BUCKET_SIZE --STATES THE NEXT BUCKET SIZE
            FROM CTE
            WHERE NEXT_BUCKETID <= (
                    SELECT CNT
                    FROM TOTAL_BUCKETS
                    ) --HALTS THE RECURSION
            )
        SELECT 
            BUCKETID
            , ITEMID
        FROM CTE
    

    【讨论】:

    • 太棒了!适用于我的测试集。我将 ISNULL 替换为 COALESCE 并添加“WITH RECURSIVE”以在 PostgreSQL 中运行查询。非常感谢!
    • 酷。你的测试集有多大?运行时间长吗?
    • ^_^ 不,我的意思是上面问题中提到的初始测试集。
    • 哦,好吧..我确实尝试了几个不同的集合,但有点惊讶它返回了预期的输出:)
    • 我会在此处发布我的结果以通知您,并确保您的答案将在截止日期结束前获得奖励 :) 当然,在某些情况下,查询将返回不正确结果。尤其是当有更多的项目然后桶或项目,对于给定的桶集来说太大了。不过没关系。
    【解决方案2】:

    我想说单个 SQL 查询可能不是这项工作的工具,因为通过向它们分配项目来“消耗”存储桶。您可以使用 SQL,但不能使用单个查询。以下伪代码中的建议:

    Have a cursor on ITEM: 
        within the FETCH loop for that {
        SELECT in BUCKET the bucket with minimum bucket id and bucket size >= item size 
        INSERT bucket id, item id to MAPPING
    }
    
    If you need the NULL (unoccupied) buckets, you can locate them via a further 
    INSERT into MAPPING (....)
    SELECT <bucket id>, NULL
    from    BUCKET 
    where <bucket id> not in (SELECT <bucket id> from MAPPING);
    

    【讨论】:

    • 谢谢您的回答!我还没有尝试过,老实说,这超出了我对 SQL 的了解。但我会试着理解。您认为我可以使用这种“命令式”方法作为子查询吗?将语句作为更高级别查询的子查询是我将解决方案作为单个 SQL 语句搜索的原因之一。
    • 嗯。好吧,我仍然不知道如何一次性完成,但是您可以使用 SQL 语句分几次完成:例如,语句 1,创建一个临时表,将所有项目与它们可以进入的潜在存储桶匹配.语句2,试试看能不能从临时表中得到每个item的最小匹配行。反正就是这样。
    【解决方案3】:

    使用来自@SoulTrain 的表定义(但需要提前对数据进行排序):

    ; WITH ORDERED_PAIRINGS AS (
        SELECT i.ITEMID, b.BUCKETID, ROW_NUMBER() OVER (ORDER BY i.SIZE, i.ITEMID, b.SIZE, b.BUCKETID) AS ORDERING, DENSE_RANK() OVER (ORDER BY b.SIZE, b.BUCKETID) AS BUCKET_ORDER, DENSE_RANK() OVER (PARTITION BY b.BUCKETID ORDER BY i.SIZE, i.ITEMID) AS ITEM_ORDER
        FROM @ITEM i
        JOIN @BUCKET b
          ON i.SIZE <= b.SIZE
    ), ITEM_PLACED AS (
        SELECT ITEMID, BUCKETID, ORDERING, BUCKET_ORDER, ITEM_ORDER, CAST(1 as int) AS SELECTION
        FROM ORDERED_PAIRINGS
        WHERE ORDERING = 1
        UNION ALL
        SELECT *
        FROM (
            SELECT op.ITEMID, op.BUCKETID, op.ORDERING, op.BUCKET_ORDER, op.ITEM_ORDER, CAST(ROW_NUMBER() OVER(ORDER BY op.BUCKET_ORDER) as int) as SELECTION
            FROM ORDERED_PAIRINGS op
            JOIN ITEM_PLACED ip
              ON op.ITEM_ORDER = ip.ITEM_ORDER + 1
             AND op.BUCKET_ORDER > ip.BUCKET_ORDER
        ) AS sq
        WHERE SELECTION = 1
    )
    SELECT *
    FROM ITEM_PLACED
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-20
      • 1970-01-01
      • 2018-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多