[英]Join two tables and replace null with date on the other table
我有两张桌子
用户名 |
---|
用户 1 |
用户 2 |
用户 3 |
用户名 | 日期 |
---|---|
用户 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.