使用另一个表中的唯一值创建一个表

Posted

技术标签:

【中文标题】使用另一个表中的唯一值创建一个表【英文标题】:Create a table with unique values from another table 【发布时间】:2015-09-04 02:46:11 【问题描述】:

我正在使用 MS SQL Server Management Studio。我有桌子 -

+--------+----------+
| Num_ID | Alpha_ID |
+--------+----------+
|   1    |    A     |
|   1    |    B     |
|   1    |    C     |
|   2    |    B     |
|   2    |    C     |
|   3    |    A     |
|   4    |    C     |
|   5    |    A     |
|   5    |    B     |
+--------+----------+

我想从该表中创建另一个包含 2 列的表,以便 column_1 给出 Num_ID 中的唯一值(即 1、2、3、4 等),column_2 给出 Alpha_ID 中的唯一值(A、B、C 和很快)。

但如果一个字母表已经出现过,它就不应该再次出现。所以输出将是这样的 -

Col_1  Col_2
================
1     -    A
----------------
2     -    B
----------------
3      -   NULL (as A has been chosen by 1, it cannot occur next to 3)
----------------
4    -     C
----------------
5     -    NULL (both 5 A and 5 B cannot be chosen as A and B were picked up by 1 and 2) 
----------------

希望这是有道理的。 我想澄清一下,输入表中的 ID 不是我显示的数字,但 Num_ID 和 Alpha_ID 都是复杂的字符串。为了这个问题,我已将它们简化为 1,2,3,... 和 A,B,C ....

【问题讨论】:

可能想在此处处理您的格式。这是不可读的。 我开始编辑这个,但想不出一个描述性的名称,所以我继续前进。 我试图修正标题;如有错误,请指正。 @GordonLinoff 请看一下这个问题,谢谢。 使用 Microsoft SQL Server Management Studio 11.0.2100.60 【参考方案1】:

我认为没有光标就无法做到这一点。 我在您的示例数据中添加了几行,以测试它如何与其他案例一起使用。

逻辑很简单。首先获取Num_ID 的所有不同值的列表。然后遍历它们并在每次迭代中向目标表添加一行。要确定要添加的 Alpha_ID 值,我将使用 EXCEPT 运算符,它从源表中获取当前 Num_ID 的所有可用 Alpha_ID 值,并从中删除之前使用过的所有值。

可以在不使用显式变量@CurrAlphaID 的情况下编写INSERT,但使用变量看起来更简洁。

这里是SQL Fiddle。

DECLARE @TSrc TABLE (Num_ID varchar(10), Alpha_ID varchar(10));
INSERT INTO @TSrc (Num_ID, Alpha_ID) VALUES
('1', 'A'),
('1', 'B'),
('1', 'C'),
('2', 'B'),
('2', 'C'),
('3', 'A'),
('3', 'C'),
('4', 'A'),
('4', 'C'),
('5', 'A'),
('5', 'B'),
('5', 'C'),
('6', 'D'),
('6', 'E');

DECLARE @TDst TABLE (Num_ID varchar(10), Alpha_ID varchar(10));

DECLARE @CurrNumID varchar(10);
DECLARE @CurrAlphaID varchar(10);

DECLARE @iFS int;
DECLARE @VarCursor CURSOR;
SET @VarCursor = CURSOR FAST_FORWARD
FOR
    SELECT DISTINCT Num_ID
    FROM @TSrc
    ORDER BY Num_ID;

OPEN @VarCursor;

FETCH NEXT FROM @VarCursor INTO @CurrNumID;
SET @iFS = @@FETCH_STATUS;
WHILE @iFS = 0
BEGIN

    SET @CurrAlphaID = 
    (
        SELECT TOP(1) Diff.Alpha_ID
        FROM
            (
                SELECT Src.Alpha_ID
                FROM @TSrc AS Src
                WHERE Src.Num_ID = @CurrNumID

                EXCEPT

                SELECT Dst.Alpha_ID
                FROM @TDst AS Dst
            ) AS Diff
        ORDER BY Diff.Alpha_ID
    );

    INSERT INTO @TDst (Num_ID, Alpha_ID) 
    VALUES (@CurrNumID, @CurrAlphaID);

    FETCH NEXT FROM @VarCursor INTO @CurrNumID;
    SET @iFS = @@FETCH_STATUS;
END;

CLOSE @VarCursor;
DEALLOCATE @VarCursor;

SELECT * FROM @TDst;

结果

Num_ID    Alpha_ID
1         A
2         B
3         C
4         NULL
5         NULL
6         D

在源表上的(Num_ID, Alpha_ID) 上有索引会有所帮助。在目标表上对(Alpha_ID) 进行索引也会有所帮助。

【讨论】:

我认为也许我们可以使用递归 CTE,以便我们可以跟踪我们选择时已经采用了哪些 alpha 值,但是您不能在子选择中使用递归,因此很遗憾它不起作用。我想 CURSOR 是唯一的选择。 @plalx,也许不用游标也可以,但肯定需要自连接,可能不止一次。结果性能可能很差,可能比直接游标更差。而且很可能很难理解逻辑并在以后维护此代码。有时光标很有用。 我认为这是可以接受CURSOR 的地方。赞一个!【参考方案2】:

我想我不是通过递归(光标或一段时间)做了一些事情

首先,我创建了一个包含行的表。

create table #tmptest
(
    Num_ID int
    , Alpha_ID varchar(50)
)

insert into #tmptest (Num_ID, Alpha_ID) values
(1,'A'),
(1,'B'),
(1,'C'),
(2,'B'),
(2,'C'),
(3,'A'),
(4,'C'),
(5,'A'),
(5,'B')

// this one, with row column
SELECT
    ROW_NUMBER() OVER (PARTITION BY Num_ID ORDER BY Num_ID ASC) as row
    , *
INTO #tmp_withrow
FROM #tmptest

这些就是结果

最后,我做了一个内部查询(可能是左连接或更好)。

SELECT DISTINCT
    Num_ID
    , (
        SELECT 
            TOP 1
                Alpha_ID
        FROM #tmp_withrow in1
        WHERE
            in1.Num_ID = t.Num_ID
        AND in1.Alpha_ID NOT IN (
            SELECT
                Alpha_ID 
            FROM #tmp_withrow in2
            WHERE
                in2.Num_ID < in1.Num_ID
            AND in2.row = 1
        )
        ORDER BY in1.Num_ID ASC
    ) AS [NonRepeatingAlpha]
from #tmptest t

这些就是结果

注意:我创建了一个标志 (row),它允许您查询所有小于您所在 ID (in2.Num_ID &lt; in1.Num_ID) 的字母,然后找出已使用的字母 (in2.row = 1),然后选择/ 避免其他 Num_ID 中已经使用过的所有字母(

WHERE in1.Num_ID = t.Num_ID 
  AND in1.Alpha_ID NOT IN (
    SELECT
        Alpha_ID 
    FROM #tmp_withrow in2
    WHERE
        in2.Num_ID < in1.Num_ID
        AND in2.row = 1

)

我希望这会有所帮助。谢谢!

【讨论】:

我没有详细查看您的查询,但将(3,'C') 添加到您的测试数据中,很遗憾您会看到结果不正确。 是的,我很抱歉,但我会回来的! :)

以上是关于使用另一个表中的唯一值创建一个表的主要内容,如果未能解决你的问题,请参考以下文章

一个表中的记录对另一个表中的记录是唯一的

根据带有参数的选择(唯一键)从另一个表中插入值

oracle 唯一索引,唯一约束,主键之间的联系

约束 CONSTRAINT

创建触发器的问题。每个表中的列名必须是唯一的。微软SQL

用另一个表中的最新值更新