繁体   English   中英

连接两个表,并将 null 替换为另一个表上的日期

[英]Join two tables and replace null with date on the other table

我有两张桌子

  1. 用户表
用户名
用户 1
用户 2
用户 3
  1. 使用表
用户名 日期
用户 1 1-2-22
用户 2 2-2-22
用户 1 3-2-22
用户 2 3-2-22

我需要谁没有明智地使用该工具。

预期 output:

用户名 日期
用户 2 1-2-22
用户 3 1-2-22
用户 1 2-2-22
用户 3 2-2-22
用户 3 3-2-22

我尝试加入(右加入)表,但我得到了正确的用户名,但没有得到日期(得到 NULL)。

select a.username,b.username,b.date from
(select distinct date, b.username username
from UsageTable 
) b
right join
toolusers a
on
b.username = a.username

您可以使用反连接:

select u.usernamte, d.date
from usertable u
cross join (select distinct date as dt from usagetable) d
left join usagetable ut on ut.username = u.username and ut.date = d.dt
where ut.username is null
order by d.date, u.username

这里的问题是您没有仅包含日期的表格。 所以你需要自己生成。

这里有两种解决方案...您要查找所有在指定范围内没有使用过的用户。 或者您想查找在其他用户使用系统的日子里没有使用的用户。

这可能会令人困惑...但基本上...如果没有人在2022-02-01上使用过,并且您尝试使用DISTINCT来获取该日期列表...那么您将不会返回当天的任何行,当你真正想要的是所有用户的列表。

我将根据我认为最有可能的情况提供答案,即查找在指定日期范围内没有使用的所有用户。

我要做的第一件事是生成一个表格,其中包含我想要检查的每一天的一行。

DECLARE @DateRangeStart date = '2022-02-01',
        @DateRangeEnd   date = '2022-02-03';

-- FYI, this tally table generator code only produces 101 records total
IF OBJECT_ID('tempdb..#daterange','U') IS NOT NULL DROP TABLE #daterange; --SELECT * FROM #daterange
WITH c1 AS (SELECT x.x FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) x(x))           -- 10
    , c2(x) AS (SELECT 1 FROM c1 x CROSS JOIN c1 y)                                         -- 10 * 10
    , c3(rn) AS (SELECT 0 UNION ALL SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) FROM c2) -- Add zero record, and row numbers
SELECT DateValue = DATEADD(DAY, x.rn, @DateRangeStart)
INTO #daterange
FROM c3 x
WHERE x.rn <= DATEDIFF(DAY, @DateRangeStart, @DateRangeEnd)

我知道这看起来很复杂,但这只是生成数字列表的一种常用方法,有时称为计数表。 然后我用它来生成一个范围内的所有日期。 有些人喜欢使用系统表。 有很多方法可以做到这一点。

主要思想是您只需要一个包含可以使用的日期值的表格。

然后查询很简单...

SELECT u.Username, d.DateValue
FROM #User u    
    CROSS JOIN #daterange d
WHERE NOT EXISTS (SELECT * FROM #Usage ug WHERE ug.Username = u.Username AND ug.DateValue = d.DateValue)

我将我们的日期列表交叉加入到用户列表中。 这为我们提供了用户名 + 日期的所有可能组合。

然后我添加了NOT EXISTS()检查,它表示排除在使用表中具有该日期记录的任何用户。


作为参考,这是我的示例数据设置查询:

IF OBJECT_ID('tempdb..#User','U') IS NOT NULL DROP TABLE #User; --SELECT * FROM #User
CREATE TABLE #User (
    Username    varchar(20) NOT NULL,
);

INSERT INTO #User (Username)
VALUES ('User1'), ('User2'), ('User3')

IF OBJECT_ID('tempdb..#Usage','U') IS NOT NULL DROP TABLE #Usage; --SELECT * FROM #Usage
CREATE TABLE #Usage (
    Username    varchar(20) NOT NULL,
    DateValue   date        NOT NULL,
);

INSERT INTO #Usage (Username, DateValue)
VALUES ('User1', '2022-02-01'), ('User2', '2022-02-02'), ('User1', '2022-02-03'), ('User2', '2022-02-03');

日期范围应首先在最小日期和最大日期之间得出,或者如果可以创建单独的日期表。 然后在日期和用户表之间进行笛卡尔积,并与使用表进行左连接,并在 where 子句中查找 null 值。 我是这样做的:

create table UserTable(Username varchar(10));
create table UsageTable(Username varchar(10), UsageDate Date);

insert into UserTable values ('User1');
insert into UserTable values ('User2');
insert into UserTable values ('User3');

insert into UsageTable values ('User1','1-FEB-2022');
insert into UsageTable values ('User2','2-FEB-2022');
insert into UsageTable values ('User1','3-FEB-2022');
insert into UsageTable values ('User2','3-FEB-2022');

commit;

with rnge as (select min(UsageDate) min_date, max(UsageDate) max_date from UsageTable),
dt as (select generate_series(min_date,max_date,'1 day') as dt from rnge),
Usr as (select Username, dt from dt, UserTable)
select Usr.* from Usr left join UsageTable usg on usr.username = usg.username
and usr.dt = usg.UsageDate
where usg.username is null; 

注意:上面的 sql 在 postgres 中工作以生成日期范围。 但是,您可以使用下面的代码在 oracle 中生成日期范围。 用下面的一些更改替换 dt 表:在 oracle 中,这是生成日期范围的方式:

select
to_date('04-01-2016','dd-mm-yyyy') + lvl
from
(select level - 1 lvl
from
  dual
connect by
level <= (to_date('10-01-2015','dd-mm-yyyy') - to_date('04-01-2016','dd-mm-yyyy'))+ 1);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM