问题
I am having issues making an ActiveRecord query in Ruby on Rails that gives me back all users, whose blocked_dates
(autogenerated string value) do not intersect with the formatted_dates
(autogenerated string value) of a given event.
The problem I'm having is that, for example:
User.where.not("string_to_array(blocked_dates, ',') && string_to_array(?, ',')", "26.12.2015")
Gives back an empty list, whereby:
User.where("string_to_array(blocked_dates, ',') && string_to_array(?, ',')", "26.12.2015")
Gives back the correct users whose blocked_dates
actually contain '26.12.2015'.
Is there a reason for this strange behavior? Or does the Postgres overlap operator &&
not work in conjunction with NOT
?
In case the question arises, here is the generated SQL query:
SELECT "users".* FROM "users" WHERE (NOT (string_to_array(blocked_dates, ',') && string_to_array('26.12.2015', ',')))
回答1:
Ok, I think I understood the problem I had. The default value of "blocked_dates" of the users table was nil. Because of that, the query was not able to calculate overlaps. After I changed the default value of blocked_dates to "" instead of nil, the NOT statement started giving me the correct values.
回答2:
You must be aware that only WHERE
clause expressions evaluating to TRUE
qualify. When inverting a boolean
value with NOT
, NULL stays NULL and still doesn't qualify. You can use NULL-safe constructs like:
WHERE (string_to_array(blocked_dates, ',')
@> string_to_array('26.12.2015', ',')) IS NOT TRUE
(Using the simpler contains operator @> for your case testing for a single date, btw.)
Or:
WHERE (blocked_dates IS NULL OR
NOT (string_to_array(blocked_dates, ',') @> string_to_array('26.12.2015', ','))
Or, while working with your awkward string:
WHERE (blocked_dates LIKE '%26.12.2015%') IS NOT TRUE
But all of this is putting lipstick on a pig and all the constructs are error-prone and depend on a (hopefully) matching date format. Why the string-to-array conversion in the first place? The column blocked_dates
should at least be an array of dates (date[]
) or, better yet, normalize your relational model with a separate table listing blocked dates instead of the column :users.blocked_dates
CREATE TABLE user_blocked_date (
user_id int REFERENCES users(user_id) ON DELETE CASCADE ON UPDATE CASCADE
, blocked_date date
, PRIMARY KEY (user_id, blocked_date)
);
Depending on data distribution this may or may not occupy more space on disk. But your query would be much faster with one of the standard techniques like:
SELECT *
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM user_blocked_date
WHERE user_id = u.user_id
AND blocked_date = '2015-12-26';
Using ISO 8601 date format, btw. Why?
- How to convert "string" to "timestamp without time zone"
Related:
- Select rows which are not present in other table
来源:https://stackoverflow.com/questions/34810819/find-entry-where-value-does-not-intersect-with-other-value