Update non-null field with possibly null values - mysql

In the query below:
update collect_irc_deploy c
set hid = (select id
from auth_hierarchy
where fqdn = (select location
from reserve
where id=c.rid
)
)
where hid = 0 and rid is not null
the subquery select id from auth_hierarchy where fqdn = (select location from reserve where id = c.rid) may return NULL while the field hid is NOT NULL.
How can I modify the statement so that if the subquery returns NULL that data item is skipped instead of failing the entire execution?

You can use update...join syntax to ensure only joined rows are updated:
update collect_irc_deploy
join reserve on reserve.id = collect_irc_deploy.rid
join auth_hierarchy on auth_hierarchy.fqdn = reserve.location
set collect_irc_deploy.hid = auth_hierarchy.id
where collect_irc_deploy.hid = 0 and collect_irc_deploy.rid is not null

Use UPDATE IGNORE solved my problem. But it will generate warning messages.

Related

UPDATE COALESCE query with null values

Definitely a basic question, but I couldn't find an example.
I'm writing a procedure which merges two rows into the good row. It moves all child rows' ids to being the correct one, replaces all NULL values with available values in the row being removed before finally deleting the 'bad' row.
What I have so far is this:
CREATE DEFINER=`danielv`#`%`
PROCEDURE `emp_merge`(IN `#core_emp_id` int, IN `#bad_emp_id` int)
BEGIN
UPDATE claim SET employee_id = #core_emp_id
WHERE employee_id = #bad_emp_id;
WITH bad_employee_values AS (
SELECT * FROM employee WHERE employee_id = #bad_emp_id
)
UPDATE employee SET
employee.employment_date = COALESCE(employee.employment_date, bad_employee_values.employment_date),
WHERE employee_id = #core_emp_id;
DELETE FROM employee WHERE employee_id = #bad_emp_id;
END
However, I'm getting non-descript error messages and I'm not sure why. I suspect there's an issue with how I'm handling my CTE and coalesce function, but I'm not sure where the gap in my understanding is.
In this statement :
WITH bad_employee_values AS (SELECT * FROM employee WHERE employee_id = #bad_emp_id)
UPDATE employee SET
employee.employment_date = COALESCE(employee.employment_date, bad_employee_values.employment_date),
WHERE employee_id = #core_emp_id;
You are defining CTE bad_employee_values but you are not using it in the UPDATE part of the query, hence you cannot access its columns : for MySQL, bad_employee_values.employment_date is unknown.
It looks like you could simply avoid a CTE here. You could just self-join the table, like so :
UPDATE employee e_core
INNER JOIN employee e_bad ON e_bad.employee_id = #bad_emp_id
SET e_core.employment_date = e_bad.employment_date,
WHERE employee_id = #core_emp_id AND e_core.employment_date IS NULL
This query will simply select the record identified by #core_emp_id, join it with the corresponding "bad" record, and copy the value of employment_date. The second condition in the WHERE clause prevents records whose employment_date is not null from being selected.

MySQL: handle EMPTY SET in UPDATE statement

I have: something like
UPDATE table
SET field = (SELECT field FROM another_table WHERE id = #id);
Problem: SELECT field FROM another_table WHERE id = #id subquery can return one field or EMPTY SET.
Question: How to handle situation when subquery returns empty set?
Updated:
UPDATE table t
SET field = IF((SELECT field FROM another_table WHERE id = #id) IS NOT NULL, -- select field
(SELECT field FROM another_table WHERE id = #id), -- Problem #1: select field AGAIN!
(SELECT field FROM table WHERE id = t.id) -- Problem #2: try to not change value, so select the current field value!!
);
If function can be useful:
UPDATE table
SET field = if((SELECT field FROM another_table WHERE id = #id) IS NULL,true,false);
You can add the conditional:
WHERE (SELECT COUNT(*) FROM another_table WHERE id = #id) > 0
This will make sure that at least one row exists in another_table with the id. See my SQL Fiddle as an example.
Note: this may not be the most efficient because it does a count on another_table, and if it is greater than 1 it will do another SELECT (two sub-queries). Instead, you can do an INNER JOIN:
UPDATE table
INNER JOIN another_table ON table.id=another_table.id
SET table.field = another_table.field
WHERE another_table.id = #id;
See this SQL Fiddle. The reason why I saved this as a second option, is not all SQL languages can UPDATE with joins (MySQL can). Also, you need some way to relate the tables..in this case I said that the table.id we are updating is equal to another_table.id we are taking the data from.
NOTE The UPDATE statement will modify EVERY row in table and assign the same value to every row; that seems a little unusual.
To answer your question:
If you want to handle the "empty set" by not updating any rows in table, then one way to do this is with a JOIN to an inline view:
UPDATE table t
CROSS
JOIN (SELECT a.field
FROM another_table a
WHERE a.id = #id
LIMIT 1
) s
SET t.field = s.field
Note that if the inline view query (aliased as s) return an "empty set", then no rows in table will be updated, because the JOIN operation will also return an "empty set", meaning there are zero rows to be updated.

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

Why does column_name = (select column_name) work and not column_name=null

I am fetching some rows for my model but the view has so many criteria i was getting tired of writing many models.To make work easier i don't want to write new select statements for every criteria selected so at first i tried to try and still return something from the select even when one of more of the available criteria are/is not supplied by the user.
SELECT * FROM members WHERE member_id = null AND member_club_id = 1 AND membership_year = null;
and returns nothing
Finally i tried
SELECT * FROM members WHERE member_id = (select member_id) AND member_club_id = (select member_club_id=1) AND membership_year = (select membership_year);
and this works correctly.
I am still new to mysql and i wanted to know why this second approach worked.
Of interest is select member_club_id=1 and member_id = (select member_id) for instance.
In member_id = (select member_id) i was thinking this would be read as member_id=member_id since i had no variable called member id and therefore fail.
In select member_club_id=1 i thought i would get unknown column error in member_club_id and therefore fail.
Someone help out.
You can't use = with NULL. Use IS NULL or IS NOT NULL.
see: http://dev.mysql.com/doc/refman/5.0/en/working-with-null.html
in first query you maybe want do this
SELECT * FROM members WHERE member_id is null
AND member_club_id = 1
AND membership_year is null;
there is not in mysql = null but is null
in your second query i dont think this member_id = (select member_id) will do something
its like you saying WHERE member_id = member_id this automatically return true in all cases. Thats why you got it working.

Updating only one row based on an "or" clause that may return multiple results

Is there a more efficient way of doing this? I do not want to update more than one record and it is possible that the two initial queries can match two different records.
SET #AppID = (SELECT appID FROM employees WHERE ssn = vSSN);
IF #AppID IS NULL THEN
SET #AppID = (SELECT appID from applications WHERE appDate = vDate);
END IF;
UPDATE applications
SET status = vStatus
WHERE appID = #AppID;
You can use the COALESCE operator and the UPDATE/REPLACE statement. To be more precise, COALESCE picks the first non-null value out of a comma-separated list.
http://dev.mysql.com/doc/refman/5.5/en/comparison-operators.html#function_coalesce
http://dev.mysql.com/doc/refman/5.5/en/update.html
http://dev.mysql.com/doc/refman/5.5/en/replace.html
First, the UPDATE alternative:
UPDATE applications AS app
INNER JOIN (
SELECT COALESCE(employees.appID, applications.appID) AS appID
FROM applications
LEFT OUTER JOIN employees
ON employees.ssn = vSSN
WHERE applications.appDate = vDate
-- do some ordering or filtering if the date is not unique
LIMIT 1) AS app2
ON app.appID = app2.appID
SET app.status = vStatus
Second, the REPLACE alternative. Please note that REPLACE first deletes the old entry and then reinserts the new one. Therefore, employees.appID may not be a foreign key of applications.appID.
REPLACE INTO applications (appID, status)
SELECT COALESCE(employees.appID, app2.appID) AS appIdToModify, vStatus
FROM applications AS app2
LEFT OUTER JOIN employees
ON ssn = vSSN
WHERE app2.appDate = vDate
UPDATE applications SET status = vStatus WHERE appID = #AppID LIMIT 1;