SQL query not selecting property - mysql

I have query like this
SELECT *
FROM `GSheets`
WHERE `sheetcat` = 'Unsubscribed'
AND `sheetcat` IS NOT NULL
AND `user` LIKE '%,r00t,%'
OR `user` LIKE 'r00t,%'
OR '%,r00t'
OR `user` = 'r00t'
I specify sheetcat to be Unsubscribed, but query response is NULL (or blank)?
Why?

This is a problem when you store lists of things in delimited strings. Bad, bad, bad data design. The first and primary advice is to create a new table with one row per sheet and one row per user.
Now, sometimes we are stuck with other people's really bad, bad, bad design decisions. MySQL offers find_in_set() which does what you want:
SELECT g.*
FROM `GSheets` g
WHERE `sheetcat` = 'Unsubscribed' AND
FIND_IN_SET('r00t', `user`) > 0;
Note that sheetcat IS NOT NULL is redundant. A NULL value would fail the first condition. Unless you possibly intend:
SELECT g.*
FROM `GSheets` g
WHERE `sheetcat` = 'Unsubscribed' OR
(sheetcat IS NOT NULL AND FIND_IN_SET('r00t', `user`) > 0);
But that would not be my first guess as to your intention.
Your query would also work if you put parentheses around the OR conditions. But even that is too complicated. A simpler version using LIKE would be:
WHERE `sheetcat` = 'Unsubscribed' AND
CONCAT(',', `user`, ',') LIKE '%,r00t,%'

AND takes precedence over OR. You need to group your OR conditions:
SELECT *
FROM `GSheets`
WHERE `sheetcat` = 'Unsubscribed'
AND
( `user` LIKE '%,r00t,%'
OR `user` LIKE 'r00t,%'
OR `user` LIKE '%,r00t'
OR `user` = 'r00t'
)
The second sheetcat condition is also redundant, you can remove the NULL check.

Related

MYSQL ERROR CODE: 1288 - can't update with join statement

Thanks for past help.
While doing an update using a join, I am getting the 'Error Code: 1288. The target table _____ of the UPDATE is not updatable' and figure out why. I can update the table with a simple update statement (UPDATE sales.customerABC Set contractID = 'x';) but can't using a join like this:
UPDATE (
SELECT * #where '*' contains columns a.uniqueID and a.contractID
FROM sales.customerABC
WHERE contractID IS NULL
) as a
LEFT JOIN (
SELECT uniqueID, contractID
FROM sales.tblCustomers
WHERE contractID IS NOT NULL
) as b
ON a.uniqueID = b.uniqueID
SET a.contractID = b.contractID;
If changing that update statement a SELECT such as:
SELECT * FROM (
SELECT *
FROM opwSales.dealerFilesCTS
WHERE pcrsContractID IS NULL
) as a
LEFT JOIN (
SELECT uniqueID, pcrsContractID
FROM opwSales.dealerFileLoad
WHERE pcrsContractID IS NOT NULL
) as b
ON a."Unique ID" = b.uniqueID;
the result table would contain these columns:
a.uniqueID, a.contractID, b.uniqueID, b.contractID
59682204, NULL, NULL, NULL
a3e8e81d, NULL, NULL, NULL
cfd1dbf9, NULL, NULL, NULL
5ece009c, , 5ece009c, B123
5ece0d04, , 5ece0d04, B456
5ece7ab0, , 5ece7ab0, B789
cfd21d2a, NULL, NULL, NULL
cfd22701, NULL, NULL, NULL
cfd23032, NULL, NULL, NULL
I pretty much have all database privileges and can't find restrictions with the table reference data. Can't find much information online concerning the error code, either.
Thanks in advance guys.
You cannot update a sub-select because it's not a "real" table - MySQL cannot easily determine how the sub-select assignment maps back to the originating table.
Try:
UPDATE customerABC
JOIN tblCustomers USING (uniqueID)
SET customerABC.contractID = tblCustomers.contractID
WHERE customerABC.contractID IS NULL AND tblCustomers.contractID IS NOT NULL
Notes:
you can use a full JOIN instead of a LEFT JOIN, since you want uniqueID to exist and not be null in both tables. A LEFT JOIN would generate extra NULL rows from tblCustomers, only to have them shot down by the clause requirement that tblCustomers.contractID be not NULL. Since they allow more stringent restrictions on indexes, JOINs tend to be more efficient than LEFT JOINs.
since the field has the same name in both tables you can replace ON (a.field1 = b.field1) with the USING (field1) shortcut.
you obviously strongly want a covering index with (uniqueID, customerID) on both tables to maximize efficiency
this is so not going to work unless you have "real" tables for the update. The "tblCustomers" may be a view or a subselect, but customerABC may not. You might need a more complicated JOIN to pull out a complex WHERE which might be otherwise hidden inside a subselect, if the original 'SELECT * FROM customerABC' was indeed a more complex query than a straight SELECT. What this boils down to is, MySQL needs a strong unique key to know what it needs to update, and it must be in a single table. To reliably update more than one table I think you need two UPDATEs inside a properly write-locked transaction.

Select where null only if a non-null value doesn't exist?

I have a simple user preferences table that looks like this:
id | user_id | preference_name | preference_value
What makes this table unique though is if the user_id field is null, it means it is the default value for that preference. I'm trying to get all the preferences for a user and use the default value only if an actual value hasn't been specified for that user.
So basically I need to:
SELECT * FROM user_preferences WHERE user_id = {userIdVar} OR user_id IS NULL;
BUT, I want to throw out a user_id is null result if there is another row in the result set with the same preference_name and a value for user_id.
Is there a way to do this with a single SQL query or should I do this in code?
Use NOT EXISTS:
SELECT up1.*
FROM user_preferences up1
WHERE ( NOT EXISTS(SELECT 1
FROM user_preferences up2
WHERE user_id = {userIdVar})
AND user_id IS NULL )
OR ( user_id = {userIdVar} );
There are various ways you can do this, but if all preferences have a default value, or you have a complete list of preferences somewhere else, I would do it like this:
select
default_preferences.preference_name,
coalesce(
real_user_preferences.preference_value,
default_preferences.preference_value) as preference_value
from
(select * from user_preferences where user_id is null)
as default_preferences
left join
(select * from user_preferences where user_id = #user_id)
as real_user_preferences
on
real_user_preferences.preference_name = default_preferences.preference_name
You've tagged your question both MySQL and SQL Server, I don't know which dialect you're looking for. I know SQL Server accepts this syntax, but it might need some tweaking for MySQL.
Edit: funkwurm points out that subqueries make this likely to perform poorly on MySQL. If that turns out to be a problem, it can be rewritten without subqueries as
select
default_preferences.preference_name,
coalesce(
real_user_preferences.preference_value,
default_preferences.preference_value) as preference_value
from
user_preferences as default_preferences
left join
user_preferences as real_user_preferences
on
real_user_preferences.preference_name = default_preferences.preference_name
and real_user_preferences.user_id = #user_id
where
default_preferences.user_id is null
Edit 2: if there are preferences that do not have a default value, the first version can be modified to use full join instead of left join, and take preference_name from either the defaults or the user-specific preferences, just like preference_value. However, the second version is not so easily modified.
COALESCE returns the first non null values of the params provided: http://dev.mysql.com/doc/refman/5.0/en/comparison-operators.html#function_coalesce
So if you grab the set of default preferences and JOIN them with the users preferences, you can use the COALESCE in your columns to populate the correct values.
This should work to select the first row that is either NULL or set the the user_id variable where the user_id variable is preffered if both are set and then shows every preference_name only once.
SELECT
*
FROM
(
SELECT
*
FROM
user_preferences
WHERE
user_id = {userIdVar} OR
user_id IS NULL
ORDER BY
CASE WHEN user_id IS NULL THEN 1 ELSE 0 END
) sub_query
GROUP BY
preference_name
SQL FIDDLE

How to optimize double select query

SELECT `id`, `field2`, `field3`, `field4` as Title FROM `articles`
WHERE `category_id` = 'X'
AND `id` NOT IN
(SELECT `articleid` FROM `article-seen` WHERE `userid` = 'Y')
How can I optimize this?
I think double select is bad, but im new to mysql
Try using JOIN that will get you the same result but makes the query looks simpler
The optimization depends (I think) on the version of MySQL you are using.
This is how you write it as a join:
SELECT `id`, `field2`, `field3`, `field4` as Title
FROM `articles` a left outer join
`articles_seen` arts
on a.id = arts.articleid and arts.userid = 'Y'
where a.`category_id` = 'X' and
arts.id is null;
This query, at least, doesn't materialize the subquery, which (I think) your originally query would.
To makes this faster, you want to add indexes. The ones that come to mind are: articles(category_id, id) and articles_seen(userid, articleid). You could also add the fields in the select to the first index, so the entire query can be satisfied by looking at the index, rather than returning to the main table.

Zend_Db_Table, JOIN and mysql expressions in one query?

Im trying to build a complex (well...) query with Zend_Db_Table where I will both need to join the original table with an extra table and get some extra info from the original table with zend_db_expr.
However, things go wrong from the start. What I to is this:
$select = $this->getDbTable()->select(Zend_Db_Table::SELECT_WITH_FROM_PART)
->setIntegrityCheck(false);
$select->from( $this->getDbTable() , array(
'*' ,
new Zend_Db_Expr('`end` IS NULL as isnull') ,
new Zend_Db_Expr('`sold` IN (1,2,3) as issold') ,
) );
Zend_Debug::dump( $select->__toString() );exit;
What results in this:
SELECT `items`.*, `items_2`.*, `end` IS NULL as isnull, `sold` IN (1,2,3) as issold FROM `items`
INNER JOIN `items` AS `items_2`
What I need it to be though, at this point before I do the join with the other table, is
SELECT `items`.*, `end` IS NULL as isnull, `sold` IN (1,2,3) as issold FROM `items`
I don't need an inner join with itself, I just need to add those two Zend_Db_Expr to the things that should be selected, after which I'd continue building the query with the join and where's etc. like
$select->joinLeft( ... )
->where(...)
Any ideas? Cheers.
You should not redo a ->from() call, which means yu add a new table in the query.
Instead you should just use ->where()->columns() calls containing you Zend_Db_expr.
edit: sorry for the mistake.

MySQL -- Better way to do this query?

This query will be done in a cached autocomplete text box, possibly by thousands of users at the same time. What I have below works, bit I feel there may be a better way to do what I am doing.
Any advice?
UPDATED -- it can be 'something%':
SELECT a.`object_id`, a.`type`,
IF( b.`name` IS NOT NULL, b.`name`,
IF( c.`name` IS NOT NULL, c.`name`,
IF( d.`name` IS NOT NULL, d.`name`,
IF ( e.`name` IS NOT NULL, e.`name`, f.`name` )
)
)
) AS name
FROM `user_permissions` AS a
LEFT JOIN `divisions` AS b
ON ( a.`object_id` = b.`division_id`
AND a.`type` = 'division'
AND b.`status` = 1 )
LEFT JOIN `departments` AS c
ON ( a.`object_id` = c.`department_id`
AND a.`type` = 'department'
AND c.`status` = 1 )
LEFT JOIN `sections` AS d
ON ( a.`object_id` = d.`section_id`
AND a.`type` = 'section'
AND d.`status` = 1 )
LEFT JOIN `units` AS e
ON ( a.`object_id` = e.`unit_id`
AND a.`type` = 'unit'
AND e.`status` = 1 )
LEFT JOIN `positions` AS f
ON ( a.`object_id` = f.`position_id`
AND a.`type` = 'position'
AND f.`status` = 1 )
WHERE a.`user_id` = 1 AND (
b.`name` LIKE '?%' OR
c.`name` LIKE '?%' OR
d.`name` LIKE '?%' OR
e.`name` LIKE '?%' OR
f.`name` LIKE '?%'
)
Two simple, fast queries is often better than one huge, inefficient query.
Here's how I'd design it:
First, create a table for all your names, in MyISAM format with a FULLTEXT index. That's where your names are stored. Each of the respective object type (e.g. departments, divisions, etc.) are dependent tables whose primary key reference the primary key of the main named objects table.
Now you can search for names with this much simpler query, which runs blazingly fast:
SELECT a.`object_id`, a.`type`, n.name, n.object_type
FROM `user_permissions` AS a
JOIN `named_objects` AS n ON a.`object_id = n.`object_id`
WHERE MATCH(n.name) AGAINST ('name-to-be-searched')
Using the fulltext index will run hundreds of times faster than using LIKE in the way you're doing.
Once you have the object id and type, if you want any other attributes of the respective object type you can do a second SQL query joining to the table for the appropriate object type:
SELECT ... FROM {$object_type} WHERE object_id = ?
This will also go very fast.
Re your comment: Yes, I'd create the table with names even if it's redundant.
Other than changing the nested Ifs to use a Coalesce() function (MySql has Coalesce() doesn't it)? There is not much you can do as long as you need to filter on that input parameter with a like expresion. Putting a filter on a column using a Like expression, where the Like parameter has a wildcard at the begining, as you do, makes the query argument non-SARG-able, which means that the query processor must do a complete table scan of all the rows in the table to evaluate the filter predicate.
It cannot use an index, because an index is based on the column values, and with your Like parameter, it doesn't know which index entries to read from (since the parameter starts with a wild card)
if MySql has Coalesce, you can replace your Select with:
SELECT a.`object_id`, a.`type`,
Coalesce(n.name, c.name, d.Name, e.Name) name
If you can replace the search argument parameter so that it does not start with a wildcard, then just ensure that there is an index on the name column in each of the tables, and (if there are not indices on that column now), the query performance will increase enormously.
There are 500 things you can do. Optimize once you know where your bottlenecks are. Until then, work on getting those users onto your app. Its a much higher priority.