【问题标题】:SQL - get default NULL value if data is not availableSQL - 如果数据不可用,则获取默认 NULL 值
【发布时间】:2023-03-24 13:06:01
【问题描述】:

我得到一个表格数据如下:

ID  | TYPE_ID | CREATED_DT | ROW_NUM  
=====================================
123 |   485   | 2019-08-31 | 1
123 |   485   | 2019-05-31 | 2
123 |   485   | 2019-02-28 | 3
123 |   485   | 2018-11-30 | 4
123 |   485   | 2018-08-31 | 5
123 |   485   | 2018-05-31 | 6
123 |   487   | 2019-05-31 | 1
123 |   487   | 2018-05-31 | 2

我想为每个 TYPE_ID 选择 6 个 ROW_NUM,如果缺少数据,我需要为 CREATED_DT 返回 NULL 值,最终结果集应如下所示:

ID  | TYPE_ID | CREATED_DT | ROW_NUM  
=====================================
123 |   485   | 2019-08-31 | 1
123 |   485   | 2019-05-31 | 2
123 |   485   | 2019-02-28 | 3
123 |   485   | 2018-11-30 | 4
123 |   485   | 2018-08-31 | 5
123 |   485   | 2018-05-31 | 6
123 |   487   | 2019-05-31 | 1
123 |   487   | 2018-05-31 | 2
123 |   487   | NULL       | 3
123 |   487   | NULL       | 4
123 |   487   | NULL       | 5
123 |   487   | NULL       | 6

查询:

SELECT 
   A.*
FROM TBL AS A 
WHERE A.ROW_NUM <= 6
UNION ALL
SELECT 
   B.*
FROM TBL AS B 
WHERE B.ROW_NUM NOT IN (SELECT ROW_NUM FROM TBL)
      AND B.ROW_NUM <= 6

我尝试使用 UNION ALL 和 ISNULL 回填不可用的数据,但它仍然给我现有的数据,但不是预期的结果。我认为这可以通过使用 CTE 以简单的方式完成,但不确定如何使其工作。在这方面可以帮助我吗?

【问题讨论】:

  • 对于带有空CREATED_DTTYPE_IDs 是否确实存在带有ROW_NUM 的条目?
  • 不,在这种情况下,ROW_NUM 不存在,我需要创建不存在的 ROW_NUM,并且默认 CREATED_DT 为 NULL,并且每个 TYPE_ID 的 ROW_NUM 始终为
  • 只是为了确定,ROW_NUM 是表格的一部分,对吗?
  • 我正在使用 ROW_NUMBER() 生成它们,我发布的表是我使用 ROW_NUMBER() 逻辑后得到的临时表结果
  • @CaitLAN Jenner 有什么帮助吗?

标签: sql sql-server-2008 select common-table-expression union-all


【解决方案1】:

用一个系列创建一个 cte 并交叉应用它

   CREATE TABLE Test (
                            ID INT,
                            TYPE_ID INT,
                            CREATED_DT DATE  
                        )

    INSERT INTO Test(ID, TYPE_ID, CREATED_DT)
    VALUES
        (123,485,'2019-08-31')
        ,(123,485,'2019-05-31')
        ,(123,485,'2019-02-28')
        ,(123,485,'2018-11-30')
        ,(123,485,'2018-08-31')
        ,(123,485,'2018-05-31')
        ,(123,487,'2019-05-31')
        ,(123,487,'2018-05-31')
;


    WITH n(n) AS
    (
        SELECT 1
        UNION ALL
        SELECT n+1 FROM n WHERE n < 6
    )

    ,id_n as (
                SELECT 
                DISTINCT 
                ID
                ,TYPE_ID
                ,n

                FROM 
                Test
                cross apply n
             )

    SELECT 
        id_n.ID
        ,id_n.TYPE_ID
        ,test.CREATED_DT
        ,id_n.n row_num

    FROM 
        id_n 
        left join 
                    (
                        select 
                            ID
                            ,TYPE_ID
                            ,CREATED_DT

                            ,ROW_NUMBER() over(partition by id, type_id order by created_dt) rn 

                        from    
                        Test 

                    ) Test on Test.ID = id_n.ID and Test.TYPE_ID = id_n.TYPE_ID and id_n.n = test.rn


    drop table Test

【讨论】:

    【解决方案2】:

    请参考其他答案,了解如何使用 CROSS JOIN 执行此操作 - 这非常简洁。或者,我们可以利用 MS-SQL 中可用的编程逻辑来实现所需的结果。以下方法在 SQL 游标中存储不同的 IDTYPE_ID 组合。然后它遍历游标条目以确保将适当数量的数据存储到临时表中。最后,在临时表上执行SELECT,并关闭游标。这是我在https://rextester.com/l/sql_server_online_compiler 上验证的概念证明。

    -- Create schema for testing
    CREATE TABLE Test (
        ID INT,
        TYPE_ID INT,
        CREATED_DT DATE  
    )
    
    -- Populate data
    INSERT INTO Test(ID, TYPE_ID, CREATED_DT)
    VALUES
    (123,485,'2019-08-31')
    ,(123,485,'2019-05-31')
    ,(123,485,'2019-02-28')
    ,(123,485,'2018-11-30')
    ,(123,485,'2018-08-31')
    ,(123,485,'2018-05-31')
    ,(123,487,'2019-05-31')
    ,(123,487,'2018-05-31');
    
    -- Create TempTable for output
    CREATE TABLE #OutputTable (
        ID INT,
        TYPE_ID INT,
        CREATED_DT DATE,
        ROW_NUM INT
    )
    
    -- Declare local variables
    DECLARE @tempID INT, @tempType INT;
    
    -- Create cursor to iterate ID and TYPE_ID
    DECLARE mycursor CURSOR FOR (
        SELECT DISTINCT ID, TYPE_ID FROM Test
        );
    OPEN mycursor
    
    -- Populate cursor
    FETCH NEXT FROM mycursor
    INTO @tempID, @tempType;
    
    -- Loop
    WHILE @@FETCH_STATUS = 0
    BEGIN
        DECLARE @count INT = (SELECT COUNT(*) FROM Test WHERE ID = @tempID AND TYPE_ID = @tempType);
    
        INSERT INTO #OutputTable (ID, TYPE_ID, CREATED_DT, ROW_NUM)
        SELECT ID, TYPE_ID, CREATED_DT, ROW_NUMBER() OVER(ORDER BY ID ASC) 
        FROM Test
        WHERE ID = @tempID AND TYPE_ID = @tempType;
    
        WHILE @count < 6
        BEGIN
            SET @count = @count + 1
            INSERT INTO #OutputTable
            VALUES (@tempID, @tempType, NULL, @count);
        END
    
        FETCH NEXT FROM mycursor
        INTO @tempID, @tempType;
    END
    
    -- Close cursor
    CLOSE mycursor;
    
    -- View results
    SELECT * FROM #OutputTable;
    

    请注意,如果您有一个实例,其中 IDTYPE_ID 的唯一组合被分组超过 6 次,则额外的分组将包含在您的最终结果中。如果您必须只显示 6 个分组,您可以将查询的那部分更改为 SELECT TOP 6 ...

    【讨论】:

    • 感谢您的快速回复
    【解决方案3】:
    • 假设 Row_Num 至少有记录,至少有所有 6 行... tbl 中的 1,2,3,4,5,6 并且没有分数或 0 或负数...
    • 我们得到了所有不同类型 ID 和 ID 的列表。 (别名 A)
    • 然后我们得到一个行号小于 7 的不同列表(给我们 6 条记录)
    • 我们交叉连接这些以确保每个 ID 和 Type_ID 都有所有 6 行。
    • 然后我们将 join 保留在基本集 (tbl) 中以获取所有需要的日期;存在此类日期的地方。由于我们使用左连接,因此没有日期的行仍将保留。

    .

    SELECT A.ID, A.Type_ID, C.Created_DT, B.Row_Num
    FROM (SELECT DISTINCT ID, Type_ID FROM tbl) A
    CROSS JOIN (SELECT distinct row_num from tbl where Row_num < 7) B
    LEFT JOIN tbl C
      on C.ID = A.ID 
     and C.Type_ID = A.Type_ID
     and C.Row_num = B.Row_num
    

    给我们:

    +----+-----+---------+------------+---------+
    |    | ID  | Type_ID | Created_DT | Row_Num |
    +----+-----+---------+------------+---------+
    |  1 | 123 |     485 | 2019-08-31 |       1 |
    |  2 | 123 |     485 | 2019-05-31 |       2 |
    |  3 | 123 |     485 | 2019-02-28 |       3 |
    |  4 | 123 |     485 | 2018-11-30 |       4 |
    |  5 | 123 |     485 | 2018-08-31 |       5 |
    |  6 | 123 |     485 | 2018-05-31 |       6 |
    |  7 | 123 |     487 | 2019-05-31 |       1 |
    |  8 | 123 |     487 | 2018-05-31 |       2 |
    |  9 | 123 |     487 | NULL       |       3 |
    | 10 | 123 |     487 | NULL       |       4 |
    | 11 | 123 |     487 | NULL       |       5 |
    | 12 | 123 |     487 | NULL       |       6 |
    +----+-----+---------+------------+---------+
    

    雷克斯测试员:Example

    这还假设您希望 type_id 和 ID 的每种组合都使用 1-6 个。如果 ID 无关紧要,则只需将其从加入条件中排除。我把它包括在内,因为它是一个 ID,而且看起来像是钥匙的一部分。

    【讨论】:

    • 问题是 OP 说 Row_Num 自然不是表的一部分,它是由 ROW_NUMBER() 函数生成的。但是,如果您考虑到这一点,您仍然可以使这项技术发挥作用。我喜欢 CROSS JOIN 的用法 - 我一开始尝试过,但无法正常工作。
    • 很容易通过第二个 CTE 获取 row_num... 然后使用第二个 CTE 作为源... 目的是提供帮助;没有给出完整的解决方案:P 关键是 Row_num 1-6 必须存在于至少 1 条记录的相同数据中。如果不;你永远不会得到 6... 你会得到表中可用的最大值..
    • 很好的例子!我学到了一些新东西。由于我花了一段时间来解决这个问题,所以我将留下我的答案,它提出了一种不同的,尽管可能过于复杂的方法来解决这个问题。
    • 我也会。总是有不止一种方法。并非所有答案都适用于所有情况。因此,替代方案可能会帮助其他人;这就是为什么我发布这个(希望更简单)的答案。我还必须做出很多假设;我认为您不必做出回应
    猜你喜欢
    • 2021-10-05
    • 1970-01-01
    • 2018-06-20
    • 2023-03-03
    • 2014-12-31
    • 2020-02-05
    • 2013-05-12
    • 2022-06-19
    • 1970-01-01
    相关资源
    最近更新 更多