Оптимизация запросов для извлечения данных путем объединения таблиц с операциями и условиями в полях jsonb - PullRequest
0 голосов
/ 30 апреля 2019

У меня есть таблицы distribution_order и distribution_order_items со следующей схемой.

CREATE TABLE public.distribution_order
(
    distributionorderid bigint NOT NULL DEFAULT nextval('distribution_order_distributionorderid_seq'::regclass),
    sourcelocationid character varying(20) COLLATE pg_catalog."default" NOT NULL,
    destinationlocationid character varying(20) COLLATE pg_catalog."default" NOT NULL,
    distributionorderheader jsonb NOT NULL,
    status character varying(25) COLLATE pg_catalog."default" NOT NULL,
    createdtimestamp timestamp without time zone NOT NULL,
    lastupdatedtimestamp timestamp without time zone,
    CONSTRAINT distribution_order_pkey PRIMARY KEY (distributionorderid)
)

---------------------------------------------------
CREATE TABLE public.distribution_order_item
(
    distributionorderid bigint NOT NULL,
    packid character varying(50) COLLATE pg_catalog."default" NOT NULL,
    status character varying(25) COLLATE pg_catalog."default" NOT NULL,
    itemdata jsonb NOT NULL,
    createdtimestamp timestamp without time zone NOT NULL,
    lastupdatedtimestamp timestamp without time zone,
    CONSTRAINT uq_distribution_order_item UNIQUE (distributionorderid, packid),
    CONSTRAINT fk_distributionorderid FOREIGN KEY (distributionorderid)
        REFERENCES public.distribution_order (distributionorderid) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
)

схема jsonb distribution_order.distributionorderheader:

{
  "orderType": "stock",
  "destinationLocationId": "1008",
  "orderIds": [
    "e63b9007-dcaa-4c33-bd1d-e5fbcced0913"
  ],
  "sourceLocationId": "1002",
  "deliveryDate": "2018-07-13T23:00:00.000Z",
  "orderedDate": "2018-07-11T17:00:00.000Z"
}

схема jsonb distribution_order_item.itemdata:

{
  "packId": "100003366",
  "orderedQuantity": 0,
  "pickedInfo": [
    {
      "pickDate": "2019-04-17T10:05:36Z",
      "lotNumber": "20191218",
      "quantityType": "CS",
      "containerId": "10000000000000014290",
      "quantity": "4.000000"
    }
  ]
  "shipInfo": [
    {
      "shippedDate": "2019-04-17T10:05:36Z",
      "lotNumber": "20191219",
      "quantityType": "CS",
      "containerId": "33333",
      "shippedQuantity": "4.000000"
    }
  ]
}

ItemData в таблице distrubtion_order_item будетобновляться при каждой отправке, полученной с помощью дополнительного jsonb-бокса в itemdata.shipInfo. У меня есть требование рассчитывать количество отправлений, добавляя «shipedQuantity» в shipInfo, selectedQuantity, добавляя количество в itemdata.pickInfo. Я написал следующее: длинныйработает, любая помощь по его оптимизации будет высоко оценена.

WITH D_ORD AS (
   SELECT distributionorderid,
   destinationlocationid,
   distributionorderheader->>'orderedDate' as od,
   distributionorderheader->>'deliveryDate' as dd
   FROM distribution_order
   WHERE distributionorderheader->>'deliveryDate' > '2019-04-23 17:09:46.46' ),
D_ORD_ITEMS AS (    
    SELECT  
        d.distributionorderid,
        i.itemdata->>'itemId' AS itemid,
        d.od::TIMESTAMP AS ordereddate,
        d.destinationlocationid,
        i.packid AS unitid,
        i.itemdata->>'orderedQuantity' orderedquantity,
        CASE WHEN i.status='SHIPPED' OR i.status='COMPLETE' 
             THEN CASE WHEN (i.itemdata->>'orderedQuantity')::float - sum((s->>'shippedQuantity')::float) >= 0 
                       THEN (i.itemdata->>'orderedQuantity')::float - sum((s->>'shippedQuantity')::float) 
                       ELSE 0 
                  END 
             ELSE CASE WHEN i.status='PICKED'  
                       THEN CASE WHEN (i.itemdata->>'orderedQuantity')::float - sum((p->>'quantity')::float) >= 0 
                                 THEN (i.itemdata->>'orderedQuantity')::float - sum((p->>'quantity')::float) 
                                 ELSE 0 
                            END 
                       ELSE 0 
                  END 
        END AS cancelorderquantity, 
        CASE WHEN (CASE WHEN i.status='SHIPPED' OR i.status='COMPLETE' 
                        THEN (i.itemdata->>'orderedQuantity')::float - sum((s->>'shippedQuantity')::float) 
                        ELSE 0 
                   END >0) 
             THEN CURRENT_DATE::TIMESTAMP::text 
             ELSE CASE WHEN (CASE WHEN i.status='PICKED'  
                                  THEN (i.itemdata->>'orderedQuantity')::float - sum((p->>'quantity')::float) 
                                  ELSE 0 
                             END >0) 
                       THEN CURRENT_DATE::TIMESTAMP::text 
                       ELSE '' 
                  END 
        END AS cancelleddate, 
        CASE WHEN (sum((s->>'shippedQuantity')::float) > 0 OR sum((p->>'quantity')::float) >0) 
             THEN CURRENT_DATE::TIMESTAMP::text 
             ELSE '' 
        END AS arrivedate , 
        CASE WHEN extract(HOUR FROM (d.dd)::TIMESTAMP) =23 
             THEN ((d.dd::TIMESTAMP + interval '1 DAY')::date + interval '6 hour') 
             ELSE d.dd::TIMESTAMP::date + interval '6 hour' 
        END AS exp_av, 
        CASE WHEN sum((s->>'shippedQuantity')::float) >0 
             THEN sum((s->>'shippedQuantity')::float) 
             ELSE CASE WHEN sum((p->>'quantity')::float)>0 
                       THEN sum((p->>'quantity')::float) 
                       ELSE 0 
                  END 
        END AS receivedquantity 
    FROM D_ORD d, distribution_order_item i LEFT JOIN jsonb_array_elements(i.itemdata->'shipmentInfo') s ON TRUE 
                LEFT JOIN jsonb_array_elements(i.itemdata->'pickedInfo')p ON TRUE 
    GROUP BY d.distributionorderid,d.destinationlocationid, i.itemdata->>'itemId', d.od::TIMESTAMP, i.packid, 
                i.itemdata->>'orderedQuantity', 
                i.itemdata->>'packSize', 
                i.status, 
                d.dd::TIMESTAMP)

SELECT * from   D_ORD_ITEMS

1 Ответ

1 голос
/ 30 апреля 2019

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

При проектировании базы данных для использования обычных полей по сравнению с полями JSON сначала отойдите от базы данных.Подумайте, какое приложение вы бы использовали, если бы не было СУБД для сохранения ваших данных.

Если вы пытаетесь сохранить данные в Excel, то это ваша модель данных, как правило, табличная.

action      product     qty
received    mouse       3
sold        mouse       2
received    keyboard    26

.. тогда просто используйте обычные поля, не пытайтесь использовать поля JSON.

Если вы стремитесь использовать Word (или даже OneNote или Notepad) вместо Excel, чтобы сохранить ваши данные (например,записи пациентов), то это хороший показатель того, что ваша модель данных является слабо определенной, что может быть облегчено с помощью JSON, тогда во всех случаях используйте JSON.Поскольку не все пациенты имеют одинаковые детали записей, которые нужно хранить, будет трудно, если не невозможно, добавлять новые поля в таблицу каждый раз, когда требуется записать новую деталь;так что да, используйте вместо этого JSON.

Patient: John Doe

Intake Criteria: 
    Dm Dx Date: 2012/03/12
    Initial Hgb A1c: 6.5
    Co Morbid: Hypertension, CAD 

Labs:
    LDL Cholestrol:
        LDL Level: 122.5,
        LDL Result Date: 2012/07/06
    Serum Creatinine:
        CreatinineLevel: 1.4
        Creatinine Result Date: 12/07/08

------


Patient: Jane Doe

Blood Type: DB

CareLocation:
    FacilityName: East Side Clinic
    ContactEmail: rsnurse@eastside.org

Weight: 60kg

Если ваши данные, как правило, основаны на документах (например, OneNote, Word и т. д.), используйте JSON.Если ваши данные, как правило, основаны на листах (то есть в табличной форме, например, Excel), не используйте JSON, вместо этого используйте обычные поля.Нормальные поля являются естественными для доступа к СУБД и естественными для СУБД для создания индекса.

Если рефакторинг базы данных для преобразования ваших свойств JSON в обычные поля не может быть учтен в текущем расписании разработки, вы можетесделайте так, чтобы ваши свойства JSON рассматривались как «обычные поля», создав индекс для этих свойств.

Создайте индекс для четко определенных данных, к этим четко определенным данным обычно обращаются и / или они всегда присутствуют в ваших данных JSON, большинство этих четко определенных данных можно найти в GROUPПредложения BY или WHERE.

К счастью, Postgres может создать индекс для выражения, это означает, что вы также можете создать индекс для выражения JSON (например, itemdata->>'itemId').Если вы использовали другие СУБД, которые не могут создать индекс для выражения, вы просто нарисовали себя в углу, поскольку планы выполнения для запросов, использующих JSON, всегда будут разрешать последовательное сканирование таблицы вместо сканирования индекса.

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

Чтобы перейти к следующему этапу, приведем подтверждение концепции, показывающее, что ваш запрос, включающий JSON, все еще можетВоспользуйтесь производительностью, связанной с обычными полями, создав индекс для регулярно используемых свойств JSON.

create table tbl
(
  id int generated by default as identity primary key,
  -- on older version of Postgres, use this instead:
  --   id serial primary key

  document jsonb not null
);


create index ix_tbl__document_code on tbl(((document->>'code')::int));

insert into tbl(document) 
select 
    jsonb_build_object
    ('name', 'Slim', 
     'nick', 'Shady', 
     'category', 'Musician', 
     'ack', x.i, 
     'code', x.i
     )
from generate_series(1,500000) as x(i)

Вот план выполнения при наличии индекса для выражения (document->>'code')::int:

explain analyze
select * from tbl
where (document->>'code')::int = 42;

Вывод: enter image description here

Вот план выполнения, если для выражения не создан индекс:

explain analyze
select * from tbl
where (document->>'ack')::int = 42;

Вывод: enter image description here

0,018 миллисекунд против 52,335 миллисекунд

...