SQL query with having count with condition - mysql

I would like to retreive records based on a joined table's number of associations with a condition. In plain English the query would be:
Get all properties where the number of associated logs having action one of ('foo', 'bar', 'moo') is less than 5 (can also be NULL). So to be more exact, it can have 10 logs, but if 4 or more of them have action one of ('foo', `'bar', 'moo') then it should not be included.
Without the count condition is working, even though I am not sure it includes the ones with no logs:
SELECT p.id
, count(l.object_id)
FROM properties p
LEFT
JOIN logs l
ON l.object_id = p.id
AND l.object_type = 'Property'
GROUP
BY p.id
HAVING (count(l.object_id) < 5);
The problem is if I want the condition specified above, it does not work (syntax is obviously wrong).
SELECT properties.id, FROM `properties` LEFT OUTER JOIN `logs` ON
-> `logs`.`object_id` = `properties`.`id` AND `logs`.`object_type` = 'Property'
-> GROUP BY properties.id HAVING (count(logs.object_id where logs.action in
-> ('foo', 'bar', 'moo')) < 5);
NOTE: The logs table has over 10 milion records... I have tried with a nested select where, but it is not an option as the query times out.

In MySql the condition can be written as:
HAVING SUM(logs.action in ('foo', 'bar', 'moo')) < 5
In case there are no matches from the the table logs the above sum will return null, do if you want these rows returned use COALESCE():
HAVING COALESCE(SUM(logs.action in ('foo', 'bar', 'moo')), 0) < 5

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.

SQL query to add a column that is currently in a different table

I'm new to SQL. I dabble with it, but get lost easily. Anyhow, I have these two tables that part numbers in them. One of them the part number is the primary key. I guess would have to be the foreign key in the other table. This other table is where the column of information is that I want to query into the first table that has the primary key of part numbers. I can manage to show that part number column easy enough in the first table, but that doesn't do me any good. I need a column called AverageUnitCost, that is directly tied to the cost of each part number.
SELECT
QALog.QALID, QALog.GroupID, QALog.LogDate, QALog.SONumber,
QALog.PartNumber, QALog.PartNotes, QALog.TravelerQty, QALog.EUser,
QALog.ITID, QALog.TrackingNumber, QALog.MDR, QALog.ExpirationDate,
QALog.PONumber, QALog.ReceiptNo, QALog.ReasonID, QALog.RRNo,
Rejections.NumDiscrp, Rejections.RRID, RejectReason.Reason,
ProductGroups.GroupName, Disposition.Disposition, CI_ITEM.itemcode
FROM QALog
INNER JOIN Rejections
ON QALog.QALID = Rejections.QALID
INNER JOIN RejectReason
ON RejectReason.RRID = Rejections.RRID
INNER JOIN Disposition
ON Disposition.DispositionID = Rejections.DispositionID
INNER JOIN ProductGroups
ON ProductGroups.PGID = QALog.GroupID
INNER JOIN CI_ITEM
ON QALog.PartNumber = CI_ITEM.itemcode
WHERE (QALog.LogDate >= DATEADD(year, - 3, GETDATE()))
AND (QALog.ITID = '3')
AND (RejectReason.GroupID = '0')
OR
(QALog.LogDate >= DATEADD(year, - 3, GETDATE()))
AND (QALog.ITID = '3')
AND (RejectReason.GroupID = '3')
ORDER BY QALog.QALID
Your joins look OK, assuming you have the correct table column relationships. But your WHERE condition is wrong, because of the way AND and OR are combined.
But you don't need such a complex mixture, since you have the same conditions on LogDate and ITID in both parts of the OR. So you can simplify it to:
WHERE QALog.LogDate >= DATEADD(year, - 3, GETDATE())
AND QALog.ITID = '3'
AND RejectReason.GroupID IN ('0', '3')
Try in this format
SELECT columnname1, columnname2, columnname3,..... FROM Table1 t, Table2,...... t2 WHERE
t.columnname = t2.columnname ORDER BY ....
Not sure what you are trying to do. Without schema, it is kind of hard to help

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.

Apply where condition when I have multiple rows for each id (Single table or Join multiple ones)

I have below requirement in mysql/SQL Server
Table Name: basic(pid int,av int,sid int,st int,wid int,wt int)
For each pid, there would be 10 rows (containing sid,st values and wid,wt values for each pid). These sets could be from 1 to 10.
So, for a pid value (example: 3213 and 3214), there will be 10 rows like below
Like the above, there could be millions of records
What am trying to achieve is, I want to get the pid's whose (sid=2 and respective st>=7) and also whose (wid=9 and respective wt>=6)
If I apply this condition, I should get list of pid's which must have two pid's 3213 and 3214.
How can I achieve this using simple sql query or i can divide the table into three like basic1(pid,av), basic_sk(pid,sid,st) and basic_wc(pid,wid,wt)
since I can use pid as reference, I can join .. even I tried using joins, and couldn't achieve the required result.
I used below join -
select t1.pid from basic1 t2
inner join basic_sk t2 on t1.pid=t2.pid
inner join basic_wc t3 on t3.pid=t2.pid
where (((t2.sid=2) and (t2.st>=7)) and ((t3.wid=9) and (t3.wt>=6)))
but no luck.
How about if I have multiple sid and st values in where condition and wid and wt values..
like in sets {sid=2,st>=7} and {sid=4,st>=9}
and {wid=9,wt>=6} and {wid=5,wt>=5}
How can I achieve my requirement using simple sql query ?
Any possibility is fine for me, with one table or multiple tables (using join)
One method is to use aggregation and a having clause;
select b.pid
from basic b
group by p.pid
having sum(case when (b.sid = 2) and (b.st >= 7) then 1 else 0 end) > 0 and
sum(case when (b.wid = 9) and (b.wt >= 6) then 1 else 0 end) > 0;
Each condition in the having clause counts the rows that match each condition. The > 0 ensure that there is at least one row for each.

Update row based on multiple relations

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.