Self Join With Multiple Conditions - mysql

I'm using mysql to storing my chemical analysis. And filtering the results with html/php and to generate the query to fetch results I want. Now things getting complex for me and I'm trying to self join the table to apply all the filters.
My table desing is like that with more than 50k rows.
+--------+---------+-------------+----------------+
| Column | Type | Index | |
+--------+---------+-------------+----------------+
| RID | int | primary_key | auto increment |
| ID | int | index | |
| Type | int | | |
| Order | int | | |
| Num | int | | |
| Val1 | decimal | | |
| Val2 | decimal | | |
+--------+---------+-------------+----------------+
Every sample has an ID, different types and order. Val1 and Val2 are the results of Num type analyse. There 42 different Num by now.
For example If my filters are like that,
Select Types (1,3,9)
Select ANum (0,5)
Type 1, Num 5 should be bigger than 10 In Val2
Type 1, Num 5, should be smaller than 30 In Val2
Type 3, Num 0, should be smaller than 8 In Val1
I'm using that query to produce results.
SELECT analyse.* FROM analyse
INNER JOIN
(SELECT ID FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 < 30) AS a1 ON a1.SID = analyse.SID
INNER JOIN
(SELECT SID FROM WHERE Type = 1 AND Num = 5 AND Val2 > 10) AS a2 ON a2.SID = analyse.SID
INNER JOIN
(SELECT SID FROM analyse WHERE Type = 3 AND Num = 0 AND Val1 > 8) AS a3 ON a2.SID = analyse.SID
WHERE Type IN (1,3,9) AND Num IN (0,5) ORDER BY ID, Type, Order, Num ASC
+-----+------+-------+---------------+--------------+--------------+--------------+
| ID | Type | Order | Val1[Num[0]] | Val2[Num[0]] | Val1[Num[5]] | Val2[Num[5]] |
+-----+------+-------+---------------+--------------+--------------+--------------+
| ... | ... | ... | ... | ... | ... | ... |
| 118 | 1 | 1 | 10.9000 | 2.2083 | 3.5056 | 15.2627 |
| 118 | 1 | 2 | 9.5000 | 1.9246 | 2.0305 | 11.7049 |
| 118 | 1 | 3 | 7.9000 | 1.6005 | 2.4274 | 16.6597 |
| 118 | 2 | 1 | 10.9000 | 2.2083 | 3.5056 | 15.2627 |
| 118 | 3 | 2 | 20.4000 | 4.1329 | 2.8187 | 22.9676 |
| 118 | 4 | 3 | 28.3000 | 5.7334 | 2.7094 | 29.6273 |
| 119 | 1 | 1 | 27.2000 | 6.8635 | 0.5506 | 14.9084 |
| 119 | 1 | 2 | 25.9000 | 6.5355 | 0.4249 | 10.9550 |
| 119 | 3 | 1 | 27.2000 | 6.8635 | 0.5506 | 14.9084 |
| 119 | 3 | 2 | 53.1000 | 13.3989 | 0.4893 | 25.8634 |
| ... | ... | ... | ... | ... | ... | ... |
+-----+------+-------+---------------+--------------+--------------+--------------+
Desired output should be like this. However, since there are other Val1 and Val2 values has same ID, same Type but different order, INNER JOIN doesn't work as expected I guess. There are always bigger and smaller values appear than my filter. Some of them never shown.
Sorry If it's too long. I can't find the correct statment or structure. Any advice?
Edit: Sorry. I forgot to mention all other the data must be shown of IDs. That's why I'm trying to use join.

So, your requirement on the table is
Select Types (1,3,9)
Select Num (0,5)
Type 1, Num 5 should be bigger than 10 In Val2
Type 1, Num 5, should be smaller than 30 In Val2
Type 3, Num 0, should be smaller than 8 In Val1
That's basically 4 different criterias which need to be evaluated (Type, num, Val1, Val2)
While there might be "better" solutions (in Terms of performance or easier to read code) the easiest approach is to create distinct pairs of conditions, which should be fine unless you deal with millions of records.
Out of your requirement, I would derrive that you need the following row-sets to be fetched:
1.) Type = 1, Num = 5, Val2 > 10
2.) Type = 1, Num = 5, Val2 < 30
3.) Type = 3, Num = 0, Val1 < 8
4.) Type = 9
So, that would be a union of "4 explicit" queries:
SELECT * FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 > 10
UNION
SELECT * FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 < 10
UNION
SELECT * FROM analyse WHERE Type = 3 AND Num = 0 AND Val1 < 8
UNION
SELECT * FROM analyse WHERE Type = 9
To filter the final result by id, you can simply use:
SELECT * FROM (
SELECT * FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 > 10
UNION
SELECT * FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 < 10
UNION
SELECT * FROM analyse WHERE Type = 3 AND Num = 0 AND Val1 < 8
UNION
SELECT * FROM analyse WHERE Type = 9
) as tempTable WHERE id = 118
or just append the id to each independent query.

I have solved my problem with querying same Nums and Types together instead of fetching them with seperate joins. By that way, no other data for same Num and Type can pass the filter.
For that conditions
Select Types (1,3,9)
Select ANum (0,5)
Type 1, Num 5 should be bigger than 10 In Val2
Type 1, Num 5, should be smaller than 30 In Val2
Type 3, Num 0, should be smaller than 8 In Val1
Query should be this,
SELECT analyse.* FROM analyse
INNER JOIN
(SELECT ID FROM analyse WHERE Type = 1 AND Num = 5 AND Val2 < 30 AND Val2 > 10) AS a1 ON a1.SID = analyse.SID
(SELECT SID FROM analyse WHERE Type = 3 AND Num = 0 AND Val1 > 8) AS a3 ON a2.SID = analyse.SID
WHERE Type IN (1,3,9) AND Num IN (0,5) ORDER BY ID, Type, Order, Num ASC

Related

How do you return only groups that contain a specific value in MySQL?

Given a table mytable with 2 columns like
| foo | bar |
| a | 10 |
| b | 10 |
| b | 15 |
| c | 10 |
| c | 10 |
| c | 10 |
| d | 10 |
| d | 45 |
| e | 20 |
How I return the groups of foo that only contain the value 10?
The resulting dataset for the above should look like
| foo | bar |
| a | 10 |
| c | 10 |
Note that b and d do not get returned because 15 and 45 are also values in those groups.
You can use group by and having:
select foo
from mytable
group by foo
having min(bar) = max(bar) and min(bar) = 10;
if 10 is the minimum value:
having max(bar) = 10
You could use exists logic here:
SELECT DISTINCT foo
FROM yourTable t1
WHERE NOT EXISTS (SELECT 1 FROM yourTable t2 WHERE t2.foo = t1.foo AND t2.bar <> 10);
This query should benefit from the following index:
CREATE INDEX idx ON yourTable (foo, bar);
According to your requirement, you need to tell the system to
retrieve the records where bar=10
filter out those which contains bar <> 10
The following are the suggested codes:
select foo, bar from mytable
where
bar=10 and foo not in (select foo from mytable where bar <> 10)
group by foo

Select that fill null fields using the id and the values of the same column

I have a table like this:
PK | IDs | name1 | name2
-------------------
1 | 1 | a | null
2 | 1 | a | x
3 | 2 | b | null
4 | 3 | c | z
5 | 2 | null | y
6 | 1 | null | x
7 | 2 | b | null
8 | 2 | null | null
And i want to execute a select in mySQL that give me an output like this:
PK | IDs | name1 | name2
-------------------
1 | 1 | a | x
2 | 1 | a | x
3 | 2 | b | y
4 | 3 | c | z
5 | 2 | b | y
6 | 1 | a | x
7 | 2 | b | y
8 | 2 | b | y
So all the rows with the same id have the same name1 and name2 checking the one that its not null to fill it, if there is no one, it will continue as null.
If you only have one value of name1 or name2 for a given ID value, you can use an aggregation function like MAX (or MIN) which will give you that value from all the value for that IDs in the table. Using a derived table with those values, you can JOIN to the original table to get the name1 and name2 values for each PK, IDs combination:
SELECT d.PK, d.IDs, m.name1, m.name2
FROM data d
JOIN (SELECT IDs, MAX(name1) AS name1, MAX(name2) AS name2
FROM data
GROUP BY IDs) m ON m.IDs = d.IDs
Output:
PK IDs name1 name2
1 1 a x
2 1 a x
3 2 b y
4 3 c z
5 2 b y
6 1 a x
7 2 b y
8 2 b y
Demo on SQLFiddle
You can use correlated sub-query :
select t.pk, t.ids,
coalesce(t.name1, (select t1.name1
from table t1
where t1.pk < t.pk and t1.name1 is not null
order by t1.pk desc
limit 1)
) as name1,
coalesce(t.name2, (select t1.name2
from table t1
where t1.pk < t.pk and t1.name2 is not null
order by t1.pk desc
limit 1)
) as name2
from table t;
You can use update with join:
update t join
(select id, max(name1) as name1, max(name2) as name2
from t
group by id
) tt
on t.id = tt.id
set t.name1 = coalesce(t.name1, tt.name1),
t.name2 = coalesce(t.name2, tt.name2)
where t.name1 is null or t.name2. is null;
Note: This will not change any values that are not NULL, so it is safe even if the values differ for a given id.

Select count of rows matching a condition grouped by increments of Id in MySQL

I have a table that has an autoincremented numeric primary. I'm trying to get a count of rows that match a condition grouped by increments of their primary key. Given the data:
| id | value |
|----|-------|
| 1 | a |
| 2 | b |
| 3 | a |
| 4 | a |
| 5 | b |
| 6 | a |
| 7 | b |
| 8 | a |
| 9 | b |
| 10 | b |
| 11 | a |
| 12 | b |
If I wanted to know how many rows matched value = 'a' for every five rows, the result should be:
| count(0) |
|----------|
| 3 |
| 2 |
| 1 |
I can nest a series of subqueries in the SELECT statement, like such:
SELECT (SELECT count(0)
FROM table
WHERE value = 'a'
AND id > 0
AND id <= 5) AS `1-5`,
(SELECT count(0)
FROM table
WHERE value = 'a'
AND id > 5
AND id <=10) AS `6-10`,
...
But is there a way to do this with a GROUP BY statement or something similar where I don't have to manually write out the increments? If not, is there a more time efficient method than a series of subqueries in the SELECT statement as in the above example?
You could divide the ID by 5 and then ceil the result:
SELECT CONCAT((CEIL(id / 5.0) - 1) * 5, '-', CEIL(id / 5.0) * 5), COUNT(*)
FROM mytable
WHERE value = 'a'
GROUP BY CEIL(id / 5.0)
The following aggregated query should do the trick :
SELECT CEIL(id/5), COUNT(*)
FROM table
WHERE value = 'a'
GROUP BY CEIL(id/5)

Reset IDs to logical order in SQL

I have table like this, because some of the items have been removed:
+----+--------+--------+--------+------+-----+
| id | nav_id | active | name | link | ... |
+----+--------+--------+--------+------+-----+
| 1 | 1 | 1 | Item 1 | .......... |
| 6 | 2 | 1 | Item 2 | .......... |
| 15 | 1 | 1 | Item 3 | .......... |
| 16 | 3 | 1 | Item 4 | .......... |
| 75 | 1 | 1 | Item 5 | .......... |
+----+--------+--------+--------+------+-----+
As you can see, there are IDs in order 1, 6, 15, 16, 75 which can be sorted nicely. But what if I want them as 1, 2, 3, 4, 5? How can I reset their IDs, so it will be better to read? Just curious, not necessary.
You can use the following query:
UPDATE mytable AS t1
JOIN (
SELECT id, #rn := #rn + 1 AS rn
FROM mytable
CROSS JOIN (SELECT #rn := 0) AS v
ORDER BY id
) AS t2 ON t1.id = t2.id
SET t1.id = t2.rn;
But, I would advise against performing such an UPDATE. PK values should be immutable as their sole purpose is to uniquely identify a table record. If you want to change the way your table is displayed on the presentation layer, you can easily do so using a SELECT statement.

MySql - Increment according to another column

I have a table (innoDB) that has 3 columns: ID, ID_FATHER, ROWPOS. ID is auto_increment and ROWPOS has values from other table. I need ID_FATHER to be incremented by 1 if ROWPOS is not a sequence, if it is a sequence ID_FATHER should not increment.
Like this:
ID | ID_FATHER | ROWPOS
1 | 1 | 250
2 | 2 | 253
3 | 2 | 254
4 | 3 | 260
5 | 4 | 263
6 | 5 | 268
7 | 6 | 270
8 | 6 | 271
9 | 6 | 272
10 | 7 | 276
Is there a way to do that?
With this query:
INSERT INTO mytable (i, rowpos)
SELECT #i := IF(t.rowpos = #prev_rowpos + 1, #i, #i + 1) AS i
, #prev_rowpos := t.rowpos AS rowpos
FROM temp
JOIN (SELECT #prev_rowpos := NULL, #i := 0) v
ORDER BY t.rowpos
I am able to import into the tables I want. But the problem is in the TABLE.Service, as you can see with this solution the ID_FATHER is wrong because it only increments by 1
but in this case it actually should be 2 because invoice 1 doesn't have service.
How can I solve this problem without changing all my schema.
TABLE.temp
ROW|TYPE |INVOICE_temp
1 |xxx |10
2 |xxP |led tv
3 |xxP |mp3 Player
4 |xxx |11
5 |xxP |tv cable
6 |xxS |install
xxx = Invoice number
xxP = Product
xxs = service
TABLE.Invoice_Number TABLE.Product
ID|ID_FATHER|ROWPOS|NUM ID|ID_FATHER|ROWPOS|PROD
1 | 1 | 1 | 10 1 | 1 | 2 | led tv
2 | 2 | 4 | 11 2 | 1 | 3 | mp3 player
3 | 2 | 5 | tv cable
TABLE.Service
ID|ID_FATHER|ROWPOS|SERV
1 | 1 | 6 | install
I made some changes in the query to work as I needed.
You could do something like this:
INSERT INTO mytable (i, rowpos)
SELECT #i := IF(t.rowpos = #prev_rowpos + 1, #i, #i + 1) AS i
, #prev_rowpos := t.rowpos AS rowpos
FROM another_table t
JOIN (SELECT #prev_rowpos := NULL, #i := 0) v
ORDER BY t.rowpos
(Test just the SELECT query, get that working returning the resultset you want, before you preface it with the INSERT.)
For completeness, I will add that this technique is dependent on UNDOCUMENTED and non-guaranteed behavior in MysQL, using "user variables". I've successfully used this approach many times, but for "one off" type admin functions, not ever embedded as SQL in an application.
Note that the ORDER of the expressions in the SELECT list is important, they are evaluated in the order they appear in the SELECT list. (MySQL doesn't guarantee this behavior, but we do observe it. It's important that the check of the user variables containing values from the previous row to precede the assignment of the current row values to the user variables. That's why i is returned first, followed by rowpos. If you reversed the order of those in the SELECT list, the query would operate differently, and we wouldn't get the same results.
The purpose of the inline view (aliased as v) is to initialize the user variables. Since MySQL materializes that view query into a "derived table" before the outer query runs, those variables get initialized before they are referenced in the outer query. We don't really care what the inline view query actually returns, except that we need it to return exactly one row (because we reference it in a JOIN operation to the table we really want to query).
E.g.:
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,rowpos INT NOT NULL
);
INSERT INTO my_table (rowpos) VALUES
(250),
(253),
(254),
(260),
(263),
(268),
(270),
(271),
(272),
(276);
SELECT x.*
, #i:=#i+ISNULL(y.id) i
FROM my_table x
LEFT
JOIN my_table y
ON y.id < x.id
AND y.rowpos = x.rowpos - 1
, (SELECT #i:=0) vals
ORDER
BY x.id;
+----+--------+------+
| id | rowpos | i |
+----+--------+------+
| 1 | 250 | 1 |
| 2 | 253 | 2 |
| 3 | 254 | 2 |
| 4 | 260 | 3 |
| 5 | 263 | 4 |
| 6 | 268 | 5 |
| 7 | 270 | 6 |
| 8 | 271 | 6 |
| 9 | 272 | 6 |
| 10 | 276 | 7 |
+----+--------+------+