选择列为真,如果在这种情况下有任何其他行为真?

huangapple go评论62阅读模式
英文:

how to select column true if any other row is true in this situation?

问题

I'm here to assist with the translation. Here's the translated content:

我正在尝试弄清楚如何在查询中的其他列为true时将列设置为true。

我有两个表,一个叫做products,另一个叫做families。一个family可以有很多products,而一个product可以有一个main product。

当我执行我的选择时,我应该:

  • 所有没有family的产品
  • 所有有family的产品
  • 所有有family的产品并且是主产品(如果产品是主产品,我不需要恢复该家庭的其他产品,只需要主产品)

我的问题是我有一个叫做product_launch的列,当产品分开时恢复产品的时间很容易。因此,我正在尝试设置返回查询中的主产品的字段,遵循以下规则:如果这个family的任何产品都有一个标志product_launch为true,即使主产品为false,我也应该将主产品的product_launch列设置为true。

在这个fiddle中,我有一个我正在尝试做的示例。

我已经尝试使用任何、over/window和聚合函数,但都没有能够达到我的目标。

我的最终目标是在这个示例结束时返回例如Playstation 3的product_launch为true,因为它是主产品,同一家庭中的PlayStation 5也有product_launch为true。在其他情况下,它已经可以工作,因为它们分开。

在这个示例中,我有以下规则:

  • Pepsi和Cocacola应该返回,因为它们没有family。
  • Macbook 2015和macbook 2018属于家庭(Macbooks),但它们中的任何一个都不是主产品,所以我应该在选择中收到它们。
  • 家庭游戏机中唯一可能返回的产品是Playstation 3,因为它是家庭游戏机中的主产品。但它需要将标志product_launch更改为true,因为在家庭(游戏机)中有一个产品(Playstation 5)具有此标志为true。如果家庭中有一些产品的标志product_launch为true,即使主产品的标志product_launch为false,我也应该返回具有此标志为true的主产品。

希望有人可以帮助我了解在Postgresql中要达到这个目标应该做什么。谢谢!

英文:

I'm trying to figure out how to set a column to true when other columns from the query be true.

I have two tables called products and families. A family can have a lot of products and a product can have a main product.

When I execute my select, I should:

  • All products that don't have a family
  • All products that have a family
  • All products that have a family and is a main product (if the product is main product, i don't need to recover others product from this family, only the main product)

My problem is that a have a column called product_launch and it's easy to recover when the product came separately. So I'm trying to set a field of the main product that returns in my query a following rule: If any product of this family has a flag product_launch as true I should set the main product product_launch column to true even if the main product is false.

In this fiddle I have an example that I'm trying to do.

I already tried using any, over/window, and aggregate functions but nothing was able to reach my objective.

My goal at the end of this example is to return for example Playstation 3 with product_launch as true because it's a main product and PlayStation 5 which is in the same family has product_launch true. In the other cases, it's already working because it brings separately.
选择列为真,如果在这种情况下有任何其他行为真?

In this example I have the following rules:

  • Pepsi and Cocacola should return because they dont have family.
  • Macbook 2015 and macbook 2018 belogs to family (Macbooks) but any of them is a main product so I should receive them in select.
  • Family Consoles the only product that could return is playstation 3 because it is main product from Family Consoles. But it need to change the flag product_launch to true because in family (Consoles) there is a product (Playstation 5) that have this flag true.
    If I have some product in family that have flag product_launch as true I should return main product with this flag true even if the main product has flag product_launch as false.

Hope someone can help me to know what to do in Postgresql to reach this goal. Thanks!

答案1

得分: 2

I'm not sure I fully understand what you want. I added a "playstation 4" row that's not the main_product, so its product_launch value is not changed. If this isn't what you want the other answers are probably easier.

Query #1

SELECT p.id, p.name, p.family_id, p.enabled
, CASE
-- We're on the family's main product, so we can check if any other products in the family are "product_launch"
WHEN p.id = f.main_product_id THEN bool_or(product_launch) OVER (PARTITION BY family_id)
ELSE product_launch
END AS product_launch
, f.id, f.name, f.main_product_id
from products as p
left outer join families as f
on p.family_id = f.id
ORDER BY family_id, p.id;

id name family_id enabled product_launch id name main_product_id
5 mac book 2015 1 true false 1 macbooks
6 mac book 2018 1 true false 1 macbooks
1 playstation 5 2 true true 2 consoles 2
2 playstation 3 2 true true 2 consoles 2
3 playstation 4 2 true false 2 consoles 2
4 xbox 360 2 true false 2 consoles 2
7 Coca cola true false
8 Pepsi true false

View on DB Fiddle

Edit:

Updating the answer to remove unwanted rows from the final select:

SELECT *
FROM (
  	SELECT p.id, p.name, p.family_id, p.enabled
       	 , CASE
             -- We're on the family's main product, so we can check if any other products in the family are "product_launch"
             WHEN p.id = f.main_product_id THEN bool_or(product_launch) OVER (PARTITION BY family_id)
	         ELSE product_launch
	       END AS product_launch
	     , f.id AS f_id, f.name, f.main_product_id
	from products as p
	left outer join families as f
	  on p.family_id = f.id
) AS sub
WHERE "enabled"
  AND ((id = main_product_id)	OR (main_product_id is null))
英文:

I'm not sure I fully understand what you want. I added a "playstation 4" row that's not the main_product, so its product_launch value is not changed. If this isn't what you want the other answers are probably easier.

Query #1

SELECT p.id, p.name, p.family_id, p.enabled
     , CASE
         -- We're on the family's main product, so we can check if any other products in the family are "product_launch"
         WHEN p.id = f.main_product_id THEN bool_or(product_launch) OVER (PARTITION BY family_id)
         ELSE product_launch
       END AS product_launch
     , f.id, f.name, f.main_product_id
from products as p
left outer join families as f
  on p.family_id = f.id
ORDER BY family_id, p.id;
id name family_id enabled product_launch id name main_product_id
5 mac book 2015 1 true false 1 macbooks
6 mac book 2018 1 true false 1 macbooks
1 playstation 5 2 true true 2 consoles 2
2 playstation 3 2 true true 2 consoles 2
3 playstation 4 2 true false 2 consoles 2
4 xbox 360 2 true false 2 consoles 2
7 Coca cola true false
8 Pepsi true false

View on DB Fiddle


Edit:

Updating the answer to remove unwanted rows from the final select:

SELECT *
FROM (
  	SELECT p.id, p.name, p.family_id, p.enabled
       	 , CASE
             -- We're on the family's main product, so we can check if any other products in the family are "product_launch"
             WHEN p.id = f.main_product_id THEN bool_or(product_launch) OVER (PARTITION BY family_id)
	         ELSE product_launch
	       END AS product_launch
	     , f.id AS f_id, f.name, f.main_product_id
	from products as p
	left outer join families as f
	  on p.family_id = f.id
) AS sub
WHERE "enabled"
  AND ((id = main_product_id)	OR (main_product_id is null))

答案2

得分: 1

以下是您要翻译的内容:

我不确定这是否是您想要的,但请尝试这个:

WITH familes_with_one_product_launched AS
(
  SELECT 
      DISTINCT family_id 
  FROM products 
  WHERE
      product_launch = TRUE
), main_products AS
(
	SELECT 
  		p.id 
  	FROM products as p 
  	INNER JOIN families AS f ON (f.main_product_id = p.id)
)
SELECT
	id, name, family_id, enabled,
    CASE WHEN is_main_product AND family_has_launched_product 
    	THEN TRUE
        ELSE product_launch
    END AS product_launch
FROM
(
  SELECT 
      p.*,
      CASE WHEN mp.id IS NULL THEN FALSE ELSE TRUE END AS is_main_product,
      CASE WHEN fl.family_id IS NULL THEN FALSE ELSE TRUE END AS family_has_launched_product
  FROM products AS p
      LEFT JOIN main_products AS mp ON (p.id = mp.id)
      LEFT JOIN familes_with_one_product_launched AS fl ON (p.family_id = fl.family_id)
) AS x
英文:

I'm not sure, if this is want you want to get, but try this:

WITH familes_with_one_product_launched AS
(
  SELECT 
      DISTINCT family_id 
  FROM products 
  WHERE
      product_launch = TRUE
), main_products AS
(
	SELECT 
  		p.id 
  	FROM products as p 
  	INNER JOIN families AS f ON (f.main_product_id = p.id)
)
SELECT
	id, name, family_id, enabled,
    CASE WHEN is_main_product AND family_has_launched_product 
    	THEN TRUE
        ELSE product_launch
    END AS product_launch
FROM
(
  SELECT 
      p.*,
      CASE WHEN mp.id IS NULL THEN FALSE ELSE TRUE END AS is_main_product,
      CASE WHEN fl.family_id IS NULL THEN FALSE ELSE TRUE END AS family_has_launched_product
  FROM products AS p
      LEFT JOIN main_products AS mp ON (p.id = mp.id)
      LEFT JOIN familes_with_one_product_launched AS fl ON (p.family_id = fl.family_id)
) AS x

答案3

得分: 1

以下是翻译好的内容:

原始表设计存在一些问题,其中最麻烦的问题是“families”和“products”相互引用。从“families”到“products”的外键关联未在DDL中声明,但在查询中明显可见。由于这个设计引起的问题之一是无法使用约束来确保“families.main_product_id”和“products.family_id”相互一致。我通过从“families”中移除“main_product_id”并向“products”添加一个名为“is_main_product”的布尔标志来重构了表。部分唯一索引强制执行了每个家庭只能有一个主要产品的约束。

我还进行了以下不与原始问题相关但改进了模型的更改:

  1. 向“name”列添加了唯一约束。
  2. 使布尔列为“NOT NULL”,并具有默认值。

布尔列的更改有助于在评估逻辑表达式时避免处理“NULL”时产生的额外复杂性。

以下是修改后的表创建语句:

CREATE TABLE families(
  id serial PRIMARY KEY,
  name varchar(50) UNIQUE NOT NULL
);

CREATE TABLE products(
  id serial PRIMARY KEY,
  name varchar(50) UNIQUE NOT NULL,
  family_id int REFERENCES families(id),
  enabled boolean NOT NULL DEFAULT FALSE,
  product_launch boolean NOT NULL DEFAULT FALSE,
  is_main_product boolean NOT NULL DEFAULT FALSE,
  -- 主要产品必须有一个家庭
  CONSTRAINT products_ck CHECK (family_id IS NOT NULL OR is_main_product = FALSE)
);

-- 一个家庭只能有一个主要产品
CREATE UNIQUE INDEX products_main_family_product_idx ON products(family_id)
  WHERE is_main_product = TRUE;

请注意,使用索引来强制执行一个家庭只能有一个主要产品的规则会导致一个问题:更改主要产品需要两次更新,一次用于将当前主要产品的“is_main_product”设置为false,另一次用于将新主要产品的“is_main_product”设置为true。由于这两次更新可以在同一个事务中发生,这应该是一个小的不便。

我重新设计了插入操作,以消除对生成的“id”值的显式依赖。这有助于确保“id”值和相关的序列同步。

INSERT INTO families(name)
VALUES ('macbooks'),
       ('consoles');

WITH p(
  name,
  family_name,
  enabled,
  product_launch,
  is_main_product
) AS (
  VALUES ('playstation 5', 'consoles', TRUE, TRUE, FALSE),
         ('playstation 3', 'consoles', TRUE, FALSE, TRUE),
         ('xbox 360', 'consoles', TRUE, FALSE, FALSE),
         ('mac book 2015', 'macbooks', TRUE, FALSE, FALSE),
         ('mac book 2018', 'macbooks', TRUE, FALSE, FALSE),
         ('Coca cola', NULL, TRUE, FALSE, FALSE),
         ('Pepsi', NULL, TRUE, FALSE, FALSE)
)
INSERT INTO products(name, family_id, enabled, product_launch, is_main_product)
SELECT
  p.name,
  f.id,
  p.enabled,
  p.product_launch,
  p.is_main_product
FROM
  p
  LEFT JOIN families f ON f.name = p.family_name;

以下查询返回与原始问题中发布的示例一致的输出:

WITH t AS (
  SELECT
    p.id,
    p.name,
    p.family_id,
    p.enabled,
    -- 如果家庭中的任何产品是主要产品或已发布产品,则product_launch为true
    BOOL_OR(p.is_main_product OR p.product_launch) OVER (PARTITION BY p.family_id) AS product_launch,
    f.name AS family_name,
    MAX(p.id) FILTER (WHERE p.is_main_product) OVER (PARTITION BY p.family_id) AS main_product_id
  FROM
    products AS p
    LEFT OUTER JOIN families AS f ON p.family_id = f.id
  WHERE
    p.enabled
)
SELECT
  t.*
FROM
  t
WHERE
  -- 仅返回为家庭定义的主要产品
  t.id = COALESCE(t.main_product_id, t.id)
ORDER BY
  t.family_id DESC NULLS LAST,
  t.id;
英文:

There are some issues with the original table design. The most troublesome is that families and products are mutually referential. The foreign key association from families to products is not declared in the DDL, but is evident in the query. Among the problems caused by this design is the inability to use constraints to ensure that families.main_product_id and products.family_id are mutually consistent. I've refactored the tables by removing main_product_id from families and adding a boolean flag, is_main_product, to products. A partial unique index enforces the constraint that each family can have only one main product.

I've also made the following changes that aren't relevant to the original problem, but improve the model:

  1. Added unique constraints to the name columns.
  2. Made the boolean columns NOT NULL with defaults.

The changes to the boolean columns help to avoid additional complexity related to handling NULLs when evaluating logic expressions.

Here are the revised table creation statements:

CREATE TABLE families(
  id serial PRIMARY KEY,
  name varchar(50) UNIQUE NOT NULL
);

CREATE TABLE products(
  id serial PRIMARY KEY,
  name varchar(50) UNIQUE NOT NULL,
  family_id int REFERENCES families(id),
  enabled boolean NOT NULL DEFAULT FALSE,
  product_launch boolean NOT NULL DEFAULT FALSE,
  is_main_product boolean NOT NULL DEFAULT FALSE,
  -- a main product must have a family
  CONSTRAINT products_ck CHECK (family_id IS NOT NULL OR is_main_product = FALSE)
);

-- a family can have only one main product
CREATE UNIQUE INDEX products_main_family_product_idx ON products(family_id)
  WHERE is_main_product = TRUE;

Please note, there is an issue caused by using an index to enforce the rule that a family can have only one main product: changing the main product requires two updates, one to set is_main_product to false for the current main product, and a second to set is_main_product to true for the new main product. Since both updates can occur within the same transaction, this should be a minor inconvenience.

I reworked the inserts to eliminate explicit dependencies on generated id values. This helps ensure that id values and the associated sequences are synchronized.

INSERT INTO families(name)(
  VALUES ('macbooks'),
         ('consoles'));

WITH p(
  name,
  family_name,
  enabled,
  product_launch,
  is_main_product
) AS (
  VALUES ('playstation 5', 'consoles', TRUE, TRUE, FALSE),
         ('playstation 3', 'consoles', TRUE, FALSE, TRUE),
         ('xbox 360', 'consoles', TRUE, FALSE, FALSE),
         ('mac book 2015', 'macbooks', TRUE, FALSE, FALSE),
         ('mac book 2018', 'macbooks', TRUE, FALSE, FALSE),
         ('Coca cola', NULL, TRUE, FALSE, FALSE),
         ('Pepsi', NULL, TRUE, FALSE, FALSE))
INSERT INTO products(name, family_id, enabled, product_launch, is_main_product)
SELECT
  p.name,
  f.id,
  p.enabled,
  p.product_launch,
  p.is_main_product
FROM
  p
  LEFT JOIN families f ON f.name = p.family_name;

The following query returns output consistent with the sample posted in the original question:

WITH t AS (
  SELECT
    p.id,
    p.name,
    p.family_id,
    p.enabled,
    -- product_launch is true if any product in the family is the main or a launched product
    BOOL_OR(p.is_main_product OR p.product_launch) OVER (PARTITION BY p.family_id) AS product_launch,
    f.name AS family_name,
    MAX(p.id) FILTER (WHERE p.is_main_product) OVER (PARTITION BY p.family_id) AS main_product_id
  FROM
    products AS p
    LEFT OUTER JOIN families AS f ON p.family_id = f.id
  WHERE
    p.enabled
)
SELECT
  t.*
FROM
  t
WHERE
  -- return only the main product if one is defined for the family
  t.id = COALESCE(t.main_product_id, t.id)
ORDER BY
  t.family_id DESC NULLS LAST,
  t.id;

huangapple
  • 本文由 发表于 2023年4月20日 03:55:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/76058351.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定