简体   繁体   中英

MySQL to PostgreSQL - Find in array, and count array values

I've a colors table and an items table, with a many to many relation between these 2 tables (via an items_colors table). An item can have many colors and a color can have many items.

items
   id

colors
   id
   name

items_colors
    item_id [foreign key: items(id)]
    color_id [foreign key: colors(id)]

I'm currently migrating from MySQL to PostgreSQL . This SQL query below is working well with MySQL, but I'm not able to make it works on PostgreSQL:

SELECT i.*
FROM
    items i
    JOIN items_colors ic ON ic.item_id = i.id
    JOIN colors c ON c.id = ic.color_id
GROUP BY i.id
HAVING COUNT(*) = SUM( c.name IN ('green', 'blue') )

A also tried to express the HAVING differently:

HAVING SUM( c.name NOT IN ('green', 'blue') ) = 0;

In all cases, with PostgreSQL I'm getting this error:

Query 1 ERROR: ERROR:  function sum(boolean) does not exist
LINE 7: HAVING COUNT(*) = SUM( c.name IN ('green'...
                          ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.

(Just for the context and the goal of this query, I want to get all items that only match one or more provided colors (from a provided array of colors). If an item is also associated with an additional color that is not specified in the array, it should not be retrieved. In my example above I'm getting all items that match the given array, so all items that have green, or blue, or green and blue color(s). But if an item has the blue and also the red colors (or only red, or no color), it is excluded from the results.)

This should work in Postgres, assuming that items.id is the primary key:

SELECT i.*
FROM items i JOIn
     items_colors ic
     ON ic.item_id = i.id JOIN
     colors c
     ON c.id = ic.color_id
GROUP BY i.id
HAVING COUNT(*) = SUM( (c.name IN ('green', 'blue'))::INT );

Or:

HAVING COUNT(*) = COUNT(*) FILTER (WHERE c.name IN ('green', 'blue'))

A version that works in both databases is:

HAVING COUNT(*) = SUM(CASE WHEN c.name IN ('green', 'blue') THEN 1 ELSE 0 END);

I would take a look at the array operators in PostgreSQL:

SELECT i.*
FROM
    items i
    JOIN items_colors ic ON ic.item_id = i.id
    JOIN colors c ON c.id = ic.color_id
GROUP BY i.id
HAVING ARRAY_AGG(c.name) <@ ARRAY['green', 'blue'];

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM