JOIN table 本身,并通过按位运算从每个 table 中过滤行

JOIN table with itself, and filter rows from each table with bitwise operation

我有以下 table,

-- Generated with pg_dump, some constraints are missing
CREATE TABLE articulos_factura_venta (
    fila integer NOT NULL,
    cantidad integer NOT NULL,
    color integer NOT NULL,
    talla integer NOT NULL,
    estado integer DEFAULT 2 NOT NULL,
    origen integer,
    factura integer NOT NULL,
    articulo integer NOT NULL,
    precio integer NOT NULL,
    vendedor integer,
    anulado boolean DEFAULT false,
    iva double precision DEFAULT 12.0,
    fecha date DEFAULT ('now'::text)::date NOT NULL
);

它包含以下行1

 fila | cantidad | color | talla | estado | origen | factura | articulo | precio | vendedor | anulado | iva |   fecha    
------+----------+-------+-------+--------+--------+---------+----------+--------+----------+---------+-----+------------
    0 |        1 |     0 |     3 |      6 |     18 |   28239 |     1325 |    455 |        6 | f       |   0 | 2015-04-22
    1 |        1 |     0 |     2 |      6 |     93 |   28239 |     2071 |    615 |        6 | f       |   0 | 2015-04-22
    2 |        1 |     0 |    49 |      6 |     76 |   28239 |     2013 |    545 |        6 | f       |   0 | 2015-04-22
    3 |        1 |     0 |    78 |      6 |     85 |   28239 |     2042 |    235 |        6 | f       |   0 | 2015-04-22
    4 |        1 |     0 |    49 |      6 |     81 |   28239 |     2026 |    615 |        6 | f       |   0 | 2015-04-22
    5 |        1 |     0 |    50 |      6 |     90 |   28239 |     2051 |    755 |        6 | f       |   0 | 2015-04-22
    6 |        1 |     0 |     1 |     38 |     21 |   28239 |     1780 |    495 |        6 | f       |   0 | 2015-04-22
    7 |        1 |    15 |     2 |     38 |     16 |   28239 |     1323 |    845 |        6 | f       |   0 | 2015-04-22
    8 |        1 |     0 |     4 |     38 |     18 |   28239 |     1326 |    455 |        6 | f       |   0 | 2015-04-22
    2 |        1 |     0 |    49 |     22 |     76 |   28239 |     2013 |    545 |        6 | f       |   0 | 2015-04-22

问题很直接,为什么这个查询没有输出行?

SELECT
    filas.factura,
    filas.fila,
    filas.cantidad,
    retirados.cantidad,
    vendidos.cantidad,
    filas.estado
FROM
    articulos_factura_venta AS filas
LEFT JOIN
    articulos_factura_venta AS retirados
    USING (fila, color, talla, origen, factura, articulo, vendedor)
LEFT JOIN
    articulos_factura_venta AS vendidos
    USING (fila, color, talla, origen, factura, articulo, vendedor)
JOIN
    articulos
    ON articulos.codigo = filas.articulo
JOIN
    tallas
    ON tallas.codigo = filas.talla
JOIN
    colores
    ON colores.codigo = filas.color
JOIN
    empleados
    ON empleados.codigo = filas.vendedor
WHERE
    filas.factura = 28239 AND 
    retirados.estado & 16 <> 0 AND 
    vendidos.estado & 8 <> 0 AND
    filas.estado & 4 <> 0
ORDER BY
    filas.estado

我希望此查询从具有 fila == 2 的行中减去 cantidad,在 estado & 16 <> 0 的情况下,因此我希望只有一行具有 fila == 2cantidad = 0

注意:位标志不是硬编码的,它们是我在用 c++ 编写的实际应用程序中使用的 enum

Table定义

database# \d articulos_factura_venta
  Column  |       Type       |              Modifiers               
----------+------------------+--------------------------------------
 fila     | integer          | not null
 cantidad | integer          | not null
 color    | integer          | not null
 talla    | integer          | not null
 estado   | integer          | not null default 2
 origen   | integer          | 
 factura  | integer          | not null
 articulo | integer          | not null
 precio   | integer          | not null
 vendedor | integer          | 
 anulado  | boolean          | default false
 iva      | double precision | default 12.0
 fecha    | date             | not null default ('now'::text)::date
Indexes:
    "articulos_factura_venta_pkey" PRIMARY KEY, btree (fila, factura, articulo, precio, talla, color, estado)
    "buscar_cantidad_venta_idx" btree (articulo, talla, color, origen)
Foreign-key constraints:
    "cantidades_venta_articulo_fkey" FOREIGN KEY (articulo) REFERENCES articulos(codigo)
    "cantidades_venta_color_fkey" FOREIGN KEY (color) REFERENCES colores(codigo) ON UPDATE CASCADE ON DELETE RESTRICT
    "cantidades_venta_factura_fkey" FOREIGN KEY (factura) REFERENCES ventas(codigo)
    "cantidades_venta_origen_fkey" FOREIGN KEY (origen) REFERENCES compras(codigo) ON UPDATE CASCADE ON DELETE RESTRICT
    "cantidades_venta_talla_fkey" FOREIGN KEY (talla) REFERENCES tallas(codigo) ON UPDATE CASCADE ON DELETE RESTRICT
    "cantidades_venta_vendedor_fkey" FOREIGN KEY (vendedor) REFERENCES empleados(codigo)

[1]table 包含数千行,但我只对这些行感兴趣,即 factura == 28239.

这是一个称为链式外部联接的问题。一旦您首先执行了一些 LEFT OUTER JOIN,它会为右侧 table 中与左侧 table 不匹配的列创建 NULL 值。然后,当您将这些 NULL 值与之后的 INNER JOIN 连接时,这些行就会消失,就好像您一开始就没有进行过外部连接一样。

有两种解决方法:

  1. 一旦您开始 LEFT JOIN,所有后续的 JOIN 必须是 LEFTFULL
  2. 更好的选择是先完成所有 INNER JOIN 然后再完成 table 想成为最后一个 RIGHT JOIN

此外,当您在执行 OUTER JOIN 时,无论是 LEFT 还是 RIGHT,将 WHERE CLAUSE 条件移动到 ON 子句而不是 WHERE 子句。这是一个非常棘手的问题,但请查看 FILTER 条件和 JOIN 条件之间的区别,以及何时应将它们放在 WHEREON 子句中。

长话短说,它可能会这样工作:

SELECT f.factura
     , f.fila
     , f.cantidad
     , r.cantidad
     , v.cantidad
     , f.estado
FROM   articulos_factura_venta f
-- JOIN   articulos a ON a.codigo = f.articulo  -- just noise
-- JOIN   tallas    t ON t.codigo = f.talla
-- JOIN   colores   c ON c.codigo = f.color
JOIN   empleados e ON e.codigo = f.vendedor
LEFT   JOIN articulos_factura_venta r ON r.fila = f.fila
                                     AND r.color = f.color
                                     AND r.talla = f.talla
                                     AND r.origen = f.origen
                                     AND r.factura = f.factura
                                     AND r.articulo = f.articulo
                                     AND r.vendedor = f.vendedor
                                     AND r.estado & 16 <> 0
LEFT   JOIN articulos_factura_venta v ON v.fila = f.fila
                                     AND v.color = f.color
                                     AND v.talla = f.talla
                                     AND v.origen = f.origen
                                     AND v.factura = f.factura
                                     AND v.articulo = f.articulo
                                     AND v.vendedor = f.vendedor
                                     AND v.estado & 8 <> 0
WHERE  f.factura = 28239
AND    f.estado & 4 <> 0
ORDER  BY f.estado;

特别是这些添加的 WHERE 子句使您在各自的 table 上尝试 LEFT JOIN 无效,并使其表现得像 JOIN:

AND r.estado & 16 <> 0
AND v.estado & 8 <> 0

另一个置顶细节:

JOIN   empleados e ON e.codigo = f.vendedor

但是f.vendedor可以是NULL。您是否打算从结果中删除所有带有 f.vendedor IS NULL 的行?因为这就是连接的作用。

并且我评论了 articulostallascolores 的三个连接。 FK 列是 NOT NULL,连接只会花费时间并且您没有使用任何列。

Table定义

超过 7 列的主键约束是一个糟糕的想法。昂贵且笨重。添加代理主键 - 我建议 serial 列:

  • Auto increment SQL function
  • Primary & Foreign Keys in pgAdmin

您仍然可以使用 UNIQUE 约束对 7 列的集合强制执行唯一性 - 如果 您确实需要它。
关于 UNIQUE and PRIMARY KEY 约束(根据评论中的请求):

  • How does PostgreSQL enforce the UNIQUE constraint / what type of index does it use?
  • Do I need a primary key for my table, which has a UNIQUE (composite 4-columns), one of which can be NULL?
  • Why can I create a table with PRIMARY KEY on a nullable column?

建议table设计:

CREATE TABLE articulos_factura_venta (
    <b>afv_id serial PRIMARY KEY</b>  -- pick your column name
    fila integer NOT NULL,
    cantidad integer NOT NULL,
    color integer NOT NULL,
    talla integer NOT NULL,
    estado integer DEFAULT 2 NOT NULL,
    factura integer NOT NULL,
    articulo integer NOT NULL,
    precio integer NOT NULL,
    fecha date NOT NULL DEFAULT now()::date,
    origen integer,
    vendedor integer,
    anulado boolean DEFAULT false,  -- NOT NULL ?
    iva double precision DEFAULT 12.0,
    <b>CONSTRAINT uni7  -- pick your contraint name
     UNIQUE (fila, factura, articulo, precio, talla, color, estado)</b>
);

那么查询可以简化为:

...
LEFT   JOIN articulos_factura_venta r ON r.afv_id = f.afv_id
                                     AND r.estado & 16 <> 0
LEFT   JOIN articulos_factura_venta v ON v.afv_id = f.afv_id
                                     AND v.estado & 8 <> 0
...