PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Điều kiện sắp xếp thứ tự trên bảng đã tham gia không hoạt động với điều kiện giới hạn

Sau khoảng một tuần địa ngục đã tìm ra cách giải quyết chấp nhận được cho trường hợp của tôi. Tin rằng nó sẽ hữu ích vì đã tìm thấy rất nhiều chủ đề / vấn đề chưa được giải đáp trên github.

TL; DR; giải pháp thực tế nằm ở cuối bài đăng, chỉ là đoạn mã cuối cùng.

Ý tưởng chính là Sequelize xây dựng truy vấn SQL chính xác, nhưng khi kết hợp bên trái, chúng tôi tạo ra sản phẩm cacte, vì vậy sẽ có rất nhiều hàng là kết quả truy vấn.

Ví dụ:Bảng A và B. Mối quan hệ nhiều đến nhiều. Nếu chúng ta muốn nhận tất cả A được nối với B, chúng ta sẽ nhận được hàng A * B, vì vậy sẽ có rất nhiều hàng cho mỗi bản ghi từ A với các giá trị khác nhau từ B.

CREATE TABLE IF NOT EXISTS a (
    id INTEGER PRIMARY KEY NOT NULL,
    title VARCHAR
)

CREATE TABLE IF NOT EXISTS b (
    id INTEGER PRIMARY KEY NOT NULL,
    age INTEGER
)

CREATE TABLE IF NOT EXISTS ab (
    id INTEGER PRIMARY KEY NOT NULL,
    aid INTEGER,
    bid INTEGER
)

SELECT *
FROM a
LEFT JOIN (ab JOIN b ON b.id = ab.bid) ON a.id = ab.aid

Trong cú pháp tiếp theo:

class A extends Model {}
A.init({
    id: {
      type: Sequelize.INTEGER,
      autoIncrement: true,
      primaryKey: true,
    },
    title: {
      type: Sequelize.STRING,
    },
});

class B extends 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’ }],
})

Mọi thứ hoạt động tốt.

Vì vậy, hãy tưởng tượng tôi muốn nhận 10 bản ghi từ A với các bản ghi được ánh xạ tới chúng từ B.Khi chúng tôi đặt LIMIT 10 cho truy vấn này, Sequelize xây dựng truy vấn chính xác nhưng LIMIT được áp dụng cho toàn bộ truy vấn và kết quả là chúng tôi chỉ nhận được 10 hàng, trong đó tất cả trong số chúng chỉ có thể dành cho một bản ghi từ A. Ví dụ:

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

Điều này sẽ được chuyển đổi thành:

SELECT *
FROM a
LEFT JOIN (ab JOIN b ON b.id = ab.bid) ON a.id = ab.aid
LIMIT 10

id  |  title    |   id  |  aid  |  bid  |  id   |  age
--- |  -------- | ----- | ----- | ----- | ----- | -----
1   |   first   |   1   |   1   |   1   |   1   |   1
1   |   first   |   2   |   1   |   2   |   2   |   2
1   |   first   |   3   |   1   |   3   |   3   |   3
1   |   first   |   4   |   1   |   4   |   4   |   4
1   |   first   |   5   |   1   |   5   |   5   |   5
2   |   second  |   6   |   2   |   5   |   5   |   5
2   |   second  |   7   |   2   |   4   |   4   |   4
2   |   second  |   8   |   2   |   3   |   3   |   3
2   |   second  |   9   |   2   |   2   |   2   |   2
2   |   second  |   10  |   2   |   1   |   1   |   1

Sau khi nhận được kết quả đầu ra, Seruqlize as ORM sẽ thực hiện ánh xạ dữ liệu và kết quả truy vấn trong mã sẽ là:

[
 {
  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 },
  ],
 }
]

Rõ ràng KHÔNG phải những gì chúng tôi muốn. Tôi muốn nhận 10 bản ghi cho A, nhưng chỉ nhận được 2 bản, trong khi tôi biết rằng có nhiều bản ghi trong cơ sở dữ liệu.

Vì vậy, chúng tôi đã truy vấn SQL chính xác nhưng vẫn nhận được kết quả không chính xác.

Được rồi, tôi đã có một số ý tưởng nhưng dễ nhất và hợp lý nhất là:1. Đưa ra yêu cầu đầu tiên với các phép nối và nhóm kết quả theo bảng nguồn (bảng mà chúng tôi đang thực hiện truy vấn và bảng nào tạo các phép nối) thuộc tính 'id'. Có vẻ dễ .....

To make so we need to provide 'group' property to Sequelize query options. Here we have some problems. First - Sequelize makes aliases for each table while generating SQL query. Second - Sequelize puts all columns from JOINED table into SELECT statement of its query and passing __'attributes' = []__ won't help. In both cases we'll receive SQL error.

To solve first we need to convert Model.tableName to singluar form of this word (this logic is based on Sequelize). Just use [pluralize.singular()](https://www.npmjs.com/package/pluralize#usage). Then compose correct property to GROUP BY:
```ts
const tableAlias = pluralize.singular('Industries') // Industry

{
 ...,
 group: [`${tableAlias}.id`]
}
```

To solve second (it was the hardest and the most ... undocumented). We need to use undocumented property 'includeIgnoreAttributes' = false. This will remove all columns from SELECT statement unless we specify some manually. We should manually specify attributes = ['id'] on root query.
  1. Bây giờ chúng tôi sẽ nhận được đầu ra chính xác chỉ với id tài nguyên cần thiết. Sau đó, chúng ta cần soạn truy vấn seconf KHÔNG có giới hạn và bù trừ, nhưng chỉ định thêm mệnh đề 'where':
{
 ...,
 where: {
  ...,
  id: Sequelize.Op.in: [array of ids],
 }
}
  1. Với truy vấn về chúng tôi có thể đưa ra truy vấn chính xác với THAM GIA TRÁI.

Giải pháp Phương thức nhận mô hình và truy vấn gốc dưới dạng đối số và trả về truy vấn đúng + bổ sung tổng số bản ghi trong DB để phân trang. Nó cũng phân tích cú pháp chính xác thứ tự truy vấn để cung cấp khả năng sắp xếp theo các trường từ các bảng đã kết hợp:

/**
   *  Workaround for Sequelize illogical behavior when querying with LEFT JOINS and having LIMIT / OFFSET
   *
   *  Here we group by 'id' prop of main (source) model, abd using undocumented 'includeIgnoreAttributes'
   *  Sequelize prop (it is used in its static count() method) in order to get correct SQL request
   *  Witout usage of 'includeIgnoreAttributes' there are a lot of extra invalid columns in SELECT statement
   *
   *  Incorrect example without 'includeIgnoreAttributes'. Here we will get correct SQL query
   *  BUT useless according to business logic:
   *
   *  SELECT "Media"."id", "Solutions->MediaSolutions"."mediaId", "Industries->MediaIndustries"."mediaId",...,
   *  FROM "Medias" AS "Media"
   *  LEFT JOIN ...
   *  WHERE ...
   *  GROUP BY "Media"."id"
   *  ORDER BY ...
   *  LIMIT ...
   *  OFFSET ...
   *
   *  Correct example with 'includeIgnoreAttributes':
   *
   *  SELECT "Media"."id"
   *  FROM "Medias" AS "Media"
   *  LEFT JOIN ...
   *  WHERE ...
   *  GROUP BY "Media"."id"
   *  ORDER BY ...
   *  LIMIT ...
   *  OFFSET ...
   *
   *  @param model - Source model (necessary for getting its tableName for GROUP BY option)
   *  @param query - Parsed and ready to use query object
   */
  private async fixSequeliseQueryWithLeftJoins<C extends Model>(
    model: ModelCtor<C>, query: FindAndCountOptions,
  ): IMsgPromise<{ query: FindAndCountOptions; total?: number }> {
    const fixedQuery: FindAndCountOptions = { ...query };

    // If there is only Tenant data joined -> return original query
    if (query.include && query.include.length === 1 && (query.include[0] as IncludeOptions).model === Tenant) {
      return msg.ok({ query: fixedQuery });
    }

    // Here we need to put it to singular form,
    // because Sequelize gets singular form for models AS aliases in SQL query
    const modelAlias = singular(model.tableName);

    const firstQuery = {
      ...fixedQuery,
      group: [`${modelAlias}.id`],
      attributes: ['id'],
      raw: true,
      includeIgnoreAttributes: false,
      logging: true,
    };

    // Ordering by joined table column - when ordering by joined data need to add it into the group
    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 ((item as GenericObject).length === 3) {
          firstQuery.group.push(`${(item as GenericObject)[0]}.${(item as GenericObject)[1]}`);
        }
      });
    }

    return model.findAndCountAll<C>(firstQuery)
      .then((ids) => {
        if (ids && ids.rows && ids.rows.length) {
          fixedQuery.where = {
            ...fixedQuery.where,
            id: {
              [Op.in]: ids.rows.map((item: GenericObject) => item.id),
            },
          };
          delete fixedQuery.limit;
          delete fixedQuery.offset;
        }

        /* eslint-disable-next-line */
        const total = (ids.count as any).length || ids.count;

        return msg.ok({ query: fixedQuery, total });
      })
      .catch((err) => this.createCustomError(err));
  }



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Ngủ đông chậm để có được kết nối Postgres

  2. Khi nào thì Kết nối bị đóng khi gọi .close () của JooQ DSLContext, nếu có?

  3. Ứng dụng Heroku push of django không có mô-đun nào có tên psycopg2.extensions

  4. Lỗi giao thức PostgreSQL. Thiết lập phiên không thành công .. lỗi

  5. Chọn tất cả các bản ghi bằng cách sử dụng SQL LIMIT và truy vấn OFFSET