Update row based on multiple relations - mysql

I have three tables:
Kits (kit_id, kit_weight)
Kit_Components (kit_id, quantity, component_id)
Components (component_id, weight)
For each entry in the kits table there can be one or more Kit_Component entries. Each component has a weight column which can either be the weight or null if we haven't weighed it yet. What I need to do is run an SQL query to update the weight column of the Kits table based on the total weight times quantity of all its components or if any of the weights are null set its value to null but I'm not even sure its possible, is it?
Note: I'd like to avoid scripts, triggers or procedures. I have code that does this when a component is saved or a kit is updated but I'd like to be able to do this in bulk.
EDIT: To further clarify I can SUM the weights * quantity however this doesn't deal with component rows being NULL as NULL acts as 0 in a SUM (I've tested this)
E.g. Kit1 has 1xComponentA with a weight of 14 and 2xComponentB with a weight of NULL
SELECT kit_id, SUM(component.weight * kit_component.quantity) FROM kit_component INNER JOIN component ON kit_component.component_id = component.id GROUP BY kit_component.kit_id
This would return 14 for kit1, however this is wrong because ComponentB has no weight so instead should return NULL.
Hugo Kornelis:
"If the data in a group (as formed by GROUP BY) has some NULLs and some
non-NULL data, the NULLs are ignored and the result is the sum of the
remaining numbers: SUM {1, 3, NULL, 5} = SUM {1, 3, 5} = 9
If all data in the group is NULL, the NULLs are ignored as well, leaving
no rows to be summed at all: the result is the sum of the empty set; by
definition this is NULL. SUM {NULL, NULL} = SUM {} = NULL."

Based on your edit, your problems seem to be to make the following query return NULL when any value going into it is NULL:
SELECT kit_id, SUM(component.weight * kit_component.quantity)
FROM kit_component INNER JOIN
component
ON kit_component.component_id = component.id
GROUP BY kit_component.kit_id
You can do this with additional logic:
SELECT kit_id,
(case when count(component.weight) = count(*) and
count(component.quantity) = count(*)
then SUM(component.weight * kit_component.quantity)
end)
FROM kit_component INNER JOIN
component
ON kit_component.component_id = component.id
GROUP BY kit_component.kit_id
Remember count(<field>) counts the number of non-NULL values in the field. So, the counts are essentially saying "all values are non-null" or, equivalently, "no values are null".

After looking around a bit more I realised the problem was the way that SUM handles groupings that have some NULL values. After finding this post SQL query to return NULL for SUM(expression) whenever an included value IS NULL I have work out a resolution and it is as follows:
UPDATE kits
LEFT JOIN
(SELECT
kit_id,
IF(SUM(component.weight is NULL), NULL, SUM(component.weight * kit_component.quantity)) AS total_weight
FROM
kit_component
INNER JOIN component ON kit_component.component_id = component.id
GROUP BY kit_component.kit_id) AS weights ON kits.id = weights.kit_id
SET
kits.weight = weights.total_weight
This will update the kits tables weight to null if any of its components weights are null or total weight if all components have valid values.

Related

Sql select where array in column

In my query I use join table category_attributes. Let's assume we have such rows:
category_id|attribute_id
1|1
1|2
1|3
I want to have the query which suites the two following needs. I have a variable (php) of allowed attribute_id's. If the array is subset of attribute_id then category_id should be selected, if not - no results.
First case:
select * from category_attributes where (1,2,3,4) in category_attributes.attribute_id
should give no results.
Second case
select * from category_attributes where (1,2,3) in category_attributes.attribute_id
should give all three rows (see dummy rows at the beginning).
So I would like to have reverse side of what standard SQL in does.
Solution
Step 1: Group the data by the field you want to check.
Step 2: Left join the list of required values with the records obtained in the previous step.
Step 3: Now we have a list with required values and corresponding values from the table. The second column will be equal to required value if it exist in the table and NULL otherwise.
Count null values in the right column. If it is equal to 0, then it means table contains all the required values. In that case return all records from the table. Otherwise there must be at least one required value is missing in the table. So, return no records.
Sample
Table "Data":
Required values:
10, 20, 50
Query:
SELECT *
FROM Data
WHERE (SELECT Count(*)
FROM (SELECT D.value
FROM (SELECT 10 AS value
UNION
SELECT 20 AS value
UNION
SELECT 50 AS value) T
LEFT JOIN (SELECT value
FROM Data
GROUP BY value) D
ON ( T.value = D.value )) J
WHERE value IS NULL) = 0;
You can use group by and having:
select ca.category_id
from category_attributes ca
where ca.attribute_id in (1, 2, 3, 4)
group by ca.category_id
having count(*) = 4; -- "4" is the size of the list
This assumes that the table has no duplicates (which is typical for attribute mapping tables). If that is a possibility, use:
having count(distinct ca.attribute_id) = 4
You can aggregate attribute_id into array and compare two array from php.
SELECT category_id FROM
(select category_id, group_concat(attribute_id) as attributes from category_attributes
order by attribute_id) t WHERE t.attributes = (1, 2, 3);
But you need to find another way to compare arrays or make sure that array is always sorted.

Select all results with all rows set to true

I have a Devices (unique elements) and a DeviceTests tables. For each device from Devices there's a max of 6 different DeviceTests. These tests can be true, false or null. The type of those tests goes from 1 to 6.
I'd like to extract all devices with no errors for tests: 1, 2, 3 and 6. Ths is my current query:
SELECT
Devices.*
FROM
Devices LEFT JOIN DeviceTests ON Devices.Imei = DeviceTests.Imei
GROUP BY
Devices.Imei
HAVING
BIT_AND(Result IS NOT NULL OR (Result IS NULL AND TestType IN ('4','5')) AND
!BIT_OR(Result IS NOT NULL AND !Result);
Assuming that DeviceTests.Result is NULL when the test has no errors. I dont' really get why you would use HAVING, BIT_AND and BIT_OR functions. You have a where clause that is supposed to present you with options to set conditions upon the dataset.
SELECT Devices.*
FROM Devices
LEFT JOIN DeviceTests ON Devices.Imei = DeviceTests.Imei
WHERE DevicesTests.TestType IN ('1','2', '3', '6')
AND DeviceTests.Result IS NULL
GROUP BY Devices.Imei
ORDER BY DeviceTests.Id ASC
I am also assuming that you have a column Id in your DeviceTests table and you want to sort ascending by test ID, if you don't, then you'll get an error

Use subquery in mysql

The query below gives me 2 out of the 3 answers I'm looking for. On the sub-query select I get null instead of no
the 3 possible values for column name isCyl could be blank, yes, no
I'm not sure if the sub-query is the best way to go about it, but I don't know how else to re-state the query.
The schedule table has a series of columns to show what tasks must be completed on an assignment. Related tables store the results of the tasks if they were assigned to be completed. So I need to test if a specific task was scheduled. If so, then I need to see if the results of the task have been recorded in the related table. For brevity I am only showing one of the columns here.
SELECT s.`reckey`,
if(s.cylinders="T",
(select
if(c.areckey is not null,
"yes",
"no"
)
from cylinders c where c.areckey = s.reckey limit 1
)
,""
) as isCyl
from schedule s
where s.assignmentDate between 20161015 and 20161016
order by s.reckey
Use a LEFT JOIN, which returns NULL for columns in the child table when there's no match.
SELECT s.reckey, IF(s.cylinders = "T",
IF(c.areckey IS NOT NULL, 'yes', 'no'),
"") AS isCyl
FROM schedule AS s
LEFT JOIN cylinders AS c ON c.areckey = s.reckey
WHERE s.assignmentDate between 20161015 and 20161016
ORDER BY s.reckey
If there can be multiple rows in cylinders with the same areckey, change it to:
LEFT JOIN (select distinct areckey FROM cylinders) AS c on c.areckey = s.reckey
or use SELECT DISTINCT in the main query.

SQL QUERY not giving required result when documents =0

I have one record with no documents (i.e., Documents= 0). When I am executing the following query, it returns zero rows, but it should return my one row since I have one record with no documents.
How can I modify this so it will return the record with no documents?
SELECT * FROM (SELECT ROW_NUMBER() OVER( Order By ParentID  ) AS RowNumber_ps,
UPPER(HostApplicationLocalData.ParentID) asParentID,
COUNT(Document.ID) as Documents from HostApplicationLocalData
inner join Document onHostApplicationLocalData.ID=Document.HostApplicationLocalData_ID
WHERE HostApplicationLocalData.TransactionType_ID = 1
AND Document.Removed=0
AND HostApplicationLocalData.Company_ID = 9000
AND ( SharePointURI is not null or ((SharePointURI isnull and Content_ID is not null )
or ((HostApplicationLocalData_ID is not null andHostApplicationLocalData_ID != 0 and Content_ID isnull))))
group by HostApplicationLocalData.ParentID )q
where Documents > 0
And
Documents = '0'
Looking over your code, it seems most likely that the INNER JOIN (along with the filter Document.Removed=0) is excluding any record without an entry in the Document table, which would naturally mean that no values would show up with a COUNT(Document.ID = 0.
Try this version of the query, which converts the JOIN type to a LEFT OUTER, and adds a condition allowing the Document.Removed filter to pass through records where the Document record would return NULL.
SELECT *
FROM
(
SELECT
ROW_NUMBER() OVER( Order By ParentID ) AS RowNumber_ps,
UPPER(HostApplicationLocalData.ParentID) asParentID,
COUNT(Document.ID) as Documents
FROM
HostApplicationLocalData
LEFT OUTER JOIN
Document ON
HostApplicationLocalData.ID=Document.HostApplicationLocalData_ID
WHERE
HostApplicationLocalData.TransactionType_ID = 1
AND (Document.Removed=0 OR Document.HostApplicationLocalData_ID IS NULL)
AND HostApplicationLocalData.Company_ID = 9000
AND
(SharePointURI is not null or
((SharePointURI isnull and Content_ID is not null ) or
((HostApplicationLocalData_ID is not null andHostApplicationLocalData_ID != 0 and Content_ID isnull))
)
)
GROUP BY HostApplicationLocalData.ParentID
)q
WHERE Documents = 0

SQL query to return NULL for SUM(expression) whenever an included value IS NULL

For MySQL, I want a query that returns a SUM of an expression, EXCEPT that I want the returned value to be NULL if any of the expressions included in the SUM are NULL. (The normal operation of SUM is to ignore NULL values.
Here's a simple test case that illustrates
CREATE TABLE t4 (fee VARCHAR(3), fi INT);
INSERT INTO t4 VALUES ('fo',10),('fo',200),('fo',NULL),('fum',400);
SELECT fee, SUM(fi) AS sum_fi FROM t4 GROUP BY fee
This returns exactly the result set I expect:
fee sum_fi
--- ------
fo 210
fum 400
What I want is a query that returns a DIFFERENT result set:
fee sum_fi
--- ------
fo NULL
fum 400
What I want is for the value returned for sum_fi to be NULL whenever an included value is NULL. (This is different than the SUM aggregate function ignores NULL, and returns a total for the non-NULL expressions.)
Question: What query can I use to return the desired result set?
I don't find any built in aggregate function that exhibits this behavior.
How about using SUM(fi is NULL) to determine if you have a NULL value in your data set?
This should work:
SELECT fee, IF(SUM(fi is NULL), NULL, SUM(fi)) AS sum_fi FROM t4 GROUP BY fee
You can use this query:
SELECT fee,
IF(COUNT(fi) < (SELECT count(fee) FROM t4 temp2 WHERE temp1.fee = temp2.fee),
NULL, SUM(fi)) AS sum_fi
FROM t4 temp1 GROUP BY fee
There is also this solution:
SELECT fee, CASE WHEN COUNT(*) = COUNT(fi) THEN SUM(fi) ELSE NULL END AS sum_fi
FROM t4 GROUP BY fee
which I derived from the answer of the question Aaron W tagged as a duplicate of this. Admittedly, the latter form looks better than my original idea.