sql >> Database teknologi >  >> RDS >> PostgreSQL

Sequelize tilstand på sammenføjet tabel virker ikke med limit condition

Efter omkring en uges helvede fandt jeg en acceptabel løsning for mit tilfælde. Tror det ville være nyttigt, da jeg fandt en masse ubesvarede emner/problemer på github.

TL;DR; den faktiske løsning er i slutningen af ​​indlægget, kun det sidste stykke kode.

Hovedideen er, at Sequelize bygger korrekt SQL-forespørgsel, men når vi har forladt joinforbindelser, producerer vi kartesiske produkter, så der vil være mange rækker som resultat af forespørgslen.

Eksempel:A- og B-tabeller. Mange til mange forhold. Hvis vi ønsker at få alle A sammen med B, vil vi modtage A * B rækker, så der vil være mange rækker for hver post fra A med forskellige værdier fra B.

OPRET TABEL HVIS IKKE FINDER a ( id HELTAL PRIMÆR NØGLE IKKE NULL, titel VARCHAR) OPRET TABEL HVIS IKKE FINNES b ( id HELTAL PRIMÆR NØGLE IKKE NULL, alder HELTAL) OPRET TABEL, HVIS IKKE FINNES PRIMARY AB ( id HELTAL PRIMARY) KEY NOT NULL, hjælp HELTAL, byd HELTAL)VÆLG *FRA VENSTRE JOIN (ab JOIN b ON b.id =ab.bid) ON a.id =ab.aid 

I efterfølgersyntaks:

klasse A udvider Model {}A.init({ id:{ type:Sequelize.INTEGER, autoIncrement:true, primaryKey:true, }, title:{ type:Sequelize.STRING, },}); klasse B udvider Model {}B.init({ id:{ type:Sequelize.INTEGER, autoIncrement:true, primaryKey:true, }, age:{ type:Sequelize.INTEGER, },});A.belongsToMany(B, { foreignKey:'aid', otherKey:'bid', as:'ab' });B.belongsToMany(A, { foreignKey:'bid', otherKey:'aid', as:'ab' });A. findAll({ distinct:true, include:[{ association:'ab' }],}) 

Alt fungerer ok.

Så forestil dig, at jeg ønsker at modtage 10 poster fra A med tilknyttede poster fra B. Når vi sætter LIMIT 10 på denne forespørgsel, bygger efterfølger den korrekte forespørgsel, men LIMIT anvendes på hele forespørgslen, og som et resultat modtager vi kun 10 rækker, hvor alle af dem kunne kun være for én post fra A. Eksempel:

A.findAll({ distinct:true, include:[{ association:'ab' }], limit:10,}) 

Som vil blive konverteret til:

VÆLG *FRA aLEFT JOIN (ab JOIN b ON b.id =ab.bid) ON a.id =ab.aidLIMIT 10id | titel | id | hjælp | byd | id | alder--- | -------- | ----- | ----- | ----- | ----- | -----1 | først | 1 | 1 | 1 | 1 | 11 | først | 2 | 1 | 2 | 2 | 21 | først | 3 | 1 | 3 | 3 | 31 | først | 4 | 1 | 4 | 4 | 41 | først | 5 | 1 | 5 | 5 | 52 | anden | 6 | 2 | 5 | 5 | 52 | anden | 7 | 2 | 4 | 4 | 42 | anden | 8 | 2 | 3 | 3 | 32 | anden | 9 | 2 | 2 | 2 | 22 | anden | 10 | 2 | 1 | 1 | 1 

Efter at output er modtaget, vil Seruqlize as ORM foretage datamapping og overforespørgselsresultat i kode vil være:

[ { id:1, title:'first', ab:[ { id:1, age:1 }, { id:2, age:2 }, { id:3, age:3 } , { id:4, age:4 }, { id:5, age:5 }, ], }, { id:2, title:'second', ab:[ { id:5, age:5 }, { id:4, age:4 }, { id:3, age:3 }, { id:2, age:2 }, { id:1, age:1 }, ], }]

Åbenbart IKKE hvad vi ønskede. Jeg ønskede at modtage 10 poster for A, men modtog kun 2, mens jeg ved, at der er flere i databasen.

Så vi har korrekt SQL-forespørgsel, men modtog stadig forkert resultat.

Ok, jeg havde nogle ideer, men den nemmeste og mest logiske er:1. Foretag den første anmodning med joins, og grupper resultater efter kildetabel (tabel, som vi laver forespørgsel på, og som gør joins til) 'id'-egenskab. Synes nemt.....

For at gøre det er vi nødt til at give 'gruppe'-egenskaber til Sequelize-forespørgselsmuligheder. Her har vi nogle problemer. Først - Sequelize laver aliaser for hver tabel, mens der genereres SQL-forespørgsel. For det andet - Sequelize sætter alle kolonner fra JOINED-tabellen i SELECT-sætningen af ​​dens forespørgsel, og det hjælper ikke at sende __'attributes' =[]__. I begge tilfælde vil vi modtage SQL-fejl. For at løse først skal vi konvertere Model.tableName til singluar form af dette ord (denne logik er baseret på Sequelize). Bare brug [pluralize.singular()](https://www.npmjs.com/package/pluralize#usage). Komponer derefter den korrekte egenskab til GROUP BY:```tsconst tableAlias ​​=pluralize.singular('Industries') // Industry{ ..., group:[`${tableAlias}.id`]}```For at løse second ( det var det sværeste og det mest ... udokumenterede). Vi skal bruge den udokumenterede egenskab 'includeIgnoreAttributes' =false. Dette vil fjerne alle kolonner fra SELECT-sætningen, medmindre vi angiver nogle manuelt. Vi bør manuelt angive attributter =['id'] på root-forespørgsel. 
  1. Nu vil vi modtage korrekt output med kun nødvendige ressource-id'er. Så er vi nødt til at komponere seconf-forespørgsel UDEN grænse og offset, men specificere yderligere 'where'-sætning:
{ ..., hvor:{ ..., id:Sequelize.Op.in:[array of ids], }} 
  1. Med forespørgsel om kan vi producere korrekt forespørgsel med LEFT JOINS.

Løsning Metode modtager model og original forespørgsel som argumenter og returnerer korrekt forespørgsel + derudover samlet antal poster i DB til paginering. Den analyserer også forespørgselsrækkefølgen korrekt for at give mulighed for at sortere efter felter fra sammenføjede tabeller:

/** * Løsning for Sequelize ulogisk adfærd, når du forespørger med LEFT JOINS og har LIMIT / OFFSET * * Her grupperer vi efter 'id' prop af hovedmodellen (kilde), abd ved at bruge udokumenterede 'includeIgnoreAttributes' * Sequelize prop (den bruges i dens statiske count() metode) for at få korrekt SQL-anmodning * Uden brug af 'includeIgnoreAttributes' er der en masse ekstra ugyldige kolonner i SELECT-sætningen * * Forkert eksempel uden 'includeIgnoreAttributes'. Her vil vi få korrekt SQL-forespørgsel * MEN ubrugelig ifølge forretningslogik:* * VÆLG "Media"."id", "Solutions->MediaSolutions"."mediaId", "Industries->MediaIndustries"."mediaId",.. ., * FRA "Medier" SOM "Medier" * LEFT JOIN ... * HVOR ... * GRUPPER EFTER "Media"."id" * BESTIL EFTER ... * GÆNSE ... * FORSKYDNING ... * * Korrekt eksempel med 'includeIgnoreAttributes':* * VÆLG "Media"."id" * FRA "Medias" SOM "Media" * LEFT JOIN ... * HVOR ... * GRUPPER EFTER "Media"."id" * BESTIL EFTER ... * LIMIT ... * OFFSET ... * * @param model - Kildemodel (nødvendig for at få dens tabelnavn for GROUP BY-indstillingen) * @param-forespørgsel - Parset og klar til brug forespørgselsobjekt */ privat async fixSequeliseQueryWithLeftJoins( model:ModelCtor, query:FindAndCountOptions, ):IMsgPromise<{ query:FindAndCountOptions; total?:antal }> { const fixedQuery:FindAndCountOptions ={ ...forespørgsel }; // Hvis der kun er lejerdata tilsluttet -> returner original forespørgsel if (query.include &&query.include.length ===1 &&(query.include[0] som IncludeOptions).model ===Lejer) { return msg .ok({ query:fixedQuery }); } // Her skal vi sætte det i entalsform, // fordi Sequelize får entalsform for modeller AS aliaser i SQL-forespørgsel const modelAlias ​​=ental(model.tabelnavn); const firstQuery ={ ...fixedQuery, gruppe:[`${modelAlias}.id`], attributter:['id'], rå:sand, includeIgnoreAttributes:falsk, logning:sand, }; // Sortering efter sammenføjet tabelkolonne - ved bestilling efter sammenføjede data skal du tilføje det til gruppen if (Array.isArray(firstQuery.order)) { firstQuery.order.forEach((item) => { if ((item as GenericObject) ).length ===2) { firstQuery.group.push(`${modelAlias}.${(item as GenericObject)[0]}`); } else if ((emne as GenericObject).length ===3 ) { firstQuery.group.push(`${(emne som GenericObject)[0]}.${(emne som GenericObject)[1]}`); } }); } returner model.findAndCountAll(firstQuery) .then((ids) => { if (ids &&ids.rows &&ids.rows.length) { fixedQuery.where ={ ...fixedQuery.where, id:{ [Op.in]:ids.rows.map((item:GenericObject) => item.id), }, }; slet fixedQuery.limit; slet fixedQuery.offset; } /* eslint-disable-next-line */ const total =(ids.tæller som enhver).længde || ids.count; return msg.ok({ query:fixedQuery, total }); }) .catch((err) => this.createCustomError(err)); } 



  1. laravel veltalende sortering efter forhold

  2. Negativ værdifejl i grænsesætning

  3. Er det muligt at bruge pandaer/sqlalchemy til at indsætte arrays i sql-databasen? (postgres)

  4. MySQL INSERT eller REPLACE kommandoer