Conditionally select some parts of table - mysql

I'm trying to conditionally select some columns of my table.
The structure of my table could look weird, but I have no influence on that:
| id | col1|1 | col2|1 | col1|2 | col2|2 | col1|3 | col2|3 |
|:--:|:------------:|:------------:|:------------:|:------------:|:------------:|:------------:|
| 1 | some | meaningless | text | don't | mind | me |
| 2 | abc | def | NULL | NULL | my | text |
| 3 | dummytext... | dummytext... | dummytext... | dummytext... | dummytext... | dummytext... |
This table is divided into 3 parts, marked with a |X at the end.
col1|1 and col2|1
col1|2 and col2|2
col1|3 and col2|3
I only want each part, if col2 of that part IS NOT NULL.
This is my approach:
SELECT t1.`col1|1`, t1.`col2|1`, t2.`col1|2`, t2.`col2|2`, t3.`col1|3`, t3.`col2|3`
FROM tab1 t1
LEFT JOIN tab1 t2 On t1.`id` = t2.`id`
LEFT JOIN tab1 t3 on t1.`id` = t3.`id`
WHERE
t1.`col2|1` IS NOT NULL
AND t2.`col2|2` IS NOT NULL # this column is NULL, so I don't want it (including table t2)
AND t3.`col2|3` IS NOT NULL
AND t1.`id` = 2
AND t2.`id` = 2
AND t3.`id` = 2
If works only if all col2 are NOT NULL, but if 1 of them IS NULL, the whole result is empty.
If you replace the both NULL-values in my example table, you would get all 6 columns, which would be right, as no part would be NULL in this case.
In my example, I want that output:
| col1|1 | col2|1 | col1|3 | col2|3 |
|:------:|:------:|:------:|:------:|
| abc | def | my | text |
Here is a fiddle.

I modified your code:
SELECT t1.`col1|1`, t1.`col2|1`, t2.`col1|2`, t2.`col2|2`, t3.`col1|3`, t3.`col2|3`
FROM
tab1 t
LEFT JOIN tab1 t1 On t.`id` = t1.`id` AND t1.`col2|1` IS NOT NULL
LEFT JOIN tab1 t2 On t.`id` = t2.`id` AND t2.`col2|2` IS NOT NULL
LEFT JOIN tab1 t3 on t.`id` = t3.`id` AND t3.`col2|3` IS NOT NULL
WHERE
t.`id` = 2
It realizes the described logic, but doesn't exclude the NULL columns, the result of the query:
+----+--------+--------+--------+--------+--------+--------+
| | col1|1 | col2|1 | col1|2 | col2|2 | col1|3 | col2|3 |
+----+--------+--------+--------+--------+--------+--------+
| 1 | abc | def | NULL | NULL | my | text |
+----+--------+--------+--------+--------+--------+--------+

In MySQL, a query can not generate the dynamic number of columns in its result. So it's not possible to conditionally select some columns of a table. The result of your query will always return six columns.
But you could try to select all 3 parts of the table. For each part, if col2 of that part is NULL then col1 of that part will be NULL also.
SELECT IF(`col2|1` IS NULL, NULL, `col1|1`) AS `col1|1`, `col2|1`,
IF(`col2|2` IS NULL, NULL, `col1|2`) AS `col1|2`, `col2|2`,
IF(`col2|3` IS NULL, NULL, `col1|3`) AS `col1|3`, `col2|3`
FROM tab1
WHERE `id` = 2

Related

Coalessing with condition in related table

I am working in MySQL 5.7.35 and I have the following tables:
create table Table1 (
Id int not null auto_increment,
Name varchar(255) not null,
primary key(Id)
);
create table Table2 (
Id int not null auto_increment,
Name varchar(255) not null,
Table1_Id int not null,
primary key(Id),
foreign key(Table1_Id) references Table1(Id)
);
create table Table3 (
Id int not null auto_increment,
Type varchar(255) not null,
Name varchar(255) not null,
Result varchar(255) not null,
Table2_Id int not null,
primary key(Id),
foreign key(Table2_Id) references Table2(Id)
);
Inside, I have the following data:
| Id | Name |
| --- | ---------- |
| 1 | Computer A |
---
| Id | Name | Table1_Id |
| --- | ---------- | --------- |
| 1 | Test Run 1 | 1 |
---
| Id | Type | Name | Result | Table2_Id |
| --- | --------- | --------- | ------- | --------- |
| 1 | Processor | MMX | Pass | 1 |
| 2 | Processor | SSE | Pass | 1 |
| 3 | Processor | SSE 2 | Pass | 1 |
| 4 | Display | Red | Pass | 1 |
| 5 | Display | Green | Pass | 1 |
| 6 | Keyboard | General | Pass | 1 |
| 7 | Keyboard | Lights | Skipped | 1 |
| 8 | Network | Ethernet | Pass | 1 |
| 9 | Network | Wireless | Skipped | 1 |
| 10 | Network | Bluetooth | Fail | 1 |
Desired Query
I would like two columns table1_name and test_result where test_result is a concatenated string with the following logic:
For any given value in Type:
If all are passes, then the result is a Pass
If any are fails, then the result is a Fail
If any are Skipped (poviding the first two points are checked), then the result is Skipped.
So for the current data, the output will be:
| table1_name | test_result |
| ----------- | ---------------------------------------------------------------- |
| Computer A | Processor: Pass, Display: Pass, Keyboard: Skipped, Network: Fail |
Current Query
I am struggling to do the coalecing bit when the items I wish to coalesce are in a child table two levels down. My current query is:
select t1.Name as 'table1_name'
-- coalesce to happen here
from Table1 t1
inner join Table2 t2 on t1.Id = t2.Table1_Id
inner join Table3 t3 on t2.Id = t3.Table2_Id;
I have created a db-fiddle to make things easier.
Use GROUP_CONCAT() to collect all Results for each Name and Type combination in your preferred order and then in another level of aggregation pick the the first 1:
SELECT table1_name,
GROUP_CONCAT(Type, ': ', SUBSTRING_INDEX(Results, ',', 1) SEPARATOR ', ') test_result
FROM (
SELECT t1.Name table1_name, t3.Type,
GROUP_CONCAT(Result ORDER BY Result = 'Fail' DESC, Result = 'Skipped' DESC) Results
FROM Table1 t1
INNER JOIN Table2 t2 on t1.Id = t2.Table1_Id
INNER JOIN Table3 t3 on t2.Id = t3.Table2_Id
GROUP BY t1.Name, t3.Type
) t
GROUP BY table1_name;
If you want to preserve the order of Types in the results:
SELECT table1_name,
GROUP_CONCAT(Type, ': ', SUBSTRING_INDEX(Results, ',', 1) ORDER BY Id SEPARATOR ', ') test_result
FROM (
SELECT t1.Name table1_name, MIN(t3.Id) Id, t3.Type,
GROUP_CONCAT(Result ORDER BY Result = 'Fail' DESC, Result = 'Skipped' DESC) Results
FROM Table1 t1
INNER JOIN Table2 t2 on t1.Id = t2.Table1_Id
INNER JOIN Table3 t3 on t2.Id = t3.Table2_Id
GROUP BY t1.Name, t3.Type
) t
GROUP BY table1_name;
See the demo.
This looks like two levels of aggregation:
select Name, group_concat(name, ': ', result separator ', ')
from (select t1.Name, t3.type,
(case when min(result) = max(result) then min(result)
else 'Skipped'
end) as result
from Table1 t1 inner join
Table2 t2
on t1.Id = t2.Table1_Id inner join
Table3 t3
on t2.Id = t3.Table2_Id
group by t1.Name, t3.type
) nt
group by Name;

How to organize my query with so many ANDs

My query looks like:
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE cpa.attribute_id=10
AND cpa.attribute_value_id=36
AND cpa.attribute_id=2
AND cpa.attribute_value_id=5
AND cpa.attribute_id=7
AND cpa.attribute_value_id=31
AND cpa.attribute_id=9
AND cpa.attribute_value_id=28
AND cpa.attribute_id=8
AND cpa.attribute_value_id=25
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC
In simple words - each of the AND condtitions evaluate to true. If I execute them one by one it is OK. But when I try to execute it like what I posted above - no results are returned. I am sure am not doing right the multiple AND conditions part. The ct_product_attribute table:
+--------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| product_id | int(11) | YES | MUL | NULL | |
| attribute_set_id | int(11) | YES | MUL | NULL | |
| attribute_id | int(11) | YES | MUL | NULL | |
| attribute_value_id | int(11) | YES | MUL | NULL | |
| value | varchar(255) | YES | | NULL | |
+--------------------+--------------+------+-----+---------+----------------+
Will post the other tables if needed. Just trying to not flood the post. Thank you!
EDIT
In ct_product I got products like ( just for example ):
id
1
2
3
In ct_product_attribute each product can have more than one attribute-attr.value pairs. Some of the pairs are same.( will show only the columns that I need )
id product_id attribute_id attribute_value_id
1 1 1 1
2 2 1 1
3 1 2 1
4 2 3 1
5 3 1 1
6 3 2 1
The values that I get from the request are:
attribute_id=1
attribute_value_id=1
attribute_id=2
attribute_value_id=1
And now I have to retrieve only the product with id=1. If I use OR it is retrieving both products id=1 and id=2. Not sure if it gets more clear now.
I'm pretty sure those are supposed to be ORs because you can't have all those IDs at the same time. With that in mind, you should be able to use IN.
WHERE cpa.attribute_id IN (10,2,7,9,8)
AND cpa.attribute_value_id IN (36,5,31,28,25)
I really don't know what you are trying to accomplish but you should/could use WHERE IN, as everyone pointed in the comments you are looking for a field with multiple values...
But, as for the AND question, you could/should use IN, as in;
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE cpa.attribute_id IN (10, 2, 7, 9, 8)
AND cpa.attribute_value_id IN (36, 5, 31, 28, 25)
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC
You can try using (cpa.attribute_id,cpa.attribute_value_id) in ((10,36),(2,5),(7,31),(9,28),(8,25))
SELECT SUM(ct_product_store_quantity.quantity) as quantity, `ct_product`.*
FROM `ct_product`
LEFT JOIN `ct_productLang` ON `ct_product`.`id` = `ct_productLang`.`product_id`
LEFT JOIN `ct_product_store_quantity` ON `ct_product`.`id` = `ct_product_store_quantity`.`product_id`
LEFT JOIN `ct_product_attribute` as cpa ON ct_product.id=cpa.product_id
WHERE (cpa.attribute_id,cpa.attribute_value_id) in ((10,36),(2,5),(7,31),(9,28),(8,25)) and `ct_product`.`id`=1
GROUP BY `ct_product`.`id`
HAVING quantity > 0
ORDER BY `id` DESC

MYSQL: Return all rows in one table along with the sum of matching rows in another

EDIT - I apologize but I didn't include the correct information the first time!
I have the following two tables:
table 1
+----+-------+-------+
| id | model | color |
+----+-------+-------+
| 1 | 111AA | red |
| 2 | 222BB | blue |
| 3 | 333CC | |
| 4 | 444DD | green |
+----+-------+-------+
table 2
+----+-------+-------+
| id | model | quant |
+----+-------+-------+
| 6 | 111AA | 2 |
| 7 | 222BB | 5 |
| 8 | 222BB | 3 |
+----+-------+-------+
I need a query that will take all the rows from table 1 where the color column is not empty along with the sum of the column quantity in table two that match a certain model (in the example given, model = '222BB') to produce the following table:
+----+-------+-------+------+
| id | model | color | quant|
+----+-------+-------+------+
| 1 | 111AA | red | |
| 2 | 222BB | blue | 8 |
| 4 | 444DD | green | |
+----+-------+-------+------+
This is what I tried so far:
SELECT t1.id, t1.model, t1.color, SUM(t2.quant)
FROM table1 t1 LEFT OUTER JOIN table2 t2
ON t1.id = t2.id
WHERE t1.color != '' AND t2.model = '222BB'
However, this didn't work.
Any help is greatly appreciated.
Thanks!
To receive the expected table, run the following SQL query:
SELECT t1.id, t1.model, t1.color, IF(t2.model = '222BB', SUM(t2.quant), NULL)
FROM table1 t1
LEFT JOIN table2 t2 ON t1.model = t2.model
WHERE t1.color != ''
GROUP BY t1.model
The result will be the same as in your table. But I think it would be better to update the design to make join on ID column but not model-name.
Try this,
select t1.id, t1.model,t1.color,sum(t2.quant)
from table1 t1
left outer join table2 t2 on (t1.model = t2.model and t1.color <> ‘’)
group by t1.model
In Sql, you should not write != or == with null. It is highly suggested that you use IS NULL and IS NOT NULL clause.
http://www.tutorialspoint.com/sql/sql-null-values.htm
** select a.model,b.total from (select model from table1 where color is not null) a, (select model,sum(quant) total from table2 group by model) b where a.model=b.model;
**

Update a column with a calculated value

Here is the table I use:
+-------------+----------+---------------------+
| sourceindex | source | pa |
+-------------+----------+---------------------+
| 0 | this | 0.13842974556609988 |
| 1 | is | 0.26446279883384705 |
| 2 | a | 0.26446279883384705 |
| 3 | book | 0.13842974556609988 |
| 4 | , | 0.26446279883384705 |
| 5 | that | 0.13842974556609988 |
I want to add a column which will be the result log(sum(pa))/pa.
Any suggestions on how I could do that?
You can use a cross join to to calculate log(sum(pa)) and in your outer you can divide the result with each value of pa colum
update
test t
join (select
`sourceindex`, `source`, `pa` , log_sum/pa new_col
from
test
cross join (select log(sum(pa)) log_sum
from test ) a
) t1
on (t.sourceindex= t1.sourceindex
and t.source = t1.source
and t.pa = t1.pa
)
set t.new_col = t1.new_col
Demo
But its better if you switch your logic to show your calculation with select query
select `sourceindex`, `source`, `pa` , log_sum/pa new_col
from
test
cross join (select log(sum(pa)) log_sum
from test ) t
Demo

MySQL Duplicate rows - specify columns

How can I run a query that finds duplicates between rows? It needs to not match one field but multiple.
Here is the EXPLAIN of the table.
+-------------+--------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+-------------------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| token | varchar(64) | NO | MUL | NULL | |
| maxvar | float | NO | | NULL | |
| maxvbr | float | NO | | NULL | |
| minvcr | float | NO | | NULL | |
| minvdr | float | NO | | NULL | |
| atype | int(11) | NO | | NULL | |
| avalue | varchar(255) | NO | | NULL | |
| createddate | timestamp | NO | | CURRENT_TIMESTAMP | |
| timesrun | int(11) | NO | | NULL | |
+-------------+--------------+------+-----+-------------------+----------------+
I need to match all rows that match: token,maxvar,maxvbr,minvcr,minvdr,type and avalue. If all of those fields match those in another row then treat it as a "duplicate".
Ultimately I want to run this as a delete command but I can easily alter the select.
UPDATE Still looking for solution that deletes with single query in MySQL
Just join the table to itself and compare the rows. You can make sure you keep the duplicate with the lowest ID by requiring the id to be deleted to be greater than the id of a duplicate:
DELETE FROM my_table WHERE id IN (
SELECT DISTINCT t1.id
FROM my_table t1
JOIN my_table t2
WHERE t1.id > t2.id
AND t1.token = t2.token AND t1.maxvar = t2.maxvar
AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr
AND t1.minvdr = t2.minvdr AND t1.type = t2.type)
This query will find all duplicate records which should be deleted -
SELECT t1.id FROM table_duplicates t1
INNER JOIN (
SELECT MIN(id) id, token, maxvar, maxvbr, minvcr, minvdr, atype, avalue FROM table_duplicates
GROUP BY token, maxvar, maxvbr, minvcr, minvdr, atype, avalue
HAVING COUNT(*) > 1
) t2
ON t1.id <> t2.id AND t1.token = t2.token AND t1.maxvar=t2.maxvar AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr AND t1.minvdr = t2.minvdr AND t1.atype = t2.atype AND t1.avalue = t2.avalue;
This query will remove all duplicates -
DELETE t1 FROM table_duplicates t1
INNER JOIN (
SELECT MIN(id) id, token, maxvar, maxvbr, minvcr, minvdr, atype, avalue FROM table_duplicates
GROUP BY token, maxvar, maxvbr, minvcr, minvdr, atype, avalue
HAVING COUNT(*) > 1
) t2
ON t1.id <> t2.id AND t1.token = t2.token AND t1.maxvar=t2.maxvar AND t1.maxvbr = t2.maxvbr AND t1.minvcr = t2.minvcr AND t1.minvdr = t2.minvdr AND t1.atype = t2.atype AND t1.avalue = t2.avalue;
SELECT token,maxvar,maxvbr,minvcr,minvdr,type, avalue,
Count(*)
FROM yourtable
GROUP BY token,maxvar,maxvbr,minvcr,minvdr,type, avalue
HAVING Count(*) > 1
This query returns all the rows that are in the table two times or more often (and how often they are).
Try:
SELECT token,maxvar,maxvbr,minvcr,minvdr,type,avalue, COUNT(*)
FROM table
GROUP BY token,maxvar,maxvbr,minvcr,minvdr,type,avalue
HAVING COUNT(*)>1