LINUX.ORG.RU

Как вернуть длинный ряд после join?

 ,


0

2

Hello, LOR

Пишу веб сервис на typescript. Прислушавшись к громкому меньшинству, принял решение не испоьзовать ORM, а писать прям SQL-запросы (строковыми литералами).

Сперва все пошло более-менее ровно, пока не появился JOIN. Та самая хваленая оптимизация, делающая один запрос там, где какая-нибудь ORM делает N+1

Условно, есть таблица «товары» и таблица «свойства_товара». Наивным способом сначала загружаем список товаров, затем в цикле SELECT-им свойства каждого по uid.

Но мы же крутые. Обладая всей мощью SQL, делаем «товары LEFT JOIN свойства_товара». И получаем.. Херню мы получаем! Длинный ряд столбцов. С повторяющимися именами (в этих таблицах есть одноименные столбцы). А с учетом того, что заботливый драйвер postgres превращает ряды в словари, одинаковые имена просто схлопываются

Погуглил что предлагает коллективный надмозг. Надмозг предлагает жесть. Разделять столбцы разделителями и потом програмно собирать эту змею в объекты:

---- foo, bar из первой таблицы. baz, bang из второй
SELECT foo, bar, '|', baz, bang

Подскажите, есть ли более лучший способ? Postgresql декларируется как фичастый и объектный. Должен же быть способ получше? Как вернуть из базы ответ в более структурированном виде? Может есть способ при селекте как-то скомбинировать поля и вернуть одним полем. Или еще как-то?



Последнее исправление: pornandme (всего исправлений: 1)
Ответ на: комментарий от pornandme
SELECT goods.*, shoes.*

Зойчемъ?! Делай выборку только нужного с INNER JOIN.

Ну или юзай ORM, не слушай тех кому оно не нужно «а значит никому не нужно!»

mord0d ★★★★★
()

где какая-нибудь ORM делает N+1

Batching? Dataloader? Не, не слышали.

в этих таблицах есть одноименные столбцы

писать прям SQL-запросы

Продолжай работать руками, именуй каждый столбец уникально. SELECT goods.thing AS goodsThing, shoes.thing AS shoesThing, ...

заботливый драйвер postgres превращает ряды в словари

А нормального драйвера нет?

no-such-file ★★★★★
()
Ответ на: комментарий от mord0d

Зойчемъ?!

Сути не меняет. В сабже пример. В реальном коде, в JOIN-еных таблицах есть одноименные поля, которые нужны в выборке. Пока обхожусь «name AS other_name», но это хрупко и утомительно

Ну или юзай ORM. Не слушай тех кому оно не нужно

Нет проблем заюзать ORM. Просто интересно покопаться в фичах postgres-a (там много интересного). Проект предоставил такую возможность

pornandme
() автор топика
Ответ на: комментарий от no-such-file

Продолжай работать руками, именуй каждый столбец уникально.

Лучше сразу создать VIEW и дёргать его, чем городить каждый раз длинные запросы извне.

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

Сути не меняет.

Ещё как меняет!

в JOIN-еных таблицах есть одноименные поля, которые нужны в выборке

Тебе выше уже порекомендовали SELECT ... AS ....

но это хрупко

Чойта?

и утомительно

CREATE VIEW.

mord0d ★★★★★
()
Ответ на: комментарий от no-such-file

Batching

Я в чистом SQL профан. Батчинг всмысле скопом отпраить 1к селектов, по каждому uid?

Продолжай работать руками

Так и делаю но утомительно, честно говоря. По любому должен быть способ сгрупировать столбцы в одно поле. Это же восхваляемый всеми postgres.. Попробовал

[code=sql] SELECT ARRAY[uid, price] from goods; [/code]

ругается, что в массиве элементы разных типов

А нормального драйвера нет

А я в целом согласен с драйвером. Не руками же списки в словари превращать

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

длинный плоский ряд

Тебе шашечки или ехать? В коде условные arr[1], arr[2], arr[3] или условный arr.sub[1][3], arr.sub[2][1] — одна фигня. Главное — пиши сразу стабильно, иначе при перепиливании сам себе всё сломаешь.

А ещё в pg есть функции. Но я их не тыкал, потому подробнее не подскажу.

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

Батчинг всмысле скопом отпраить 1к селектов, по каждому uid?

Нет, это когда набираешь id и делаешь select ... where id in (...). В твоём случае получится 2 запроса вместо N+1. Ещё не известно, что быстрее выполнится – 2 таких запроса или твой join. Это к слову про «ненужно ORM».

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

набираешь id и делаешь select … where id in (…)

Интересный вариант. Запрос правда будет. 1000 uid вида «‘26-chars’,» это 290 килобайт. Это вообще законно?)

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

1000 uid вида «‘26-chars’,» это 290 килобайт

Это ты как посчитал? 26 символов даже по 4 байта это ~100k. Хотя непонятно почему uid должен быть многобайтным. И почему тебе нужно за один раз 1000 строк.

Это вообще законно?

Естественно.

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

Это ты как посчитал?

Херово посчитал)

И почему тебе нужно за один раз 1000 строк

В админку. На fullhd монитор 3 сотни пиктограм с товарами в легкую умещается

pornandme
() автор топика
Ответ на: комментарий от level1

Скажи тогда, а зачем СУБД изкожи вон лезут, встраивая разные супер фичи, структуры данных, операторы, OLAP. Если ORM не воспользуется и десятой долей этой крутизны. Как юзать все эти супер фишки, если не руками в коде?

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

Как юзать все эти супер фишки, если не руками в коде?

Если у тебя большое приложение, то не надо использовать все фишки, пусть полежат отдохнут. Для тебя важнее поддерживаемость кода. Со временем у тебя обязательно будет меняться структура данных, и каждый раз бегать по всему коду, переименовывая атрибуты, жизни не хватит. Как в этом случае делает орм? - она смотрит на декларацию атрибутов в коде, и сама для тебя составляет запрос вида t1.a1 as ‘t1.a1’. Потом вытягивает результат в код и там разносит по сущностям. Дальше ты свободно добавляешь, удаляешь, меняешь имена атрибутов.

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

Doubt

О! Шпрехен зи дойч, Иван Андреич. (с)

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

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

Если ORM не воспользуется и десятой долей этой крутизны

ORM - это чтобы на модель данных мапить.
Кто сказал, что всё остальное не нужно руками делать?

И кто сказал, что если пишешь хитровыдуманные запросы, то не нужно мапить результаты с помощью ORM??

Shadow ★★★★★
()

Та самая хваленая оптимизация, делающая один запрос там, где какая-нибудь ORM делает N+1

Лор, поясни плиз, когда у тебя например 20 колонок слева и две справа, причем справа строк в десять раз больше (например по десять юзер-свойств на каждый итем), это вообще хотя бы на сервере сворачивается, или все летит отдельными строками? И зачем так делать в общем виде, и когда/как ты решаешь, что лучше N+1 и не выделываться, и собираешь руками на клиенте? Почему вообще в sql’ях не принято возвращать деревья по джойну, это же было бы удобнее во всех отношениях?

заботливый драйвер postgres превращает ряды в словари, одинаковые имена просто схлопываются

Бывает что драйвер умеет в полные имена сам, и даже собирает их в отдельные объекты, как better-sqlite3 например (stmt.expand()). Но твой похоже не умеет, возможно время патчить.

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

Я уже думаю, может вообще не заморачиваться. Отдавать таблицы как есть на клиент, а там пусть редакс страдает

pornandme
() автор топика
Ответ на: комментарий от anonymous

Почему вообще в sql’ях не принято возвращать деревья по джойну

Может быть потому что SQL это про СУРБД? В реляционной модели не бывает деревьев. Первое правило Кодда.

Бывают нереляционные БД, с многомерными значениями в полях. Но тогда это не про SQL )

Лично мне, когда это возможно, удобно делать поля с битовыми масками. Это тоже нарушение реляционных правил. Зато не надо городить связь с таблицами. Получается такая многомерность внутри двумерности. Ту же категорию shoes из примера ТСа можно закодировать в биты зимние/летние, мужские/женские и т.п. и хранить в одном поле. Сомневаюсь что это best practices, просто мне так удобнее.

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

Бывают нереляционные БД, с многомерными значениями в полях. Но тогда это не про SQL )

Точно бывают? Очень хотелоь бы транзакции, связность и всё такое прочее, но без SQL

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

Точно )

Я большую часть жизни сидел в постреляционной БД PickSystems. Оно несколько раз умирало и перепродавалось новым владельцам. Вот не знаю в каком оно состоянии сейчас - попробуйте посмотреть тут https://www.rocketsoftware.com/products/rocket-d3. Может еще живое.

Почти наверняка есть и более популярные решения, которых я не знаю.

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

причем справа строк в десять раз больше (например по десять юзер-свойств на каждый итем)

Главный вопрос: зачем ты это джойнишь к большой таблице?????
Любой источник данных обычно делает несколько маленьких запросов - если ты хочешь дерево, то ты его можешь хранить где угодно, RDBMS тебе не для этого дана - реляции дёшевы, запросы кешируются, пользуйся.

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

Из того, что есть в стандартной поставке убунты нашел только OpenLink Virtuoso

pornandme
() автор топика
Ответ на: комментарий от mord0d

Лучше сразу создать VIEW и дёргать его, чем городить каждый раз длинные запросы извне

Но чтобы создать вьюшку, всёравно ведь надо нагородить этот запрос. Нет?

deep-purple ★★★★★
()
Ответ на: комментарий от pornandme

Да сделай ты уже два запроса — один за основной, второй за свойствами. Это ж сраные копейки.

deep-purple ★★★★★
()
Ответ на: комментарий от pornandme

В админку. На fullhd монитор 3 сотни пиктограм с товарами в легкую умещается

Не забудь плашку повесить красную для тех кто с мобилы зайдёт: «Тут со смарта ничерта не получится, потому что потому!»

deep-purple ★★★★★
()
Ответ на: комментарий от pornandme

Отдавать таблицы как есть на клиент, а там пусть редакс страдает

И на каждую кнопку и ссылку ещё вдобавок по отдельному инстансу жквери, скачанных с разных цэдээнов, повесь — хуже уже не будет.

deep-purple ★★★★★
()
Ответ на: комментарий от Toxo2

категорию shoes из примера ТСа можно закодировать в биты

Биты быстро кончаются. Или ты писал только микромагазинчики на пару товаров?

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

Я писал региональный банк данных по организованной преступности Западной Сибири ) Это, пожалуй, самое больше из того что писал. По крайней мере - территориально. РУОП же. 90-е же. Надо же было кому-то бандитов по головам считать.

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

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

«реляции» это что?

Это значит — реальные яйца, а не вот это вот всё.

deep-purple ★★★★★
()
Ответ на: комментарий от Toxo2

по нескольким льготам претендовать

Вот пока всего по нескольким оно и прокатывает. Я тоже использую битовые маски. Но предварительно всё продумываю — хватит ли их если через пару лет придётся расширить. Даже если не мне.

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

про нормальные формы не знают

Картинка с Гигачедом:
- да, я слышал про нормальные формы, а не в pdf

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

Понимаете ли, в этой фразе не связи в виде таблиц, а связи описаны в таблицах, например.

Shadow ★★★★★
()
$ node
Welcome to Node.js v12.19.0.
Type ".help" for more information.
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
const { Client } = require('pg');
const cnn = new Client('postgres://postgres:password@127.0.0.1:5434');
cnn.connect()
.then(() => cnn.query(`
    with core(id,name) as (
        values(1, 'ORM'), (2, 'SQL')
    ),
    properties(id, name) as (
        values(1, 'useless'),(2, 'just works'),(1, 'slow'),(2, 'fast')
    )
    select c.id, c.name, array_agg(p.name) props from core c
    left join properties p on c.id = p.id
    group by 1,2`
))
.then((x) => console.log(x.rows));

Promise { <pending> }
> [
  { id: 2, name: 'SQL', props: [ 'just works', 'fast' ] },
  { id: 1, name: 'ORM', props: [ 'useless', 'slow' ] }
]
drsm ★★
()
Ответ на: комментарий от no-such-file

«Отношение», «Кортеж», «Атрибут» - это таблица, строка, столбец, соответственно.

Дальше сам, пожалуйста, образовывайся.

level1 ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.