【问题标题】:How to query for missing fields and associations and return what's missing in PostgreSQL?如何查询缺少的字段和关联并返回 PostgreSQL 中缺少的内容?
【发布时间】:2021-08-17 09:54:40
【问题描述】:

在多年使用 Rails 和其他 ORM 之后,我正在学习更多原始 SQL,因此有很多方法可以学习如何有效地执行复杂的查询。我在这里想知道的是_如何找到所有缺少某些字段和关联的用户,并返回他们缺少的字段/关联。

我对如何用 SQL 编写这个有一个粗略的想法,但并不准确(对于 PostgreSQL)。

我有类似这样的数据模型、用户表、“社交媒体链接”表以及将链接映射到用户的关联,因此用户可以拥有许多社交媒体链接,但可能还有更多超过一个用户与一个链接相关联(即组织):

CREATE TABLE users (
  id INT GENERATED BY DEFAULT AS IDENTITY,
  slug VARCHAR(255) NOT NULL,
  name VARCHAR(255),
  description TEXT,
  PRIMARY KEY (id)
)

CREATE TABLE sociallinks (
  id INT GENERATED BY DEFAULT AS IDENTITY,
  type VARCHAR(255),
  value TEXT,
  PRIMARY KEY (id)
)

CREATE TABLE usersociallinks (
  id INT GENERATED BY DEFAULT AS IDENTITY,
  user_id INTEGER REFERENCES users;
  sociallink_id INTEGER REFERENCES sociallinks,
  PRIMARY KEY (id)
)

问题是,您如何执行查询(使用此伪代码):

select from users
join on usersociallinks.user_id = users.id
join on sociallinks.id = usersociallinks.id
where name = null
  or description = null
  or missing linkedin? (sociallinks.type == linkedin)
  or missing facebook? (sociallinks.type == facebook)
return slug from users table
return has_name = false if name is null from users table
return has_description = false if description is null from users table
return has_linkedin = false if linkedin is null from sociallinks table
return has_facebook = false if facebook is null from sociallinks table

在自然语言中,“选择没有姓名或描述,或者缺少linkedin或facebook链接(sociallinks.value)的用户,并返回他们缺少的字段”。

我可以用幼稚、冗长和复杂的方式来做这件事,或者一次查询一件事,但我想知道如何有效地做到这一点,可能只需要一个查询(或尽可能少的查询)。

SELECT * FROM users
WHERE name IS NULL
  OR description IS NULL
LIMIT 1

获取这些,然后执行:

const record = await knex.raw(SQL)
const output = {}
if (!record.name) output.hasName = false
if (!record.description) output.hasDescription = false
return output

然后下一个查询更复杂,但我会一次做一个。

您如何在尽可能少的查询中高效地做到这一点?每次查询限制为 100 个用户。

输入数据为:

users:
id,slug,name,description
1,foo,Foo,Im a description
2,bar,,Im a description too
3,baz,,
4,hello,Hello,
5,world,,
6,food,Foo,Im a descriptiond
7,bard,,Im a description tood
8,bazd,asdf,fdsa
9,hellod,,
10,worldd,,A worldd description

sociallinks:
id,type,value
1,facebook,foo
2,facebook,bar
3,facebook,baz
4,facebook,hello
5,facebook,world
6,linkedin,foo
7,linkedin,bar
8,linkedin,baz
9,linkedin,hello
10,linkedin,world

usersociallinks:
id,user_id,sociallink_id
1,1,1
2,2,2
3,2,6
4,3,7
5,5,3
6,8,4
7,8,8
8,9,9

输出数据为:

user_id,slug,has_name,has_description,has_linkedin,has_facebook
1,foo,true,true,false,true
2,bar,false,true,true,true
3,baz,false,false,true,false
4,hello,true,false,false,false
5,world,false,false,false,true
6,food,true,true,false,false
7,bard,false,true,false,false
// 8 has everything so it is missing
9,hellod,false,false,true,false
10,worldd,false,true,false,false

【问题讨论】:

  • 伪代码没有多大帮助。对于纯 SQL 问题,显示输入和输出表数据会更有帮助。
  • 解决办法是(not) EXISTS(...)
  • 您想要的结果将链接类型硬编码到列名中。这是否意味着存在并且总是只有两种链接类型?如果您需要它来处理多种和/或不断增长的链接类型,我建议每个缺少(标准化)的用户/类型组合一个输出行。
  • @MatBailie 我不太完全理解那会是什么样子,但可以随意更改输出以使其更有意义。这正是某种形式所需要的。谢谢。
  • @MatBailie 哦,抱歉,假设可能有 100 种不同的类型,而不仅仅是两种。总数未知,但不会太大。

标签: sql postgresql join relational-division


【解决方案1】:

EXISTS() 救援:


SELECT u.id, u.slug
        , (u.name > '' ) AS has_name 
        , (u.description> '' ) AS has_description
        , EXISTS(SELECT 1 FROM usersociallinks sl JOIN sociallinks s ON s.id = usl.sociallink_i
                 WHERE sl.user_id = u.id AND s.type = 'facebook') AS has_facebook
        , EXISTS(SELECT 1 FROM usersociallinks sl JOIN sociallinks s ON s.id = usl.sociallink_i
                 WHERE sl.user_id = u.id AND s.type = 'linkedin') AS has_linkedin
FROM users u
ORDER BY u.id
        ;

【讨论】:

    【解决方案2】:

    您可以加入表格并使用条件聚合:

    SELECT u.id, u.slug, 
           MAX(name) IS NOT NULL has_name, 
           MAX(description) IS NOT NULL has_description, 
           MAX(CASE WHEN s.type = 'linkedin' THEN 1 ELSE 0 END)::boolean has_linkedin, 
           MAX(CASE WHEN s.type = 'facebook' THEN 1 ELSE 0 END)::boolean has_facebook
    FROM users u
    LEFT JOIN usersociallinks usl ON usl.user_id = u.id
    LEFT JOIN sociallinks s ON s.id = usl.sociallink_id AND s.type IN ('facebook', 'linkedin')
    GROUP BY u.id, u.slug
    ORDER BY u.id
    

    请参阅demo

    【讨论】:

    • 哇,太酷了,谢谢!现在可以从中学到很多东西。
    【解决方案3】:

    选择没有姓名或描述,或者缺少linkedin或facebook链接(sociallinks.value)的用户,并返回他们缺少的字段

    我想两个left joins:

    with usl as (
          select usl.*, sl.type
          from usersociallinks usl join
               sociallinks sl
               on sl.id = usl.sociallink_id
         )
    select concat_ws(',',
                     (case when u.name is null then 'name' end),
                     (case when u.description is null then 'description' end),
                     (case when usl_f.user_id is null then 'facebook' end),
                     (case when usl_l.user_id is null then 'linkedin' end)
                    ) as missing_values
    from users u left join
         usl usl_f
         on usl_f.user_id = u.id and usl_f.value = 'facebook' left join
         usl usl_l
         on usl_l.user_id = u.id and usl_l.value = 'linkedin'
    where u.name is null or u.description is null or
          usl_f.user_id is null or usl_l.user_id is null
    
     
    

    【讨论】:

    • 哦,哇,这太聪明了,我从来没有考虑过这些可能性,谢谢!哇,这怎么解决的,case when u.name 出现在变量 from users u 定义之前,等等……但那是另一回事了哈哈。
    • 你的意思是usl_f.user_id而不是usl_f_user_id等吗?
    • @LancePollard 。 . .是的。我修正了错字。
    猜你喜欢
    • 1970-01-01
    • 2020-05-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多