【问题标题】:Join information from one table to another without joining twice cross apply T-SQL将信息从一个表连接到另一个表而不连接两次交叉应用 T-SQL
【发布时间】:2019-09-27 10:54:18
【问题描述】:

情况:

我有两张桌子。 #t1 有登录名和电子邮件。 #t2 具有与每封电子邮件相关联的国家/地区。 我想加入从#t2 到#t1 的信息,而不必加入两次。在内部或外部查询中只加入一次会破坏cross apply 逻辑。 我当前的查询使用交叉应用来获取滚动信息(下面的小提琴数据):

SELECT DISTINCT CAST(logins AS DATE) AS Dates,
    count(distinct d.email) AS DAU,
    count(distinct m.MAU) AS MAU
FROM #t1 d
CROSS APPLY (
            SELECT CAST(m.logins as date) as dates, m.email AS MAU
            FROM #t1 m
            WHERE m.logins BETWEEN d.logins and DATEADD(dd, 30, d.logins) 
            ) m
group by CAST(logins as date)

我发现在不中断交叉应用的情况下加入两个表的唯一方法是在外部和内部查询中使用inner join 它,这可能是错误的,但至少输出是正确的。我这样做是为了在内部查询的 where 语句中添加我的第二个条件。当我将逻辑应用于我的实际表时,性能很糟糕(下面的小提琴数据):

SELECT distinct CASt(logins AS DATE) AS Dates,
    #t2.country,
    count(distinct d.email) AS DAU,
    count(distinct m.MAU) AS MAU
FROM #t1 d
inner join #t2 on d.email=#t2.email 
CROSS APPLY (
            SELECT cast(m.logins as date) as dates, m.email AS MAU, country.country AS country
            FROM #t1 m
            inner join #t2 country on m.email=country.email         
            WHERE m.logins BETWEEN d.logins and DATEADD(dd, 30, d.logins)  
            and #t2.country = country.country
            ) m
group by cast(logins as date), #t2.country


+-------------+---------+-----+-----+
|    Dates    | country | DAU | MAU |
+-------------+---------+-----+-----+
|  2019-04-01 | france  |   1 |   2 |
|  2019-04-02 | france  |   1 |   2 |
|  2019-04-03 | france  |   1 |   2 |
|  2019-04-10 | france  |   1 |   1 |
|  2019-04-03 | italie  |   2 |   2 |
+-------------+---------+-----+-----+

目标:

我怎样才能找到一种方法来将信息从一个表连接到另一个表而不必加入两次。 (下面摆弄数据)

结果应该是这样的(上面第二个查询的输出):

  • DAU:每个国家/地区在“x”日发生了多少不同的登录
  • MAU:每个国家/地区在第 'x' 天之间发生了多少不同的登录 30 天后。

小提琴:

create table #t1 (email varchar(max), logins datetime)
insert into #t1 values 
('aa@gmail.com', '2019-04-01 00:00:00.000'),
('aa@gmail.com', '2019-04-02 00:00:00.000'), 
('aa@gmail.com', '2019-04-03 00:00:00.000'), 
('zz@gmail.com', '2019-04-10 00:00:00.000'), 

('cc@gmail.com', '2019-04-03 00:00:00.000'), 
('dd@gmail.com', '2019-04-03 00:00:00.000'), 
('dd@gmail.com', '2019-04-03 00:00:00.000')  

create table #t2 (country varchar(max), email varchar(max))
insert into #t2 values 
('france', 'aa@gmail.com'),
('france', 'zz@gmail.com'),
('italie', 'cc@gmail.com'),
('italie', 'dd@gmail.com')

【问题讨论】:

  • 请说明您希望整体查询做什么。 cross apply 在做什么?
  • @GordonLinoff 查询应该输出每天的每日登录和特定日期的每月登录(这两个指标的不同电子邮件)。交叉应用每天查看每个登录,并查看当天和 30 天后是否有任何登录发生,这就是我计算 MAU 的方式
  • 为什么不想“加入两次”?您有性能问题吗?
  • @iamdave 因为我无法加入一次,因为电子邮件和登录不会与国家/地区分组

标签: sql sql-server


【解决方案1】:

更新

所以我最初说第二个应该表现得更好,但我会吃那些话。在我的测试中,第一个效果更好。

在我的测试环境中,我将您的表格生成为永久表格,并首先使用分布在 206 个国家/地区的 100,000 个唯一电子邮件地址填充了 #t2(我称之为 emailLocation)。第二个表 (loginRecord) 填充了 2,000,000 个随机条目,分布在 2018 年 1 月 1 日至 2019 年 12 月 31 日之间。这两个表都有索引。

下面的查询基本上是我说的会慢的查询(不是)。这一个的主要区别是我正在过滤 CTE 中的日期以减少数据集。在我的环境中,这将在 20 秒内运行并返回 48,410 行。我没有测试返回整个集合需要多长时间,但是在我杀死它之前,尝试使用自加入的相同 CTE 运行了 10 分钟。

WITH joined AS
(
    SELECT
        t1.logins AS dates,
        t1.email,
        t2.country
        FROM loginRecord t1
        JOIN dbo.emailLocation t2 ON t2.email = t1.email
        WHERE t1.logins > GETDATE()
)
SELECT 
    dates,
    country,
    COUNT(DISTINCT(email)) AS DAU,
    (SELECT COUNT(DISTINCT(email)) FROM joined WHERE country = j.country AND dates BETWEEN j.dates AND DATEADD(DAY,30,j.dates)) AS MAU
FROM joined j
GROUP BY j.dates, j.country
ORDER BY country, dates

---原答案

感觉就像您坚持使用cross apply 逻辑。 这里有两个不使用cross apply 的选项。两者都使用 CTE 对您的临时表进行干净的分组,然后第一个选项是相关子查询 (blech),第二个选项是自联接。

这里是测试者:https://rextester.com/AVJS76389

WITH joined AS
(
    SELECT 
        t1.logins AS dates,
        t1.email,
        t2.country
        FROM #t1 t1
        JOIN #t2 t2 ON t2.email = t1.email
)
SELECT 
    dates,
    country,
    COUNT(DISTINCT(email)) AS DAU,
    (SELECT COUNT(DISTINCT(email)) FROM joined WHERE country = j.country AND dates BETWEEN j.dates AND DATEADD(DAY,30,j.dates)) AS MAU
FROM joined j
GROUP BY j.dates, j.country;



WITH joined AS
(
    SELECT 
        t1.logins AS dates,
        t1.email,
        t2.country
        FROM #t1 t1
        JOIN #t2 t2 ON t2.email = t1.email
)
SELECT 
    j1.dates,
    j1.country,
    COUNT(DISTINCT(j1.email)) AS DAU,
    COUNT(DISTINCT(j2.email)) AS MAU
FROM joined j1
JOIN joined j2
    ON j1.country = j2.country
    AND j2.dates BETWEEN j1.dates AND DATEADD(DAY,30,j1.dates)
GROUP BY j1.dates, j1.country 

【讨论】:

  • 我知道它有效,但该死的我的查询已经运行了 40 多万分钟,但仍然无法正常工作。
  • 我已经更新了我的答案,因为我错了。看看更新是否有帮助。
  • 我现在就试试。昨天其他查询根本不起作用。希望这有效!你不应该在 cte 中加上 distinct 吗?
  • 在 CTE 本身中,我们没有进行任何类型的聚合,我们只是使用它来识别与每个登录记录相关联的国家/地区。减少 CTE 中的数据集将是最有帮助的事情 - 例如,返回一个国家/地区的样本数据集中的所有数据大约需要一秒钟。
猜你喜欢
  • 2014-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-27
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多