在SQL中映射弱相关表的字段

时间:2014-03-20 16:54:51

标签: sql database sorting mapping bucket-sort

我正在搜索一个SQL-Query,它可以将一个单独大小的项目映射到一个单独大小的存储桶。

我想满足以下条件:

  • 铲斗的大小必须大于或等于物品的大小。
  • 每个桶只能包含一个项目,或者它是空的。
  • 每个项目只能放在一个桶中。
  • 任何项目都不能拆分为多个存储桶。
  • 我想以某种方式填充存储桶,首先填充最小的未使用的存储桶。
  • 然后,初始项目和存储桶集可以按大小或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小提琴,上面是一个示例查询的给定测试集,它返回一个错误的结果,但是接近结果看起来像http://sqlfiddle.com/#!15/a6c30/1

3 个答案:

答案 0 :(得分:4)

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

我唯一的假设是 Bucket and 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

答案 1 :(得分:3)

我说单个SQL查询可能不是工作的工具,因为存储桶被消耗了&#34;通过分配项目给他们。您可以使用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);

答案 2 :(得分:2)

使用@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