It's hard to put in correctly, but I'm using MySQL and I need to select one id, let's call it parent_id which has to meet child_id values in multiple rows.
For example:
+-----------+----------+
| parent_id | child_id |
+-----------+----------+
| 1 | 10 |
+-----------+----------+
| 2 | 11 |
+-----------+----------+
| 2 | 12 |
+-----------+----------+
| 3 | 13 |
+-----------+----------+
| 4 | 11 |
+-----------+----------+
| 5 | 12 |
+-----------+----------+
Now if I pass child_id parameters 11 and 12, I have to get parent_id 2 back, but if I pass 10 and 11, I have to get nothing back. Also, if I pass 11, I have to receive 4. And if I pass 13, I have to receive 3 back.
How do I go about this? I tried counting the parent_id's and using HAVING clause, also using GROUP BY clause, but nothing I try meets all of my requirements.
EDIT:
Example Fiddle: http://sqlfiddle.com/#!2/abbc4/5
EDIT2:
Expected results:
Passed parameters: 11, 12
Received result: 2
Passed parameters: 11
Received result: 4
Passed parameters: 13
Received result: 3
Passed parameters: 12, 13
Received result NULL
EDIT3:
Updated the spec. See here also: http://sqlfiddle.com/#!2/2f750/1
The following statement does what you want. I am not so sure about its performance though...
select t.parent_id, t.cnt from
(
select parent_id, count(*) cnt
from t
WHERE child_id IN (12, 11)
GROUP BY parent_id
) t
inner join
(
select parent_id, count(*) cnt
from t group by parent_id
) s
on t.parent_id = s.parent_id
and t.cnt = s.cnt -- Check that the parent has exactly as many children as
-- passed in - and not more. Prevents matching if only part
-- of the children of a parent were specified.
and t.cnt = 2 -- Check that all passed in children produced a match on the same
-- parent. Prevents matching of parents that match only a subset
-- of the specified children
Replace the 2 with the number of specified children in the IN list.
You can also use this more compact version
select case
when min(t.parent_id) = max(t.parent_id) -- parent_ids are the same?
-- and all children share the same parent?
and count(t.parent_id) = (
select count(parents.parent_id)
from t parents
where parents.parent_id in
(select parent_id
from t
where child_id in (11, 12) -- <= change here
))
then t.parent_id
else null
end as parent_id
from t
where child_id in (11, 12); -- <= and here
I have tested this and works for all your use cases
You can test here http://sqlfiddle.com/#!2/abbc4/183
You have to have two variables for this to work. First one is the comma separated list of your child_ids (child_list) and second is the number of the children (the count of children in your child_list) you are searching for (child_count).
SELECT parent_id,COUNT(*)
FROM table
WHERE child_id IN (<child_list>)
GROUP BY parent_id
HAVING COUNT(*)=<child_count>
This should give you the desired results.
Related
I have question regarding getting sum of the items by their IDS. Is there are any elegant way to do this with sql procedure? The IDS are coming from in array, for instance (10, 10, 11, 11, 12). So the sum should be 300. If I am right functions cannot take array as an parameter in mysql, so it can be procedure.
The closest what I could think of:
SELECT price FROM table WHERE FIND_IN_SET(ID, (10, 10, 11, 11, 12))
tho it doesn't work properly.
I know that SUM is almost does what I need, except it skips duplicate values, if there is a way to use it so it wouldn't skip, then it probably would be fastest. Table is below:
|---------------------|------------------|
| ID | Price |
|---------------------|------------------|
| 10 | 34 |
|---------------------|------------------|
| 11 | 99 |
|---------------------|------------------|
| 12 | 34 |
|---------------------|------------------|
You would create a derived table and join:
select sum(t.price)
from (select 10 as id union all
select 10 as id union all
select 11 as id union all
select 11 as id union all
select 12 as id
) i join
t
on i.id = t.id;
In MySQL 8.0, you can do this with running your input array thru JSON_TABLE, which will interpret your array entries as rows.
This approach respects the array's order and doesn't skip duplicities, as it's not a filtering function like WHERE.
set #arr = '[10, 10, 11, 11, 12]';
select *
from json_table(#arr, '$[*]' columns (id int path '$')) input
left join products using (id);
-> id price
10 34
10 34
11 99
11 99
12 34
select sum(price)
from json_table(#arr, '$[*]' columns (id int path '$')) input
left join products using (id);
-> 300
I have table:
id | parent | regno | person
1 | 0 | 12 | 5
2 | 1 | 12 | 15
3 | 0 | 13 | 5
4 | 0 | 14 | 6
I have MySQL query...
SELECT *
FROM table
WHERE person='5';
...that returns rows 1 and 3.
In this table row 1 and 2 are related (same regno).
How can i build this query to include related rows?
Basically when searching for person 5 i need MySQL query to return following:
id | parent | regno | person
1 | 0 | 12 | 5
2 | 1 | 12 | 15
3 | 0 | 13 | 5
Parent column has id of column it is related to, but it can be positive and negative integer. All related rows always have same regno.
Thank you.
You want all people who have a regno that is the same as the regno of anyone who is person 5:
--this main query finds all people with the regno from the subquery
SELECT *
FROM table
WHERE regno IN
( --this subquery finds the list of regno
SELECT regno
FROM table
WHERE person = '5'
)
There are other ways to write this; i'm not a fan of IN, and personally would write it like this:
SELECT t.*
FROM table t
INNER JOIN
(
SELECT DISTINCT regno
FROM table
WHERE person = '5'
) u
WHERE t.regno = u.regno
But it's harder to understand, and it's quite likely that these queries would end up being executed identically internally anyway. In this form the DISTINCT is required to make the regno from the subquery unique. If it were not, joined rows would end up duplicated. Why do I prefer it over IN? In some database systems IN's implementation can be very naive and low performing. "Never use IN to create a list longer then you would write by hand" is an old mantra I tend to stick to. This join pattern is also more flexible, can work with multiple values. Not every database supports Oracle-esque where x,y in ((1,3),(3,4)) value multiples
As an aside (and partly in response to the first comment on this answer) it would be more typical and more useful/usual to have the database prepare a set of rows that had parent and child data on the same line
It would look more like this:
SELECT *
FROM
table c
LEFT OUTER JOIN
table p
ON c.regno = p.regno AND p.parent = 1
WHERE c.person = '5' AND c.parent=0
This is assuming your "parent" column is 0 1 indicating true false.. you seem to have made a comment that parent is the id of the relative (not sure if it's parent-of or parent-is)
For a table where there is an id, and parentid column, and the parentid is set to a value when the row is a child of that other id;
id, parentid, name
1, null, Daddy
2, 1, Little Jonny
3, 1, Little Sarah
That looks like:
SELECT *
FROM
table c
INNER JOIN
table p
ON c.parentid = p.id
WHERE p.parentid ID NULL
Rows can have only one parent. A NULL in the parent id defines the row as being a parent, otherwise it's a child. You could turn this logic on its head if you wanted, call the column isparentof and have all child rows with null in the isparentof, and anyone who is a parent of a child, out the child id in isparentof. This then limits you to one child per multiple parents (single child families).. the query to pull them out is broadly the same
You can get all the id values for the person = '5' in a Derived Table.
Now, join back to the main table, matching either the absolute of parent (to get the child row(s)) or the id (to get the parent id row itself).
Based on discussion in comments, Try:
SELECT t.*
FROM your_table AS t
JOIN
(
SELECT id AS parent_id
FROM your_table
WHERE person = '5'
) AS dt
ON dt.parent_id = ABS(t.parent) OR
dt.parent_id = t.id
It is hard to comprehend though, why would you put negative values in parent!
I'm having trouble with summing the fields values based on another fields value.
I need to SUM(activities.points) based on activities.activity_type if it's used_points or added_points and put it in AS used_points/added_points.
Table activities:
id | subscription_id | activity_type | points
--------------------------------------------------
1 | 1 | used_points | 10
2 | 1 | used_points | 50
3 | 1 | added_points | 20
4 | 1 | added_points | 30
5 | 2 | used_points | 20
6 | 2 | used_points | 45
7 | 2 | added_points | 45
8 | 2 | added_points | 45
Table subscriptions:
id | name | current_points
-------------------------------------
1 | card_1 | 700
2 | card_2 | 900
What I need:
name | current_points | used_points | added_points
-----------------------------------------------------------
card_1 | 700 | 60 | 50
card_2 | 900 | 65 | 90
What I tried :
SELECT
subscriptions.name,
subscriptions.current_points,
IF(activities.activity_type="used_points", SUM(activities.points), null)
AS used_points,
IF(activities.activity_type="added_points", SUM(activities.points), null)
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription.id = subscription.id
GROUP BY subscriptions.name
Which is wrong.
Thanks
You want to use SUM(IF( )). You want to add up the values returned from the IF. You want that IF expression to be evaluated for each individual row. Then, use the SUM aggregate to add up the value returned for each row.
Remove the SUM aggregate from inside the IF expression and instead, wrap the IF inside a SUM.
Followup
Q But why SUM() inside of IF doesn't work ?
A Well, it does work. It's just not working the way you want it to work.
The MySQL SUM function is an "aggregate" function. It aggregates rows together, and returns a single value.
For an expression of this form: IF(col='foo',SUM(numcol),0)
What MySQL is doing is aggregating all the rows into the SUM, and returning a single value.
Other databases would pitch a fit, and throw an error with the reference to the non-aggregate col in that expression. MySQL is more lenient, and treats the col reference like it was an aggregate (like MIN(col), or MAX(col)... working on a group of rows, and returning a single value. In this case, MySQL is selecting a single, sample row. (It's not determinate which row will be "chosen" as the sample row.) So that reference to col is sort of like a GET_VALUE_FROM_SAMPLE_ROW(col). Once the aggregates are completed, then that IF expression gets evaluated once.
If you start with this query, this is the set of rows you want to operate on.
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
When you add a GROUP BY clause, that's going to aggregate some of those rows together. What you will get back for the non-aggregates is values from a sample row.
Try adding GROUP BY s.name to the query, and see what is returned.
Also try adding in some aggregates, such as SUM(a.points)
SELECT s.name
, s.current_points
, a.activity_type
, a.points
, IF(a.activity_type='used_points',a.points,NULL) AS used_points
, IF(a.activity_type='added_points',a.points,NULL) AS added_points
, SUM(a.points) AS total_points
FROM subscriptions s
JOIN activities a
ON a.subscription_id = s.id
GROUP BY s.name
Finally, we can add in the expressions in your query into the SELECT list:
, IF(a.activty_type='used_points',SUM(a.points),NULL) AS if_used_sum
, IF(a.activty_type='added_points',SUM(a.points),NULL) AS if_added_sum
What we discover is that the value returned from these expressions will either be SUM(a.points), which will match the total_points, or it will be NULL. And we can see the value of the activity_type column, retrieved from a single, sample row for each group, and we can see that this is expression is "working", it's just not doing what we you really want to happen: for the conditional test to run on each individual row, returning a value for points or a null, and then summing that up for the group.
Your code is only slightly out:
SELECT
subscriptions.name,
subscriptions.current_points,
SUM(IF(activities.activity_type="used_points", 0, activities.points))
AS used_points,
SUM(IF(activities.activity_type="added_points", 0, activities.points))
AS added_points
FROM activities
JOIN subscriptions
ON activities.subscription_id = subscription.id
GROUP BY subscriptions.name, subscriptions.current_points
Note the fixed typo in the second last line - you wrote subscription.id instead of subscription_id. Also you only grouped by name instead of name and current_points, not sure if that's allowed in mysql (I use T-SQL), it's good practice to have it there anyway.
Well, I did it not using the IF statement. Here's the example (http://sqlfiddle.com/#!2/076c3f/12):
SELECT
subs.name,
subs.current_points,
(SELECT SUM(points) FROM activities WHERE type = 1 AND subs_id = subs.id) AS used_points,
(SELECT SUM(points) FROM activities WHERE type = 2 AND subs_id = subs.id) AS added_points
FROM activities
JOIN subs ON activities.id = subs.id
GROUP BY subs.name
NOTE: I changed the type from VARCHAR to INT to simplify.
Try change
IF(activities.activity_type="used_points", null, SUM(activities.points))
AS used_points,
IF(activities.activity_type="added_points", null, SUM(activities.points))
AS added_points
To next
SUM(IF(activities.activity_type="used_points", activities.points, 0))
AS used_points,
SUM(IF(activities.activity_type="added_points", activities.points, 0))
AS added_points
In this way you check column and sum points or 0
To sum a column of integer values(c1) based on another column of character values(c2). And if you need to sum only not null values, the below code will help.
SELECT SUM(c1) FROM table_name WHERE c2 <> '' AND c2 IS NOT NULL
I have a MySQL table of following structure.
**Table elements :**
element_id element_name parent_id
1 UIG 0
2 CAM 1
3 IHG 1
4 USR 1
5 DBL 1
6 APD 1
7 RTM 1
8 OCR 2
9 IRT 3
10 ICR 3
11 OCR 2
12 USH 1
13 AML 1
I need to find child elements of a given element.
I made the following query :
SELECT parent_id,GROUP_CONCAT(element_id)
FROM elements
WHERE parent_id='1'
GROUP BY parent_id
which returns,
+-----------+--------------------------+
| parent_id | GROUP_CONCAT(element_id) |
+-----------+--------------------------+
| 1 | 2,3,4,5,6,7,12,13 |
+-----------+--------------------------+
1 row in set (0.00 sec)
While I need also need the childs of element 2 and 3, which should result into
+-----------+------------------------------------+
| parent_id | GROUP_CONCAT(element_id) |
+-----------+------------------------------------+
| 1 | 2,3,4,5,6,7,8,9,10,11,12,13 |
+-----------+------------------------------------+
1 row in set (0.00 sec)
How do I achieve this without procedures and just a query?
You just need to use the in operator on a subquery.
select group_concat(element_id)
from chpr
where parent_id in (
select group_concat(element_id)
from chpr
where parent_id = 1
group by parent_id)
;
** AS per OP's comment the levels are definitely a concern**
However based on the initial sample data and request, here is the SQLFIDDLE DEMO that provides the results as per the question's expected output.
The only change is that one needs to group by both element_id and parent_id the innner most first subquery.
Not a very elegant query at all:
select 1 as parent_id, group_concat(x.element_id)
from (
(select element_id
from chpr
where parent_id in
(select element_id
from chpr
where parent_id = 1
group by element_id, parent_id
))
union all
(select element_id
from chpr
where parent_id = 1
group by element_id, parent_id
)) x
;
Results:
PARENT_ID GROUP_CONCAT(X.ELEMENT_ID)
1 8,9,10,11,2,3,4,5,6,7,12,13
Use an IN () predicate...
SELECT '1' As Parent_Id,GROUP_CONCAT(element_id)
FROM elements
WHERE Parent_Id IN ('1','2','3')
GROUP BY '1';
Some database require the GROUP BY in this scenario others don't but it's fairly good practice to include it even if it is not needed.
Incidentally, I suspect Parent_Id is a numeric of some sort in which case it should really read...
SELECT 1 As Parent_Id,GROUP_CONCAT(element_id)
FROM elements
WHERE Parent_Id IN (1,2,3)
GROUP BY 1;
You could actually exclude the 1 As Parent_Id and GROUP BY 1 completely.
If you can only specify one value use a sub-query...
SELECT GROUP_CONCAT(element_id)
FROM Elements
WHERE Parent_Id IN (SELECT Element_Id
FROM Elements
WHERE Parent_Id = 1)
Take a look at my this question
Finding all parents in mysql table with single query (Recursive Query)
And here is the source for this kind of work.
http://explainextended.com/2009/07/20/hierarchical-data-in-mysql-parents-and-children-in-one-query/
I'm trying to do a query that selects mike if it isn't in the three highest bids for a keyword. Rows 4 and 7 should be selected.
So in final, if mike isn't in the three highest bids for a keyword, then select.
How do I solve this? With a sub query?
$construct = "SELECT child.* FROM `temp-advertise` child
LEFT JOIN `temp-advertise` parent on child.keyword=parent.keyword
WHERE child.name='mike'
ORDER BY child.id DESC";
id | name| keyword | bid |
1 | mike| one | 7 |
2 | tom | one | 4 |
3 | ced | one | 6 |
4 | mike| two | 1 |
5 | tom | two | 5 |
6 | har | two | 5 |
7 | mike| one | 3 |
8 | har | two | 3 |
SELECT *
FROM `temp-advertise` ta
WHERE ta.keyword = 'one'
AND ta.name = 'mike'
AND ta.bid <
(
SELECT bid
FROM `temp-advertise` tai
WHERE tai.keyword = 'one'
ORDER BY
bid DESC
LIMIT 2, 1
)
Your structure doesn't look too promising, nor your sample data. However, that said, you want to know if "Mike" was in the top 3 per keyword... and that he has 3 bids.... 2 for "one", 1 for "two". From the raw data, it looks like Mike is in 1st place and 4th place for the "one" keyword, and 4th place for "two" keyword.
This should get you what you need with SOME respect to not doing a full query of all keywords. The first innermost query is to just get keywords bid on by "mike" (hence alias "JustMike"). Then join that to the temp-advertise on ONLY THOSE keywords.
Next, by using MySQL variables, we can keep track of the rank PER KEYWORD. The trick is the ORDER BY clause needs to return them in the order that represents proper ranking. In this case, each keyword first, then within each keyword, ordered by highest bid first.
By querying the records, then using the #variables, we increase the counter, start at 1 every time the keyword changes, then preserve the keyword into the #grpKeyword variable for comparison of the next record. Once ALL bids are processed for the respective keywords, it then queries THAT result but ONLY for those bid on by "mike". These records will have whatever his rank position was.
select RankPerKeyword.*
from
( SELECT ta.*,
#grpCnt := if( #grpKeyword = ta.Keyword, #grpCnt +1, 1 ) as KWRank,
#grpKeyword := ta.Keyword as carryForward
FROM
( select distinct ta1.keyword
from `temp-advertise` ta1
where ta1.name = "mike" ) as JustMike
JOIN `temp-advertise` ta
on JustMike.Keyword = ta.Keyword,
( select #grpCnt := 0,
#grpKeyword := '' ) SqlVars
ORDER BY
ta.Keyword,
ta.Bid DESC" ) RankPerKeyword
where
RankPerKeyword.name = "mike"
(Run above to just preview the results... should show 3 records)
So, if you want to know if it was WITHIN the top 3 for a keyword you could just change to
select RankPerKeyword.keyword, MIN( RankPerKeyword.KWRank ) as BestRank
from (rest of query)
group by RankPerKeyword.Keyword
Try this:
Select ID, name, keyword from temp-advertise e
where 3 <= (select count(name) from temp-advertise
where e.keyword = keyword and bid > e.bid)
Try
SELECT .. ORDER BY bid LIMIT 3,999