Selecting intersection of two queries with a single map table in MySQL - mysql

I'm sure this is easy, but so help me I can't figure out why I can't return the right result.
Pretty standard setup, I have a ref_product table, a ref_tagmap table and a ref_tag table...
CREATE TABLE `ref_product` (
`id` DOUBLE ,
`name` VARCHAR (765),
`familyid` DOUBLE );
INSERT INTO `ref_product` (`id`, `name`, `familyid`) VALUES('264','Old Red Fixture 1','4');
INSERT INTO `ref_product` (`id`, `name`, `familyid`) VALUES('30206','Modern Red Fixture 2','405');
CREATE TABLE `ref_tag` (
`TagID` DOUBLE ,
`TagName` VARCHAR (150));
INSERT INTO `ref_tag` (`TagID`, `TagName`) VALUES('103','Modern Contemporary');
INSERT INTO `ref_tag` (`TagID`, `TagName`) VALUES('131','Red');
CREATE TABLE `ref_tagmap` (
`MapID` DOUBLE ,
`tagid` DOUBLE ,
`containertype` VARCHAR (45),
`containerid` DOUBLE );
INSERT INTO `ref_tagmap` (`MapID`, `tagid`, `containertype`, `containerid`) VALUES('17035','131','PROD','264');
INSERT INTO `ref_tagmap` (`MapID`, `tagid`, `containertype`, `containerid`) VALUES('17747','131','PROD','30206');
INSERT INTO `ref_tagmap` (`MapID`, `tagid`, `containertype`, `containerid`) VALUES('31959','103','PROD','30206');
Querying these tables using:
SELECT DISTINCT ref_product.familyid,ref_tag.tagid
FROM (ref_tag,ref_product)
JOIN ref_tagmap AS mt2 ON mt2.containerid=ref_product.id
AND mt2.containertype='PROD'
AND mt2.tagid=ref_tag.tagid
AND ref_tag.tagname='red'
correctly returns all of the product familyids that have the tag 'red' mapped to them. Similarly:
SELECT DISTINCT ref_product.familyid,ref_tag.tagid
FROM (ref_tag,ref_product)
JOIN ref_tagmap AS mt1 ON mt1.containerid=ref_product.id
AND mt1.containertype='PROD'
AND mt1.tagid=ref_tag.tagid
AND LCASE(ref_tag.tagname)='modern contemporary'
correctly returns the product familyids that have the tag 'modern contemporary' mapped to them. QUESTION IS, HOW DO I RETURN A LIST OF ONLY THE PRODUCT FAMILYIDS THAT HAVE BOTH TAGS MAPPED TO THEM?
I'm trying this, and it returns empty:
SELECT DISTINCT ref_product.familyid,ref_tag.tagid
FROM (ref_tag,ref_product)
JOIN ref_tagmap AS mt2 ON mt2.containerid=ref_product.id
AND mt2.containertype='PROD'
AND mt2.tagid=ref_tag.tagid
AND ref_tag.tagname='red'
JOIN ref_tagmap AS mt1 ON mt1.containerid=ref_product.id
AND mt1.containertype='PROD'
AND mt1.tagid=ref_tag.tagid
AND LCASE(ref_tag.tagname)='modern contemporary'
I have to assume I'm missing something fundamental here...feeling dense. Please help.
Thanks!

The typical way to do this is to ensure that the number of distinct items in the tag table is equal to the number of tags you wish to isolate.
Example:
SELECT p.familyid
FROM ref_product p
JOIN ref_tagmap tm ON tm.containerid=p.id
AND tm.containertype='PROD'
JOIN ref_tag t ON t.tagid = tm.tagid
AND t.tagname IN ('red',
'modern contemporary')
GROUP BY p.familyid
HAVING count(DISTINCT t.tagid) = 2;
In action: http://sqlfiddle.com/#!2/f377e/7

Related

Find all linked records using self join

I have this table and I am trying to use self join.
I am able to achieve only 50% of the expected results.
create table auto_correct(spell varchar(255), ne varchar(255));
insert into auto_correct values ('test', 'testing');
insert into auto_correct values ('adn', 'and');
insert into auto_correct values ('and', 'andru');
insert into auto_correct values ('testing', 'tested');
insert into auto_correct values ('this', 'that');
insert into auto_correct values ('junk', 'delete');
spell
ne
test
testing
adn
and
and
andru
testing
tested
this
that
junk
delete
The word 'test' is linked to 'tested' through 'testing'
The word 'adn' is linked to 'andru' through 'and'
How do I find all such linked records and display along with non-linked rows?
The expected results are:
spell
ne
test
tested
adn
andru
this
that
junk
delete
I have tried this:
select b.spell, a.ne
from auto_correct as a
left join auto_correct as b on a.spell = b.ne
where b.spell is not null;
returns:
adn
andru
test
tested
The rest of the records where no match is found are missing from above output.
WITH RECURSIVE
cte AS ( SELECT *
FROM auto_correct
WHERE NOT EXISTS ( SELECT NULL
FROM auto_correct ac
WHERE auto_correct.spell = ac.ne )
UNION ALL
SELECT cte.spell, auto_correct.ne
FROM cte
JOIN auto_correct ON cte.ne = auto_correct.spell )
SELECT *
FROM cte
WHERE NOT EXISTS ( SELECT NULL
FROM auto_correct
WHERE auto_correct.spell = cte.ne )
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=e90235ce5dde96a7255f8afe1a19d334

Assign product category by string match from other table

I have a lots of products that we need to assign categories and a table, that contains keywords by which you can identify what goes where.
There may be multiple keywords for one category, in this case the first match from top should be used.
I came up with this test case, which works as I need:
CREATE TABLE Products
(`name` varchar(30), `category_id` int, `filter` varchar (30))
;
INSERT INTO Products
(`name`)
VALUES
('Plate'),
('Mushrooms'),
('Mushroom soup in a plate'),
('Mushroom on a plate'),
('Tomato soup'),
('Sausage'),
('Soupcan')
;
CREATE TABLE Filters
(`filter` varchar(30), `category_name` varchar(30))
;
INSERT INTO Filters
(`filter`, `category_name`)
VALUES
('Soup', 'Meals'),
('Mushroom', 'Ingredients'),
('Plate', 'Containers')
;
CREATE TABLE Categories
(`id` int, `name` varchar(30))
;
INSERT INTO Categories
(`id`, `name`)
VALUES
(1, "Ingredients"),
(2, 'Containers'),
(3, 'Meals')
;
update Products
left join Filters
on Products.name LIKE CONCAT('%',Filters.filter,'%')
join Categories
on Filters.category_name = Categories.name
set Products.category_id = Categories.id, Products.filter = Filters.filter;
select Products.*, Categories.name as category_name from Products
left join Categories on Products.category_id = Categories.id
Category gets assigned by first match in "filters", just as I need.
But if I add another row to Filter table that has same category_id (for example try adding: ('Soupcan', 'Containers') to Filters), the results will not be the first match (or the last).
"Mushroom soup in a plate" must go to "Meals", because it matches first filter row "Soup".
But if I add 'Soupcan', 'Containers' row to Filters - "Mushroom soup in a plate" will now match "Mushroom" and get into "Ingredients" category.
I think the problem lies in sorting between first and second join, but I can't figure it out.
If you know a better approach to this task - feel free to offer.
I just need to sort products by keyword matching, where topmost keyword matched will have the priority.
SQLFiddle
I don't know what kind of SQL you are using.
But what you are looking for is a join of a subselect of the Filter table.
See these links for more info:
SQL Server: How to Join to first row
LEFT JOIN only first row

Make query inner query result selectable

I was wondering if it is possible using MySQL to somehow, wrap the select of the inner query so that the outer query can use it in its where clause.
SELECT
`FirstName`,
`Surname`,
`AddressLine1`,
`AddressLine2`,
`AddressLine3`,
`AddressLocale`,
`AddressRegion`,
`AddressZip`,
`AddressCountry`,
`CopyShipAddress`
FROM `Contacts`
WHERE `EEID`
IN
(SELECT CONCAT_WS(',', `Sender`, `Receiver`, `Buyer` ) AS EEID_list
FROM `Transactions`
WHERE `TransactionID` = 3)
Sender, Receiver and Buyer are EEIDs. Perhaps there is a function other than CONCAT_WS I can use that will provide me with this functionality.
dont use concat_ws records on IN query , it may not give correct data
concat_ws may work perfectly for IN query with integers but may not work for strings because they need to be enclosed in quotes '
try below instead
SELECT
`FirstName`,
`Surname`,
`AddressLine1`,
`AddressLine2`,
`AddressLine3`,
`AddressLocale`,
`AddressRegion`,
`AddressZip`,
`AddressCountry`,
`CopyShipAddress`
FROM `Contacts`
WHERE `EEID`
IN
(
select Sender as eeid FROM Transactions WHERE TransactionId=3
UNION ALL
select Receiver as eeid FROM Transactions WHERE TransactionId=3
UNION ALL
select Buyer as eeid FROM Transactions WHERE TransactionId=3
)

MySQL combining records from two tables with join and without

What's the difference between these two queries:
SELECT `threads`.`id` AS `threads.id` , `posts`.`id` , `posts`.`content`
FROM `threads`
JOIN `posts` ON `threads`.`id` = `posts`.`thread_id`
And
SELECT `threads`.`id` AS `threads.id` , `posts`.`id` , `posts`.`content`
FROM `threads` , `posts`
WHERE `threads`.`id` = `posts`.`thread_id`
They both return same data.
WHERE clause filtering result set which is returned by JOIN, so this is a difference.
As long as you are using INNER JOIN there is no differences neither performance nor execution plan, in case of any OUTER JOIN query would produce different execution plan.
Also pay attention to what is said in the MySql online doc:
Generally, you should use the ON clause for conditions that specify
how to join tables, and the WHERE clause to restrict which rows you
want in the result set.
One uses ANSI Joins the other is using pre-ansi style join. MOST DB engines will compile them into the same execution plan.
In one word: Readability.
Running the following code:
create table #threads (
id int
)
create table #posts (
id int,
thread_id int,
content varchar(10)
)
insert into #threads values (1)
insert into #threads values (2)
insert into #posts values (1, 1, 'Stack')
insert into #posts values (2, 2, 'OverFlow')
SELECT #threads.id AS 'threads.id' , #posts.id , #posts.content
FROM #threads
JOIN #posts ON #threads.id = #posts.thread_id
SELECT #threads.id AS 'threads.id' , #posts.id , #posts.content
FROM #threads, #posts
WHERE #threads.id = #posts.thread_id
drop table #threads
drop table #posts
in https://data.stackexchange.com/stackoverflow/query/new you'll get the same execution plan :)
The only real difference is that inner join is ANSI and the from #threads, #posts is Transact-SQL syntax.
There might be difference in internal implementation of those queries, but I doubt it.
I'd personally prefer JOIN because it makes execution plan more obvious.

Zend_Db_Table, JOIN and mysql expressions in one query?

Im trying to build a complex (well...) query with Zend_Db_Table where I will both need to join the original table with an extra table and get some extra info from the original table with zend_db_expr.
However, things go wrong from the start. What I to is this:
$select = $this->getDbTable()->select(Zend_Db_Table::SELECT_WITH_FROM_PART)
->setIntegrityCheck(false);
$select->from( $this->getDbTable() , array(
'*' ,
new Zend_Db_Expr('`end` IS NULL as isnull') ,
new Zend_Db_Expr('`sold` IN (1,2,3) as issold') ,
) );
Zend_Debug::dump( $select->__toString() );exit;
What results in this:
SELECT `items`.*, `items_2`.*, `end` IS NULL as isnull, `sold` IN (1,2,3) as issold FROM `items`
INNER JOIN `items` AS `items_2`
What I need it to be though, at this point before I do the join with the other table, is
SELECT `items`.*, `end` IS NULL as isnull, `sold` IN (1,2,3) as issold FROM `items`
I don't need an inner join with itself, I just need to add those two Zend_Db_Expr to the things that should be selected, after which I'd continue building the query with the join and where's etc. like
$select->joinLeft( ... )
->where(...)
Any ideas? Cheers.
You should not redo a ->from() call, which means yu add a new table in the query.
Instead you should just use ->where()->columns() calls containing you Zend_Db_expr.
edit: sorry for the mistake.