Reuse MySQL subquery in various WHERE clause without subquery duplication - mysql

Extracting a set record ids from a translations table using a subquery.
Then need to feed this set of ids to several WHERE clauses of another query in order to extract the record from a specific table (product_listings) via a series of joins.
Table join structure
product_brands(1) <-> (n)products(1) <-> (n)product_categories(1) <-> (n)product_listings
The set of ids returned by the subquery can be for any of the 4 tables above.
Subquery returning the sets of ids
select
record_id
from
translations
where
translations.locale = 'en_CA'
and (
translations.table = 'product_listings'
or translations.table = 'product_categories'
or translations.table = 'products'
or translations.table = 'product_brands'
)
and MATCH (translations.translation) AGAINST ('+jack*' IN BOOLEAN MODE);
Main query here using the ids in WHERE clauses
select
product_listings.*
from
product_listings
left join product_categories on product_categories.ch_id = product_listings.ch_vintage_id
left join products on products.ch_id = product_categories.ch_product_id
left join product_brands on product_brands.ch_id = products.ch_brand_id
where
product_listings.ch_id in (5951765, 252242) <---| Replace these fixed ids
or product_categories.ch_id in (5951765, 252242) <---| with the "record_id" set
or products.ch_id in (5951765, 252242) <---| returned by the subquery
or product_brands.ch_id in (5951765, 252242); <---|
Both queries works perfectly independently. But cannot succesfully merge them into one.
Only dirty way I found is to repeat the subquery at each WHERE clause. Tried it and it works, but doubtfully the most effective and optimized way to do it.
Tried using variable, but only one value can be stored - unfortunately not a viable option.
Spent countless hours ressearching on how to avoid repeating a subquery and been rewriting those in many ways, but still can't get it to work.
Any suggestion on how to integrate the subquery elegantly and efficiently?
Currently working with Mysql Ver 14.14 Distrib 5.7.37, for Linux (x86_64)
UPDATE 2022/04/16: Adding sample data of translations table and expected results of both queries
Sample of the translations table with those 2 ids
+-----------+----------------+--------+-------------------------------+
| record_id | table | locale | translation |
+-----------+----------------+--------+-------------------------------+
| 5951765 | products | en_CA | Jack Daniel's |
| 252242 | product_brands | en_CA | Dixon's & Jack Daniel's |
+-----------+----------------+--------+-------------------------------+
Here is the subquery response
+-----------+
| record_id |
+-----------+
| 5951765 |
| 252242 |
+-----------+
And a the main query response (final expected results) using the set of hardcoded ids. I modified the select clause to return specific columns to make the table readable instead of the '*'.
First 2 columns are the located set of ids in the products and product_brands table and 2 other one are from the corresponding product_listings record extracted via the joins.
+------------+----------+--------------+-----------------+
| product_id | brand_id | listing_cspc | listing_format |
+------------+----------+--------------+-----------------+
| 5951765 | 5936861 | 798248 | 6x750 |
| 5951765 | 5936861 | 545186 | 6x750 |
| 5951956 | 252242 | 400669 | 12x750 |
| 5951955 | 252242 | 400666 | 12x750 |
| 5951701 | 252242 | 437924 | 12x750 |
| 5951337 | 252242 | 20244 | 6x750 |
| 5950782 | 252242 | 65166 | 12x750 |
| 5950528 | 252242 | 104941 | 12x750 |
| 5949763 | 252242 | 13990091 | 12x750 |
| 5949750 | 252242 | 614064 | 12x750 |
...
| 1729121 | 252242 | 280248 | 12x750 |
| 1729121 | 252242 | 36414 | 12x750 |
+------------+----------+--------------+-----------------+
As you can see, the ids from the subquery are matching different column. In this case 5951765 is the products.ch_id and the 252242 is the product_brands.ch_id.
Below is a visual representation of what I'm trying to achieve considering the current (1):(n) relations of the tables

Translations seems to be the driver for this so I would consider a view and drive from the view
Create view yoursubquery as vids;
select
product_listings.*
from vids
left join product_listings pn product_listings.id = vids.record_id
left join product_categories on product_categories.ch_id = product_listings.ch_vintage_id
left join products on products.ch_id = product_categories.ch_product_id
left join product_brands on product_brands.ch_id = products.ch_brand_id
Sample data would be good..

FINALLY! Got it to work.
With #P.Salmon suggestion to store the subquery result in a view, I then did a cross join on that view and use the results in the WHERE clause of the main query.
But that led me to now simply skip the view and the true final solution is to put the subquery in the cross join thus skipping the view.
Sleek and VERY performant.
Final query with subquery in the croos join
select
product_listings.*
from
product_listings
cross join (
select
record_id
from
translations
where
translations.locale = 'en_CA'
and (
translations.table = 'product_listings'
or translations.table = 'product_categories'
or translations.table = 'products'
or translations.table = 'product_brands'
)
and MATCH (translations.translation) AGAINST ('+jack*' IN BOOLEAN MODE)
) as vids
left join product_categories on product_categories.ch_id = product_listings.ch_vintage_id
left join products on products.ch_id = product_categories.ch_product_id
left join product_brands on product_brands.ch_id = products.ch_brand_id
where
product_listings.ch_id = vids.record_id
or product_categories.ch_id = vids.record_id
or products.ch_id = vids.record_id
or product_brands.ch_id = vids.record_id
order by
product_brands.ch_id desc,
products.ch_id desc;

Related

SQL How to make inner join query from sentences table to itself using linking table

I have 2 tables
sentences
-----------
id | lang | sentence
links
-------------
sentence_id | translation_id
I've tried to do something like this
SELECT *
FROM sentences_unicode AS s
INNER JOIN links AS l ON l.sentence_id = s.id
INNER JOIN sentences_unicode AS t ON l.translation_id = t.id
WHERE s.lang = 'pol' AND s.sentence LIKE "%pasek%"
But this doesn't work. I tought it is correct, but this seems to timeout. Maybe because there are several millions of sentences. Is there any other way to get
sentences and their translations for given search conditions ?
MySQL said: Documentation
#2006 - MySQL server has gone away
mysql> DESCRIBE links;
+----------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------------+---------+------+-----+---------+-------+
| sentence_id | int(11) | NO | | NULL | |
| translation_id | int(11) | NO | | NULL | |
mysql> DESCRIBE sentences_unicode;
+----------+----------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+----------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| lang | varchar(3) | NO | | NULL | |
| sentence | varchar(15000) | NO | | NULL | |
+----------+----------------+------+-----+---------+-------+
3 rows in set (0.02 sec)
I can only execute single INNER JOIN and it tooks about 26 sec
I've tried this
SELECT *
FROM sentences_unicode AS s
INNER JOIN links AS l ON l.sentence_id = s.id
WHERE s.lang = 'pol' AND MATCH (s.sentence) AGAINST ('pasek' IN NATURAL LANGUAGE MODE)
If your query times out it's not a syntax error, it's a performance problem.
Like with '%search%' it's the slowest possible! If you have an index on the sentence column, searches LIKE 'pasek%' will be faster but will not search inside the string, only strings that start with pasek.
You can optimize by creating a FULLTEXT index on the sentence column.
https://dev.mysql.com/doc/refman/8.0/en/fulltext-search.html
SELECT *
FROM sentences_unicode AS s
INNER JOIN links AS l ON l.sentence_id = s.id
INNER JOIN sentences_unicode AS t ON l.translation_id = t.id
WHERE s.lang = 'pol' AND MATCH (sentence) AGAINST ('pasek') IN NATURAL LANGUAGE MODE
In MySQL versions, prior to 5.6.4, Full-Text search was only available on MyISAM tables. With the general phasing out of MyISAM, InnoDB full-text search (FTS) is finally available since MySQL 5.6.4
Also, you should create indexes on the columns you use in the joins: sentence_id, translation_id, etc. It would have been useful the see the table definitions.

In mysql how can I get only rows from one table which do not link to any rows in another table with a specific ID

I have two tables with the following structures (unnecessary columns trimmed out)
----------------- ---------------------
| mod_personnel | | mod_skills |
| | | |
| - prs_id | | - prs_id |
| - name | | - skl_id |
----------------- | |
---------------------
There may be 0 to many rows in the skills table for each prs_id
What I want is all the personnel records which do NOT have an associated skill record with skill_id 1.
In plain English "I want all the people who do not have the skill x".
Currently, I have only been able to do it with the following nested select. But I am hoping to find a faster way.
SELECT * FROM `mod_personnel` WHERE `prs_id` NOT IN (
SELECT `prs_id` FROM `mod_skills` WHERE `skl_id` = 1 )
This may be faster:
SELECT `mod_personnel`.*
FROM `mod_personnel`
left outer join `mod_skills`
on `mod_skills`.`prs_id` = `mod_personnel`.`prs_id`
and `mod_skills`.`skl_id` = 1
WHERE `mod_skills`.`prs_id` is null;
Using a NOT EXISTS might be faster.
SELECT *
FROM `mod_personnel` p
WHERE NOT EXISTS (SELECT *
FROM `mod_skills` s
WHERE s.`prs_id` = p.`prs_id`
AND s.`skl_id` = 1 );

Concatenate fields of rows with the same ID in MySQL

I have the following query:
SELECT mutations.id, genes.loc FROM mutations, genes where mutations.id=genes.id;
and outputs this:
| SL2.50ch02_51014904 | intergenic |
| SL2.50ch02_51014907 | upstream |
| SL2.50ch02_51014907 | downstream |
| SL2.50ch02_51014907 | intergenic |
| SL2.50ch02_51014911 | upstream |
| SL2.50ch02_51014911 | downstream |
My desired output is this:
| SL2.50ch02_51014904 | intergenic |
| SL2.50ch02_51014907 | upstream,downstream,intergenic |
| SL2.50ch02_51014911 | upstream,downstream |
I thought GROUP_CONCAT was useful for this. However, doing this:
SELECT mutations.id, GROUP_CONCAT(distinct(genes.loc)) FROM mutations, genes WHERE mutations.id=genes.id;
I have a unique row like this:
SL2.50ch02_51014904 | downstream,intergenic,upstream
How can I solve this?
You need to add group by:
SELECT m.id, GROUP_CONCAT(distinct(g.loc))
FROM mutations m JOIN
genes g
ON m.id = g.id
GROUP BY m.id;
Along the way, you should learn a couple other things:
Use explicit join syntax. A simple rule: never use commas in the from clause.
Use table aliases (the m and g). They make the query easier to write and to read.
You forgot the GROUP BY:
SELECT
mutations.id,
GROUP_CONCAT(DISTINCT(genes.loc))
FROM
mutations, genes
WHERE
mutations.id=genes.id
GROUP BY
mutations.id

mysql select data from 3 tables using user input

I have 3 tables with the following columns and values (they have multiple entries but I'm showing you one):
protein
+--------+------+-------------+
| pdb_id | name | description |
+--------+------+-------------+
| 1AF6 | porin| maltoporin |
+--------+------+-------------+
organism:
+--------+-----------------------+
| org_id | organismName |
+--------+-----------------------+
| 4 | Comamonas acidovorans |
+--------+-----------------------+
protein_organism:
+--------+--------+
| pdb_id | org_id |
+--------+--------+
| 1AF6 | 4 |
+--------+--------+
I'm making a website where someone can see all the proteins from a specific organism that can be selected from a drop down menu.
However when I try to fetch the data the browser goes to the correct url: http://localhost:8084/response_organism?organismName=Comamonas+acidovorans
but shows nothing.
This is my sql command:
query = "SELECT * FROM protein JOIN protein_organism ON protein_organism.pdb_id = protein.pdb_id JOIN organism ON organism.org_id = protein_organism.org_id WHERE organism.organismName="+po;
po (string) is the user input fetched from my index.jsp form
What is wrong with my sql command?
You are querying the wrong way. This is the correct way of query with JOIN.
"SELECT *
FROM protein P
INNER JOIN protein_organism PO
ON PO.pdb_id = P.pdb_id
LEFT JOIN organism O
ON O.org_id = PO.org_id
WHERE O.organismName="+po;
Make appropriate changes to this query if you have some error or different result when run it.

one to many mapping without any relation in tables mysql

i have two tables named as oc_users and oc_groups there is no specific relation between both the tables as shown below so, here i want to map each user with each group:
1)table 1:
select uid from oc_users;
+-----------------+
| uid |
+-----------------+
| manesh#abc.in |
| pankaj |
| sumit |
+-----------------+
2)table 2:
select gid from oc_groups;
+---------+
| gid |
+---------+
| qlc |
| qlc-web |
+---------+
Then i want o/p like:
+---------+-----------------+
| gid | uid |
+---------+-----------------+
| qlc | manesh#abc.in |
| qlc | pankaj |
| qlc | sumit |
| qlc-web | manesh#abc.in |
| qlc-web | pankaj |
| qlc-web | sumit |
+---------+-----------------+
Use this
select * from oc_user , oc_groups ORDER BY gid, uidS
This will print all columns with Cartesian multiplecation of rows.
you need to use CROSS JOIN (new SQL Syntax: ANSI SQL-92)
SELECT gid, uid
FROM oc_users CROSS JOIN oc_groups
ORDER BY gid, uid
SQLFiddle Demo
Using CROSS JOIN(Cartesian Product Join) will solve your purpose. For your information
A cross join that does not have a WHERE clause produces the Cartesian product of the tables involved in the join. The size of a Cartesian product result set is the number of rows in the first table multiplied by the number of rows in the second table.
So simply this will do,
SELECT gid, uid
FROM oc_users CROSS JOIN oc_groups;
Always go for 'CROSS JOIN' syntax instead of giving nothing. Since this is ANSI format you can use it across database and it will be useful during migration also as you dont need to change anything in your query.
Hope this helps you!!