反加入两个 table 并用另一个 table 上的日期替换 null
Anti-Join two tables and replace null with date on the other table
我有两个table
- 用户表
Username
User1
User2
User3
- 使用表
Username
Date
User1
1-2-22
User2
2-2-22
User1
3-2-22
User2
3-2-22
我需要谁没有明智地使用该工具。
预期输出:
Username
Date
User2
1-2-22
User3
1-2-22
User1
2-2-22
User3
2-2-22
User3
3-2-22
我尝试加入(右加入)table,但我得到的是正确的用户名,但不是日期(得到 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
您可以使用 anti-join:
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
这里的问题是您没有 table 日期。所以你需要自己生成。
这里有两种解决方案...要么你想找到指定范围内所有没有使用过的用户。或者您想要查找在有其他用户使用该系统的日子里没有使用的用户。
这可能令人困惑...但基本上...如果没有人使用 2022-02-01
,而您尝试使用 DISTINCT
来获取日期列表...那么您将 return 那天没有行,而您真正想要的是所有用户的列表。
我将根据我认为最有可能出现的情况提供答案,即查找在指定日期范围内未使用过的所有用户。
我做的第一件事是生成一个 table,其中包含我要检查的每一天的行。
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)
我知道这看起来很复杂,但这只是生成数字列表的常用方法,有时称为计数 table。然后我用它来生成一个范围内的所有日期。有些人喜欢使用系统tables。有很多方法可以做到。
主要思想是您只需要 table 和您可以使用的日期值。
那么查询就简单了...
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()
检查,它表示排除在使用 table.
中有该日期记录的任何用户
作为参考,这是我的示例数据设置查询:
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');
日期范围应首先在最小日期和最大日期之间导出,或者如果可以创建单独的日期 table。然后在日期和用户 table 之间进行笛卡尔乘积,并使用 Usage table 进行左连接,并在 where 子句中查找空值。我是这样做的:
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 table :
在 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);
我有两个table
- 用户表
Username |
---|
User1 |
User2 |
User3 |
- 使用表
Username | Date |
---|---|
User1 | 1-2-22 |
User2 | 2-2-22 |
User1 | 3-2-22 |
User2 | 3-2-22 |
我需要谁没有明智地使用该工具。
预期输出:
Username | Date |
---|---|
User2 | 1-2-22 |
User3 | 1-2-22 |
User1 | 2-2-22 |
User3 | 2-2-22 |
User3 | 3-2-22 |
我尝试加入(右加入)table,但我得到的是正确的用户名,但不是日期(得到 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
您可以使用 anti-join:
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
这里的问题是您没有 table 日期。所以你需要自己生成。
这里有两种解决方案...要么你想找到指定范围内所有没有使用过的用户。或者您想要查找在有其他用户使用该系统的日子里没有使用的用户。
这可能令人困惑...但基本上...如果没有人使用 2022-02-01
,而您尝试使用 DISTINCT
来获取日期列表...那么您将 return 那天没有行,而您真正想要的是所有用户的列表。
我将根据我认为最有可能出现的情况提供答案,即查找在指定日期范围内未使用过的所有用户。
我做的第一件事是生成一个 table,其中包含我要检查的每一天的行。
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)
我知道这看起来很复杂,但这只是生成数字列表的常用方法,有时称为计数 table。然后我用它来生成一个范围内的所有日期。有些人喜欢使用系统tables。有很多方法可以做到。
主要思想是您只需要 table 和您可以使用的日期值。
那么查询就简单了...
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()
检查,它表示排除在使用 table.
作为参考,这是我的示例数据设置查询:
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');
日期范围应首先在最小日期和最大日期之间导出,或者如果可以创建单独的日期 table。然后在日期和用户 table 之间进行笛卡尔乘积,并使用 Usage table 进行左连接,并在 where 子句中查找空值。我是这样做的:
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 table : 在 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);