Как проверять TV-поля одновр. и на '', и на NULL
Для определённости рассмотрим функционал сниппета pdoResources.
Проблема заключается в том, что классическим форматом JSONxPDO в параметре &where в общем случае невозможно указать проверку значения некоторого TV-поля одновременно и на пустое значение, и на NULL. В каких случаях может понадобиться такая проверка — напишу ниже.
Для примера возьмём параметр introtext (в БД modX это поле может принимать значения NULL)
Казалось бы, чего проще:
Но даже если мы в коде сниппета pdoResources будем заключать пользовательские условия в скобки, то и здесь остаются проблемы в тех случаях, когда наше условие будет включать и другие параметры. Например, нам нужно отобрать ресурсы, у которых аннотация не указана и которые имеют шаблон «frog.tpl». Как задать это условие?
Если записать так:
========================================================================================
По большому счёту, префикс «OR» в составе JSON-ключа вообще не слудует использовать, т.к. в общем случае мы получим неверную логику. Вообще, я не понимаю, как можно было добавлять возможность указания префиксов «OR» и «AND» без возможности указания логических скобок (речь о разработчиках modX).
========================================================================================
Теперь ситуации, в которых необходима одновременная проверка значения поля и на пустую строку, и на NULL.
1. Стандартные TV-параметры
===============================
В таблице modx_site_tmplvar_contentvalues имеется 4 поля, для которых допустимы значения NULL:
Что мы имеем:
а) при создании нового ресурса modX этим полям присваивает не NULL, а пустые значения (''). При очищении этих полей значение поля в БД также принимает пустое значение (''). Т.е. если работать с чистым modX, то можно обойтись и одной проверкой — на пустую строку ('').
б) при импорте ресурсов с помощью компонента importX указанные поля принимают значения NULL (это значение по умолчанию).
в) не исключено, и скорее всего, многие другие компоненты/плагины/сниппеты, которые создают новые ресурсы, тоже инициализируют значения этих полей NULL'ами (вернее, не трогают их, и как следствие, они принимают значение по умолчанию — NULL).
Поскольку:
а) в общем случае нам неизвестно, импортировались ли ресурсы, создавались ли ресурсы сторонними компонентами и какое значение имеют поля, для которых значения явно не указывались: пустая строка или NULL;
б) в общем случае нам неизвестно, есть ли в таблице site_content поля, для которых допустимы значения NULL и что это за поля;
в) наличие и состав полей, которые могут иметь значение NULL, от версии к версии modX могут меняться.
то при отборе ресурсов, для которых не указано значение некоторого поля, необходимо предусматривать одновременную проверку и на пустую строку, и на NULL. Причём для всех стандартных TV-параметров.
2. Пользовательские TV-параметры
=====================================
Что мы имеем:
а) в таблице site_tmplvar_contentvalues поле value НЕ может иметь значения NULL, и присутствуют в этой таблице только непустые значения. Тем не менее, при формировании результирующего запроса в pdoResources таблица site_tmplvar_contentvalues присоединяется к основной таблице через LEFT JOIN. В итоге для тех пользовательских TV-параметров, которые не имеют значения, в результирующем запросе мы получим NULL. Т.е. в случае с TV-параметрами вместо сравнения с пустой строкой необходимо выполнять проверку на NULL.
б) опять-таки некоторые сторонние компоненты/плагины/сниппеты могут заносить в таблицу site_tmplvar_contentvalues пустые строки (на уровне БД пустые строки разрешены).
Таким образом, и в случае с пользовательскими TV-параметрами нам необходимо выполнять одновременную проверку и на пустую строку, и на NULL.
Проблема заключается в том, что классическим форматом JSONxPDO в параметре &where в общем случае невозможно указать проверку значения некоторого TV-поля одновременно и на пустое значение, и на NULL. В каких случаях может понадобиться такая проверка — напишу ниже.
Для примера возьмём параметр introtext (в БД modX это поле может принимать значения NULL)
Казалось бы, чего проще:
&where=`{"introtext":"", "OR:introtext:IS":null}`
Но проблема здесь в том, что в конечном запросе перед нашим условием в преобладающем большинстве случаев будут стоять ещё и другие условия, которые де факто будут объединены с нашим условием логикой «AND», причём наше условие в скобки взято не будет. В итоге в конечном запросе мы получим следующее условие:WHERE (<i>прочие условия</i> AND `modResource`.`introtext` = '' OR `modResource`.`introtext` IS NULL)
Как видим, логика нарушена.Но даже если мы в коде сниппета pdoResources будем заключать пользовательские условия в скобки, то и здесь остаются проблемы в тех случаях, когда наше условие будет включать и другие параметры. Например, нам нужно отобрать ресурсы, у которых аннотация не указана и которые имеют шаблон «frog.tpl». Как задать это условие?
Если записать так:
&where=`{"introtext":"", "OR:introtext:IS":null, "template":"frog.tpl"}`
то мы получим неверную логику:`modResource`.`introtext` = '' OR `modResource`.`introtext` IS NULL AND `modResource`.`template` = 'frog.tpl')
Нужны скобки. А скобки в JSONxPDO-формате не предусмотрены.========================================================================================
По большому счёту, префикс «OR» в составе JSON-ключа вообще не слудует использовать, т.к. в общем случае мы получим неверную логику. Вообще, я не понимаю, как можно было добавлять возможность указания префиксов «OR» и «AND» без возможности указания логических скобок (речь о разработчиках modX).
========================================================================================
Теперь ситуации, в которых необходима одновременная проверка значения поля и на пустую строку, и на NULL.
1. Стандартные TV-параметры
===============================
В таблице modx_site_tmplvar_contentvalues имеется 4 поля, для которых допустимы значения NULL:
introtext
content
uri
properties
Что мы имеем:
а) при создании нового ресурса modX этим полям присваивает не NULL, а пустые значения (''). При очищении этих полей значение поля в БД также принимает пустое значение (''). Т.е. если работать с чистым modX, то можно обойтись и одной проверкой — на пустую строку ('').
б) при импорте ресурсов с помощью компонента importX указанные поля принимают значения NULL (это значение по умолчанию).
в) не исключено, и скорее всего, многие другие компоненты/плагины/сниппеты, которые создают новые ресурсы, тоже инициализируют значения этих полей NULL'ами (вернее, не трогают их, и как следствие, они принимают значение по умолчанию — NULL).
Поскольку:
а) в общем случае нам неизвестно, импортировались ли ресурсы, создавались ли ресурсы сторонними компонентами и какое значение имеют поля, для которых значения явно не указывались: пустая строка или NULL;
б) в общем случае нам неизвестно, есть ли в таблице site_content поля, для которых допустимы значения NULL и что это за поля;
в) наличие и состав полей, которые могут иметь значение NULL, от версии к версии modX могут меняться.
то при отборе ресурсов, для которых не указано значение некоторого поля, необходимо предусматривать одновременную проверку и на пустую строку, и на NULL. Причём для всех стандартных TV-параметров.
2. Пользовательские TV-параметры
=====================================
Что мы имеем:
а) в таблице site_tmplvar_contentvalues поле value НЕ может иметь значения NULL, и присутствуют в этой таблице только непустые значения. Тем не менее, при формировании результирующего запроса в pdoResources таблица site_tmplvar_contentvalues присоединяется к основной таблице через LEFT JOIN. В итоге для тех пользовательских TV-параметров, которые не имеют значения, в результирующем запросе мы получим NULL. Т.е. в случае с TV-параметрами вместо сравнения с пустой строкой необходимо выполнять проверку на NULL.
б) опять-таки некоторые сторонние компоненты/плагины/сниппеты могут заносить в таблицу site_tmplvar_contentvalues пустые строки (на уровне БД пустые строки разрешены).
Таким образом, и в случае с пользовательскими TV-параметрами нам необходимо выполнять одновременную проверку и на пустую строку, и на NULL.
Комментарии: 6
Насколько я понимаю, начиная с версии 1.5.0-pl всегда будет возвращаться "", вместо NULL. То есть, проверять нужно только на "".
Если не трудно, посмотришь?
Если не трудно, посмотришь?
Ну вот, только руки дошли.
В общем, получается так. В предложение SELECT Вы добавили проверку IFNULL для всех дополнительных TV-полей:
Но осталось ещё добавить IFNULL:
1) в предложение SELECT для стандартных полей `properties`, `uri`, `introtext`, `alias` — эти поля также могут принимать значения null
2) в предложение WHERE для всех дополнительных TV-полей.
Например, при указании в параметре &where JSON-условия {..., «tv1»:"", ...} в предложении WHERE должно формироваться условие:
Необходимости добавления IFNULL в предложении WHERE (последние два пункта) можно избежать, если все проверки вытащить во внешний запрос: во внутреннем запросе выполняются JOIN'ы необходимых таблиц и отбираются все необходимые поля с использованием IFNULL, а во внешнем запросе выполняются все необходимые проверки. Но в этом случае памяти может требоваться больше. Если же без использования внешнего запроса — будет дольше выполняться. Впрочем, нужно тестировать.
В общем, получается так. В предложение SELECT Вы добавили проверку IFNULL для всех дополнительных TV-полей:
IFNULL(`TVtv1`.`value`, '')
В итоге при заполнении чанка-шаблона значениями TV-полей туда будут «вставляться» не null, а пустые строки.Но осталось ещё добавить IFNULL:
1) в предложение SELECT для стандартных полей `properties`, `uri`, `introtext`, `alias` — эти поля также могут принимать значения null
2) в предложение WHERE для всех дополнительных TV-полей.
Например, при указании в параметре &where JSON-условия {..., «tv1»:"", ...} в предложении WHERE должно формироваться условие:
... AND IFNULL(`TVtv1`.`value`, '') = '' AND ...
3) в предложении WHERE для стандартных полей `properties`, `uri`, `introtext`, `alias`Необходимости добавления IFNULL в предложении WHERE (последние два пункта) можно избежать, если все проверки вытащить во внешний запрос: во внутреннем запросе выполняются JOIN'ы необходимых таблиц и отбираются все необходимые поля с использованием IFNULL, а во внешнем запросе выполняются все необходимые проверки. Но в этом случае памяти может требоваться больше. Если же без использования внешнего запроса — будет дольше выполняться. Впрочем, нужно тестировать.
Да, и касательно IFNULL. В целях сохранения переносимости я бы сделал так:
switch($modx->getOption('dbtype')) {
case 'mysql': $dbObjectNameBorder = '`'; $nullCheckExpr = 'IFNULL'; break;
case 'sqlsrv': $dbObjectNameBorder = ''; $nullCheckExpr = 'ISNULL'; break;
}
и далее использовал $dbObjectNameBorder и $nullCheckExpr. Такой вариант будет работать и с mySQL, и с MS SQL.
И ещё одна оптимизация. В предложении WHERE (как для стандартных полей `properties`, `uri`, `introtext`, `alias`, так и для пользовательских TV-полей) добавлять проверку на NULL (IFNULL/ISNULL) достаточно только в 2 случаях:
1. Если поле сравнивается с некоторым значением операцией «не равно»:
2. Если поле сравнивается с пустым значением:
Во всех остальных случаях подстановка IFNULL/ISNULL не нужна. Все прочие условия будут работать корректно и без IFNULL/ISNULL.
— Почему в предложении WHERE важно не добавлять IFNULL/ISNULL в безусловном порядке для всех полей, а делать это только для двух вышеуказанных случаев:
а) ускорение выполнения запроса (т.к. каждый IFNULL/ISNULL увеличивает время выполнения запроса)
б) если IFNULL/ISNULL ставить всегда, то пользователь не будет иметь возможности корректно использовать оператор is null:
1. Если поле сравнивается с некоторым значением операцией «не равно»:
..., "tv1:!=":"5", ...
Сейчас такому условию НЕ будут удовлетворять ресурсы, для которых значение поля tv1 не указано. А должны удовлетворять.2. Если поле сравнивается с пустым значением:
..., "tv1:=":"", ...
..., "tv1":"", ...
Сейчас такому условию НЕ будет удовлетворять ни один ресурс. А должны удовлетворять все ресурсы, для которых не задано значение поля tv1.Во всех остальных случаях подстановка IFNULL/ISNULL не нужна. Все прочие условия будут работать корректно и без IFNULL/ISNULL.
— Почему в предложении WHERE важно не добавлять IFNULL/ISNULL в безусловном порядке для всех полей, а делать это только для двух вышеуказанных случаев:
а) ускорение выполнения запроса (т.к. каждый IFNULL/ISNULL увеличивает время выполнения запроса)
б) если IFNULL/ISNULL ставить всегда, то пользователь не будет иметь возможности корректно использовать оператор is null:
..., "tv1:IS":"null", ...
Этот оператор ничего не будет возвращать, т.к. в итоговом запросе вместо (tv1 IS NULL) получим (tv1 IS '').
Самую последнюю строчку читать так:
Этот оператор ничего не будет возвращать для tv, не содержащих значений, т.к. в итоговом запросе вместо (tv IS NULL) получим ('' IS NULL)
trash
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.