【问题标题】:Query returning exact number of rows查询返回确切的行数
【发布时间】:2012-02-12 02:31:23
【问题描述】:

我有一个存储两个外键的表,实现了 n:m 关系。

其中一个指向一个人 (subject),另一个指向特定项目。
现在,一个人可能拥有的物品数量在另一个表中指定,我需要一个查询,该查询将返回与一个人可能拥有的物品数量相同的行数。

其余的记录可以用NULL 值或其他值填充。

事实证明,从应用程序端解决这个问题很痛苦,所以我决定尝试不同的方法。

编辑: 示例

CREATE TABLE subject_items
(
  sub_item integer NOT NULL,
  sal_subject integer NOT NULL,
  CONSTRAINT pkey PRIMARY KEY (sub_item, sal_subject),
  CONSTRAINT fk1 FOREIGN KEY (sal_subject)
      REFERENCES subject (sub_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE,
  CONSTRAINT fk2 FOREIGN KEY (sub_item)
      REFERENCES item (item_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE CASCADE
)

我需要一个查询/函数来返回所有主题项目(主题可能有 5 个项目) 但只有 3 个项目分配给该主题。

返回有点像:

sub_item   |  sal_subject
2          |   1
3          |   1
4          |   1
NULL       |   1
NULL       |   1

我正在使用 postgresql-8.3

【问题讨论】:

  • 无法理解情况。你能给出一些表结构和查询的期望输出吗?
  • 问题末尾的返回表必须在所有行中为sal_subject 保存值1,而不是NULL,才有意义。

标签: sql postgresql plpgsql postgresql-8.3


【解决方案1】:

考虑一下您的plpgsql 函数 的这种很大程度上简化 版本。应该在 PostgreSQL 8.3 中工作:

CREATE OR REPLACE FUNCTION x.fnk_abonemento_nariai(_prm_item integer)
  RETURNS SETOF subject_items AS
$BODY$
DECLARE
    _kiek    integer :=  num_records    -- get number at declaration time
                         FROM subjekto_abonementai WHERE num_id = _prm_item;
    _counter integer;
BEGIN

RETURN QUERY                            -- get the records that actualy exist
SELECT sub_item, sal_subject
FROM   sal_subject 
WHERE  sub_item = prm_item;

GET DIAGNOSTICS _counter = ROW_COUNT;   -- save number of returned rows.

RETURN QUERY
SELECT NULL, NULL                       -- fill the rest with null values
FROM   generate_series(_counter + 1, _kiek);

END;
$BODY$ LANGUAGE plpgsql VOLATILE STRICT;

关于plpgsql in the manual 的详细信息(链接到版本 8.3)。

【讨论】:

  • 感谢您的回答。尽管我很喜欢另一种方法,但我接受这种方法只是因为它更易于阅读/修改。
  • @ertx:对于 pg 8.3,我也会选择这个。在这种情况下,如果没有 row_number() 窗口函数,纯 SQL 会很笨拙。
  • 另外,我不知道可以通过选择在声明时分配变量。很方便。
【解决方案2】:

可以这样工作(纯 SQL 解决方案):

SELECT a.sal_subject
     , b.sub_item
FROM  (
    SELECT generate_series(1, max_items) AS rn
         , sal_subject
    FROM   subject
    ) a
LEFT   JOIN (
    SELECT row_number() OVER (PARTITION BY sal_subject ORDER BY sub_item) AS rn
         , sal_subject
         , sub_item
    FROM   subject_items
    ) b USING (sal_subject, rn)
ORDER  BY sal_subject, rn
  1. 生成每个主题的最大行数,我们称它们为理论项。
    请参阅generate_series() 的手册。
  2. 为每个主题的现有项目应用行号。
    关于window functions的手册。
  3. LEFT JOIN现有项目到每门学科的理论项目。缺少的项目用 NULL 填充。

除了您在问题中披露的表格之外,我假设有一列包含subject 表格中的最大项目数:

CREATE temp TABLE subject
( sal_subject integer,     -- primary key of subject
  max_items int);          -- max. number of items

查询PostgreSQL 8.3,替换缺少的窗口函数row_number()

SELECT a.sal_subject
     , b.sub_item
FROM  (
    SELECT generate_series(1, max_items) AS rn
         , sal_subject
    FROM   subject
    ) a
LEFT   JOIN (
    SELECT rn, sal_subject, arr[rn] AS sub_item
    FROM  (
        SELECT generate_series(1, ct) rn, sal_subject, arr
        FROM  (
            SELECT s.sal_subject
                 , s.ct
                 , ARRAY(
                        SELECT sub_item
                        FROM   subject_items s0
                        WHERE  s0.sal_subject = s.sal_subject
                        ORDER  BY sub_item
                    ) AS arr
            FROM  (
                SELECT sal_subject
                     , count(*) AS ct
                FROM   subject_items
                GROUP  BY 1
                ) s
            ) x
        ) y
    ) b USING (sal_subject, rn)
ORDER  BY sal_subject, rn

更多关于在这个article by Quassnoi中替换row_number()

【讨论】:

  • 您的解决方案看起来非常不错,但我可能应该提到我使用的是 8.3.4 版本,据我所知,窗口功能仅适用于 8.4+。
  • @ertx:是的,你绝对应该提到这一点。窗口函数适用于 PostgreSQL 8.4+。我为 8.3 添加了一个解决方案。
【解决方案3】:

我想出了这个简单的解决方案: 首先返回我可以选择的所有值,然后在我们有正确数量的情况下循环返回空值。如果有人偶然发现同样的问题,请在此处发布。 仍在寻找更简单/更快的解决方案(如果存在)。

CREATE OR REPLACE FUNCTION fnk_abonemento_nariai(prm_item integer)
  RETURNS SETOF subject_items AS
$BODY$DECLARE _kiek integer;
DECLARE _rec subject_items;
DECLARE _counter integer;
BEGIN
  /*get the number of records we need*/
  SELECT INTO _kiek num_records
  FROM subjekto_abonementai
    WHERE num_id = prm_item;

  /*get the records that actualy exist */

  FOR _rec IN SELECT sub_item, sal_subject
      FROM sal_subject 
      WHERE sub_item = prm_item LOOP
    return 
      next _rec;
    _counter := COALESCE(_counter, 0) + 1;
  END LOOP;

  /*fill the rest with null values*/

  While _kiek > _counter loop
    _rec.sub_item := NULL;
    _rec.sal_subject := NULL;
    Return next _rec;
    _counter := COALESCE(_counter, 0) + 1;
  end loop;

END;$BODY$
  LANGUAGE plpgsql VOLATILE;

【讨论】:

  • 函数体第8行的WHERE num_id = sub_item子句没有意义。
  • 已修复,因为这只是更大结构的一部分,所以在发布之前我已经更改了一些内容。
猜你喜欢
  • 1970-01-01
  • 2014-06-16
  • 1970-01-01
  • 2014-04-22
  • 2013-12-20
  • 2017-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多