LINUX.ORG.RU

SQL function

 ,


0

2

Продолжение темы:
SQL case

Postgresql 9.3.
Несколько связанных таблиц:

t4 ()
t3 (t4_id)
t2 (t3_id, valuefact, manualinput)
t1 (t2_id, valuefact)

IF t2.manualinput = TRUE
THEN t2.valuefact
ELSE sum(t1.valuefact)


Для одной таблицы вроде бы проблем нет,
но нужно постоянно вычислять t2.valuefact для таблиц верхнего уровня,
и приходится делать sum (sum (sum.
Логика запросов для меня теряется.

sum(
  CASE WHEN t2.manualinput = TRUE
    THEN valuefact = t2.valuefact
    ELSE valuefact = (select sum(valuefact) from t1 where t1.t2_id = ?)
  END
)


Правильно ли создать функцию, вычисляющую valuefact по t2.id
и потом ее суммировать sum( f(id) )?

сделаю lorcode за тебя

Несколько связанных таблиц:

t4 (t3.id)
t3 (t2.id)
t2 (valuefact, manualinput)
t1 (t2_id, valuefact)

Если t2.manualinput = TRUE, то берется t2.valuefact, иначе sum(t1.valuefact).

Для одной таблицы вроде бы проблем нет, но нужно постоянно вычислять t2.valuefact для таблиц более верхнего уровня, и приходится постоянно делать sum (sum (sum. Логика запросов для меня теряется.

sum(
    CASE WHEN wu.manualinput = TRUE
	 THEN valuefact2 = t1.valuefact
	 ELSE valuefact2 = (select sum(valuefact) from t2 where t1.id = t2.t1_id)
	 END
)

Правильно ли сделать так: создать функцию, вычисляющую valuefact по t2.id и потом ее суммировать sum(f(id)).

anonymous ()

Попробуй описать что у тебя есть и что нужно. Вероятно, это больше поможет, чем обрывки мыслей в посте.

AnDoR ★★★★★ ()
Ответ на: комментарий от AnDoR

<code='SQL'> table wu (id, manualinput)table wo (id, wu_id, valuefact)
table wp (id, wo_id, valuefact)

IF wu.manualinput = TRUE
THEN wo.valuefact
ELSE суммировать valuefact из wp
</code>

Вычисленное wo.valuefact суммируется также в других группировках
и есть таблица преобразований wu.id1 -> wu.id2 (голову сломаешь).

Создал функцию wo_valuefact() для вычисления valuefact.
На практике такое применяют?

<code SQL>
-- input: workotdel.id
-- return: valuefact
CREATE FUNCTION wo_valuefact (INTEGER) RETURNS SETOF NUMERIC AS $$
BEGIN
RETURN QUERY
SELECT
(CASE WHEN wu.manualinput = TRUE
THEN
wo.valuefact
ELSE
(
SELECT sum(wp.valuefact)
FROM workpeople wp
WHERE wp.workotdel_id = $1
)
END
)
FROM
workotdel wo,
workunit wu
WHERE
wo.workunit_id = wu.id
AND wo.id = $1;
END;
$$ LANGUAGE plpgsql;
</code>

WinLin2 ()
Последнее исправление: WinLin2 (всего исправлений: 2)

Ничего не понял, но вот, например:

create function get_t1_valuefact(p_t2_id t1.t2_id%type) returns t2.valuefact%type language sql as $$select sum(t1.valuefact) from t1 where t1.t2_id = $1$$;

create function get_t2_valuefact(p_t2_id t2.id%type) returns t2.valuefact%type language sql as $$select case when t2.manualinput then t2.valuefact else get_t1_valuefact(t2.id) end from t2 where id = $1$$;
Но я бы не стал такое в sql-запросах использовать. Каша в коде принципиально оставлена.

winlook38 ★★ ()
Ответ на: комментарий от winlook38

В реальности вычисление сложнее выглядит.
И если еще суммирование с группировками добавить,
то прочитать это невозможно.

CREATE FUNCTION wo_valuefact (INTEGER) RETURNS SETOF NUMERIC AS $$
BEGIN
  RETURN QUERY
    SELECT 
	(CASE WHEN wu.manualinput = TRUE
	  THEN wo.valuefact
	  ELSE (
		SELECT sum(wp.valuefact)
		FROM workpeople wp
		WHERE wp.workotdel_id = $1
		)
	  END
	)
     FROM
	workotdel wo,
	workunit wu
     WHERE
	wo.workunit_id = wu.id
	AND wo.id = $1;
END;
$$ LANGUAGE plpgsql;

WinLin2 ()
Ответ на: комментарий от WinLin2

table wu (id, manualinput)
table wo (id, wu_id, valuefact)
table wp (id, wo_id, valuefact)

IF wu.manualinput = TRUE
THEN wo.valuefact
ELSE суммировать valuefact из wp

Кажется, тебе надо что-то такое:

select
  case
    when wu.manualinput = true then
      wo.valuefact
    else
      (select sum(valuefact) from wp)
  end as somevalue
from
  wu,
  wo;


Но больше похоже, что у тебя какая-то хреновая структура базы. Зачем эти данные все в разных таблицах хранить? У тебя же между wu и wo связь 1к1?

AnDoR ★★★★★ ()
Последнее исправление: AnDoR (всего исправлений: 1)
Ответ на: комментарий от AnDoR

А, ну ты уже это же самое и написал, только зачем-то в хранимке.

AnDoR ★★★★★ ()
Ответ на: комментарий от WinLin2

Я не очень понимаю чего ты хочешь. Ты сейчас написал то же самое, что в ОП. Я тебе предлагал разбить это на функции, каждая из которых читается легче, чем вся простыня вместе. Если вычисления усложняются, делай еще одну функцию.

winlook38 ★★ ()
Ответ на: комментарий от AnDoR

wu - wo (1 ко многим).
wu - название работ и признак откуда брать данные из wo или wp.
wp - сделал сотрудник.
wo - сделал отдел.
t - (wu_id1, wu_id2) - преобразовать wu_id2 в wu_id1.

Пока до организации дойдешь уже непонятно, что с чем суммируется :)
Спасибо за помощь.

WinLin2 ()
Ответ на: комментарий от winlook38

Сейчас все понял.
Просто интересовал вопрос - на практике разбивают на функции или делают вложенными запросами.
Функциями проще сделать.

WinLin2 ()
Ответ на: комментарий от WinLin2

wu - название работ и признак откуда брать данные из wo или wp.
wp - сделал сотрудник.
wo - сделал отдел.

А должно быть максимум две таблицы. Возможно даже одна. Тогда всех этих костылей вокруг структуры можно избежать.

AnDoR ★★★★★ ()
Ответ на: комментарий от AnDoR

А должно быть максимум две таблицы. Возможно даже одна.

Видел примерную структуру базы из одной таблицы:

id     - идентификатор
code   - код поля (название)
value1 - значение1 поля
value2 - значение2 поля

При запросе делают соединения таблицы с самой сбой несколько раз.

WinLin2 ()
Последнее исправление: WinLin2 (всего исправлений: 2)
Ответ на: комментарий от WinLin2

Скорее как-то так:

id serial
manualinput boolean # вместо отдельной таблицы wu
valuefact int # вместо разнесения одинаковых данных по разным таблицам

Тогда то, что ты желаешь, будет простым select sum(valuefact) from tablename;

AnDoR ★★★★★ ()
Ответ на: комментарий от AnDoR

В заголовке темы описал упрощенный пример и поэтому возникло «несовпадение взглядов».
workunit - справочник видов работ.


CREATE TABLE workunit (
	id serial PRIMARY KEY,
	name VARCHAR(200) NOT NULL,
	name2 VARCHAR(200) NOT NULL,
	numberorder INT,
	manualinput BOOLEAN DEFAULT FALSE
);

CREATE TABLE workotdel (
	id serial PRIMARY KEY,
	otdel_id INT REFERENCES otdel(id) on delete cascade,
	document_id INT REFERENCES document(id) on delete cascade,
	workunit_id INT REFERENCES workunit(id) on delete cascade,
	valueplan NUMERIC DEFAULT 0,
	valuefact NUMERIC DEFAULT 0,
	sumplan NUMERIC DEFAULT 0,
	sumfact NUMERIC DEFAULT 0,
	UNIQUE (otdel_id, document_id, workunit_id)
);

CREATE TABLE workpeople (
	id serial PRIMARY KEY,
	workotdel_id INT REFERENCES workotdel(id) on delete cascade,
	people_id INT REFERENCES people(id) on delete cascade,
	valuefact NUMERIC DEFAULT 0,
	comment text DEFAULT '',
	UNIQUE (people_id, workotdel_id)
);


WinLin2 ()
Ответ на: комментарий от WinLin2

Так тебе в workunit можно nullable внешние ключ хранить на workotdel и на workpeople и по условию джойн делать.

Хотя фигня какая-то всё равно получается.

AnDoR ★★★★★ ()
Последнее исправление: AnDoR (всего исправлений: 1)
Ответ на: комментарий от WinLin2

Если из дизайна таблиц и их взаимосвязей не очевидна задача, которую они решают, то скорее всего они спроектированы плохо. По-моему, это твой случай.

winlook38 ★★ ()
Ответ на: комментарий от WinLin2

Я это дело не запускал, просто в текстовом редакторе набросал. Вроде, должно подойти, если я правильно понял смысл твоих наворотов. Попробуй, если не заработает - отпишись.

with work_aggr as (
  select wp.workotdel_id,
         wp.
         sum(wp.valuefact) as valuefact_sum
  from workpeople wp
  group by wp.workotdel_id
) 
select wu.id,
       wu.name,
       case wu.manualinput 
          when true then wo.valuefact
          else wa.valuefact_sum
       end
from workunit wu
inner join workotdel wo on wo.workunit_id = wu.id
inner join work_aggr wa on wa.workotdel_id = wo.id;

alex_the_v ★★★ ()
Последнее исправление: alex_the_v (всего исправлений: 2)
Ответ на: комментарий от alex_the_v

Я было подумал, что косяк допустил, но вроде все норм.

Схема данных у тебя отвратительная. Особенно жутко завязана воркпипл на воркотдел, руки надо отрывать за это.

alex_the_v ★★★ ()
Ответ на: комментарий от alex_the_v

Блин, сломал пока туда-сюда гонял, заодно немного покрасивее переделал. Вот правильный вариант:

with work_aggr as (
  select wp.workotdel_id,
         sum(wp.valuefact) as valuefact_sum
  from workpeople wp
  group by wp.workotdel_id
) 
select wu.id,
       wu.name,
       coalesce(wa.valuefact_sum, wo.valuefact) as valuefact
from workunit wu
inner join workotdel wo on wo.workunit_id = wu.id
left join work_aggr wa on wa.workotdel_id = wo.id and wu.manualinput = true;

alex_the_v ★★★ ()
Последнее исправление: alex_the_v (всего исправлений: 3)
Ответ на: комментарий от alex_the_v

Первоначальная идея была такой:

CREATE TABLE workpeople (
	id serial PRIMARY KEY,
	otdel_id INT REFERENCES otdel(id) on delete cascade,
	document_id INT REFERENCES document(id) on delete cascade,
	workunit_id INT REFERENCES workunit(id) on delete cascade,
	valueplan NUMERIC DEFAULT 0,
	valuefact NUMERIC DEFAULT 0,
	sumplan NUMERIC DEFAULT 0,
	sumfact NUMERIC DEFAULT 0
);

CREATE TABLE workunit (
	id serial PRIMARY KEY,
	name VARCHAR(200) NOT NULL, - полное наименование
	name2 VARCHAR(200) NOT NULL,- краткое наименование 
	numberorder INT             - для сортировки вместо алфавита. 
);


Данные храним по каждому человеку в workpeople,
выборками получаем итоги по отделам и организации.

После показа макета выяснилось, что по НЕКОТОРЫМ видам работ
не нужно суммировать workpeople по отделам (только просмотр)
и сразу откуда-то брать итоги по отделу,
планы для всех работ вводить по отделам.
Кроме самих вычислений итогов нужно показывать выборки
по человеку, отделам, организации с подсчетом вклада каждого во всех уровнях.

Схема данных у тебя отвратительная.
Особенно жутко завязана воркпипл на воркотдел, руки надо отрывать за это.

Сам завязывал :)
Предложи, что можно исправить.
Можно хранить вычисленное значение workotdel.valuefact,
что наверно не совсем правильно.

WinLin2 ()
Ответ на: комментарий от WinLin2

Сам завязывал :)

Ай молодца =) На самом деле, из меня дата-моделер тоже как из говна пуля. Так с разбегу трудно родить хорошую схему, тем более, что у тебя куча всяких хитрых маленьких нюансов, в которые надо вникать.

Наверное, стоило бы workpeople завязать напрямую на workunit, а не через workotdel. Ну, то есть в workpeople проставлять workunit_id. Это слегка упростило бы жизнь.

alex_the_v ★★★ ()
Последнее исправление: alex_the_v (всего исправлений: 1)
Ответ на: комментарий от WinLin2

После показа макета выяснилось, что по НЕКОТОРЫМ видам работ
не нужно суммировать workpeople по отделам (только просмотр)
и сразу откуда-то брать итоги по отделу,
планы для всех работ вводить по отделам.
Кроме самих вычислений итогов нужно показывать выборки
по человеку, отделам, организации с подсчетом вклада каждого во всех уровнях.

Может это всё регулировать запросами, а не структурой БД?

yyk ★★★★★ ()
Ответ на: комментарий от WinLin2

Сначала максимально нормализуешь данные, чтобы отношения между сущностями были очевидны, потом денормализуешь отдельные участки, ради увеличения производительности. Сейчас у тебя каша, как в первоначальном варианте, так и в имеющемся. На сколько я вижу, у тебя там план и факт по сотруднику, а так же суммарный план и факт чего-то в одной таблице. Разнеси все по разным таблицам. Затем, я так и не понял из структуры, к кому относится признак workunit, к отделу или к сотруднику (сейчас к сотруднику, но, как я понял, от него зависит нужно ли суммировать показатель отдела по сотрудникам, а значит, он должен относиться к отделу).

winlook38 ★★ ()
Ответ на: комментарий от winlook38

Сначала была свобода:
* Каждый сотрудник мог составить свой список работ за период (document_id),
выбрав из справочника workunit.

* Список работ организации/отделов формировался налету
из уникальных значений workpeople.workunit_id.

* valueplan, valuefact, sumplan, sumfact суммировались по разным уровням,
вычислялся % сотрудника в сумме уровней.

* у отдела и организации ничего не вводилось.

Новое задание:
* valueplan, sumplan, sumfact вводим на уровне отдела (workotdel).

* valuefact вводит каждый сотрудник (workpeople).

* valuefact для отдела и организации суммируем от сотрудников,
но для workunit_id (хочу такие id) вручную заполнить valuefact для отдела,
valuefact сотрудника считать справочным и тоже хранить.

Статистический казус по старой схеме:
Провели мероприятие 10 раз, в каждом участвовало по 5 сотрудников.
В итогах оказалось 50 мероприятий.
В отделах пишем 10 мероприятий, а у сотрудников свои значения (справочно).

***************************

На сколько я вижу, у тебя там план и факт по сотруднику,
а так же суммарный план и факт чего-то в одной таблице.

valuefact берем из отдела или от сотрудника, что зависит от workunit.manualinput,
но хранить нужно оба workpeople.valuefact и workotdel.valuefact.

Затем, я так и не понял из структуры, к кому относится признак workunit, к отделу или к сотруднику
(сейчас к сотруднику, но, как я понял, от него зависит нужно ли суммировать показатель отдела по
сотрудникам, а значит, он должен относиться к отделу).

Отменил свободный набор видов работ у сотрудника,
теперь виды работ идентичны у отдела и сотрудника
workotdel.workunit_id <-> workpeople.workotdel_id

WinLin2 ()
Ответ на: комментарий от WinLin2

В итогах оказалось 50 мероприятий.

А что не так? Все правильно же? Давай ТЗ, а то все равно что-то тут не так.
Но лучше стукнись в jabber.ru на мой ник. Я бы поковырялся, если ты сформулируешь задачу.

winlook38 ★★ ()
Ответ на: комментарий от winlook38

А что не так? Все правильно же? Давай ТЗ, а то все равно что-то тут не так.

Почти правильно.
Одно мероприятие могут готовить несколько человек,
каждый участник запишет его себе: sum(workpeople.valuefact) => 50

Для такого случая:
разово настраиваем вид работ: workunit.manualinput = true,
пользователь вводит показатель за свой отдел в workotdel,
workpeople.valuefact используем справочно,

Я бы поковырялся, если ты сформулируешь задачу.

Нужно сделать аналог X-матрицы:
http://www.leaninfo.ru/wp-content/uploads/2008/08/x-matrix-560x373.png
http://www.leaninfo.ru/2008/08/13/strategiya-na-odnom-liste/

WinLin2 ()
Последнее исправление: WinLin2 (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.