查询返回确切的行数

时间:2012-01-17 09:14:40

标签: sql postgresql plpgsql postgresql-8.3

我有一个存储两个外键的表,实现了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

3 个答案:

答案 0 :(得分: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填充。
  4. 除了您在问题中披露的表格之外,我假设一列保存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()的详情。

答案 1 :(得分:2)

考虑 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版)。

答案 2 :(得分:0)

我能够达到这个简单的解决方案: 首先返回我可以选择的所有值,然后循环返回空值,同时我们有正确的数量。如果有人偶然发现同样的问题,请在此处发布。 如果它们存在,仍在寻找更容易/更快的解决方案。

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;