I've got two tables, one for listings and another representing a list of tags for the listings table.
In the listings table the tag ids are stored in a field called tags as 1-2-3-. This has worked out very well for me (regular expressions and joins to separate and display the data), but I now need to pull the titles of those tags into a single row. See below.
listings table
id tags
1 1-2-3-
2 4-5-6-
tags table
id title
1 pig
2 dog
3 cat
4 mouse
5 elephant
6 duck
And what I need to produce out of the listings table is:
id tags
2 mouse, elephant, duck
Here is a query that could help. But since it is doing some string operations, it may not be as good as a regular join:
select l.id, group_concat( t.title )
from listings l, tags t
where concat( '-', l.tags ) like concat( '%-', t.id, '-%' ) group by l.id ;
Unfortunately, with your tags stored in this denormalized format, there's no easy way to go from 1-2-3 to the corresponding tags. In other words, there's no simple way to split out the ids, join to another table and then recombine. Your best option would be to create a listing_tag table with two columns
listing_id tag_id
1 1
1 2
1 3
2 4
2 5
2 6
and then it's just a simple join:
SELECT listing_id, GROUP_CONCAT(title SEPARATOR ', ') FROM listing_tag JOIN tags ON tags.id = tag_id
GROUP_CONCAT() + INNER JOIN + GROUP BY
Related
I need to make one SQL command.
From table with comments i'll get comment id, then
with this ID I need to get count of reactions with the same comment ID and user's names.
So for example I have this 2 tables:
Comments:
ID
Comm_text
1
Example text
2
Another example
and Reactions:
ID
comm_id
usr
etc..
1
1
Peter
another
2
1
John
collon
3
1
Dog
cuz
4
2
Cat
why not
I need to get this:
ID
Comm_text
Reactions_Count
Users
1
Example text
3
Peter, John, Dog
2
Another example
1
Cat
I tried this:
SELECT k.id, k.comm, COUNT(r.id) as reactions, r.usr
FROM `comms` k
INNER JOIN `reactions` r ON r.id=k.id
It's just one row with one comment and count of all rows in reaction table.
Thanks.
Try this query that makes the same output:
select comments.id as ID , comments.Comm_text as Comm_text ,
(select count(id) from Reactions where comm_id = comments.id) as Reactions_Count ,
(select coalesce(GROUP_CONCAT(usr ORDER BY usr DESC) , '') from Reactions WHERE comm_id = comments.id) as Users
from comments group by comments.id
You should use group by to group the comments and have just one row then use query to count and gather the data, based on each row of the group.
The GROUP_CONCAT attach the output with , and the coalesce set the output to a given string if the output was empty.
Read more about:
GROUP BY
GROUP_CONCAT
COALESCE
subquery
According to the names that u set in the example, this will work. Just fix the table names for your database structure.
SELECT `Comments`.`ID`, `Comments`.`Comm_text`, count(`Reactions`.`comm_id`) as react, `Reactions`.`usr`
FROM `Comments`
INNER JOIN `Reactions`
ON `Comments`.`ID`=`Reactions`.`comm_id`
GROUP BY `Reactions`.`comm_id`
I can't find an answer to my problem on the site.
I have 3 tables: data, tags and data_tag_rel.
data
id data
------------------------------------
1 A string of long data A.
2 A string of long data B.
3 A string of long data C.
4 A string of long data D.
5 A string of long data E.
6 A string of long data F.
7 A string of long data G.
8 A string of long data H.
n Etc...
tags
id tag
------------
1 gold
2 silver
3 copper
4 emerald
5 steel
6 ruby
7 carbon
8 zinc
9 mercury
n Etc...
data_tag_rel
data tag
------------------
1 1
1 2
2 1
3 2
4 3
5 1
5 2
5 3
6 1
7 1
8 1
8 2
8 4
8 6
n n
As you can see, there is data and tags, and a relationship table to determine what tags are assigned to what data. Here the data is talking about minerals and rocks.
The query I want is to SELECT the tags (id and name) that are related to a set of more tags in the relationship table, by looking at what data id they target in common.
So for example, imagine I assign a data id 8 to be related to tags 1:"gold", 2:"silver", 4:"ruby" and 6:"emerald" in the relationship table. So now I would like to query common tags. If I query "gold", "silver", I would like to get returned either:
A. "gold", "silver", "ruby" and "emerald" (include the search tags).
or
B. "ruby" and "emerald" (don't include the search tags).
The purpose is to click a tag and see what other tags are related to that clicked tag,by what data they are related to in common, using the relationship table as a guide.
So far I managed to make it work searching for only 1 tag, but I can't make it work for 2, 3 or n tags.
SELECT DISTINCT tags.tag FROM tags, data_tag_rel WHERE tags.id = data_tag_rel.tag AND data_tag_rel.data IN (SELECT data_tag_rel.data FROM data_tag_rel WHERE data_tag_rel.tag IN (SELECT tags.id FROM tags WHERE tags.tag IN ('gold')));
How can I query related tags to a list of 2 or more tags in this database structure?
Thanks so much!
SELECT
data_tag_rel.data
FROM
data_tag_rel
WHERE
data_tag_rel.tag IN ((SELECT id FROM tags WHERE tags.tag IN ('silver', 'gold')))
GROUP BY
data_tag_rel.data
HAVING
COUNT(*) = 2
If it's possible for a data item to have the same tag multiple times, that changes slightly...
HAVING
COUNT(DISTINCT data_tag_rel.tag) = 2
The nasty nested IN() can also be replaced if you feel like it...
FROM
data_tag_rel
INNER JOIN
tags
ON tags.id = data_tag_rel.tag
WHERE
tags.tag IN ('silver', 'gold')
The crux of it is; filter normally, then use the HAVING clause to make sure you have two mathces, not just one.
EDIT:
To be explicit about how to get from there to the associated tags, just join back on to the data_tag_rel table...
SELECT DISTINCT
tags.tag
FROM
(
SELECT
data_tag_rel.data AS id
FROM
data_tag_rel
WHERE
data_tag_rel.tag IN ((SELECT id FROM tags WHERE tags.tag IN ('silver', 'gold')))
GROUP BY
data_tag_rel.data
HAVING
COUNT(*) = 2
)
AS data
INNER JOIN
data_tag_rel
ON data_tag_rel.data = data.id
INNER JOIN
tags
ON tags.id = data_tag_rel.tag
I think the logic you want is:
select t.*
from tags t
where exists (
select 1
from data_tag_rel dtr
inner join data_tag_rel dtr1 on dtr1.data = dtr.data
inner join tags t1 on t1.id = dtr1.tag
where t1.tag in ('gold', 'silver') and dtr.tag = t.id
)
I have these 3 tables: data, tags and data_tag_rel.
data
id data
------------------------------------
1 A string of long data A.
2 A string of long data B.
3 A string of long data C.
4 A string of long data D.
5 A string of long data E.
6 A string of long data F.
7 A string of long data G.
tags
id tag
------------
1 gold
2 silver
3 copper
data_tag_rel
data tag
------------------
1 1
1 2
2 1
3 2
4 3
5 1
5 2
5 3
6 1
7 1
As you can see, there is data and tags, and a relationship table to determine what tags are assigned to what data. Here the data is talking about metals. In this example:
The gold tag has been assigned to 5 data strings.
The silver tag has been assigned to 3 data strings.
The copper tag has been assigned to 2 data strings.
I want to query the database and obtain an INTERSECTION of tags gold, silver and copper. Meaning I want to obtain the table_data that is assigned to all 3 tags. The result would be just 1 row from the data table, row id 5: "A string of long data E."
What query would accomplish this INTERSECTION?
So far I can get the query working querying only 1 tag:
SELECT data.id, data.data
FROM data
INNER JOIN data_tag_rel ON data.id = data_tag_rel.data
INNER JOIN tags ON data_tag_rel.tag = tags.id
WHERE tags.tag = "gold"
Thanks so much!
Aggregation provides one option:
SELECT d.id, d.data
FROM data d
INNER JOIN data_tag_rel dtr ON d.id = dtr.data
INNER JOIN tags t ON dtr.tag = t.id
WHERE t.tag IN ('gold', 'silver', 'copper')
GROUP BY d.id, d.data
HAVING COUNT(DISTINCT t.tag) = 3;
Demo
Speed is going to vary based on your data. Tim's answer is likely to be fast enough for practical purposes, but if you find it is not, you may be able to slightly improve it by not joining data until needed (the other changes here are just stylistic and unlikely to have any effect):
select d.id, d.data
from (
select dtr.data as id
from data_tag_rel dtr
where dtr.tag in (select id from tag where tag in ('gold','silver','copper')
group by dtr.data
having count(tag) = 3
) d_ids
join data d using (id)
If you have a great deal of data, doing separate joins for each tag is likely to be faster, especially if you know which tags are rare and can go from rarest to least rare:
select d.id, d.data
from data_tag_rel dtr1 on dtr
join data_tag_rel dtr2 on dtr2.data=dtr1.data and dtr2.tag=(select id from tag where tag='silver')
join data_tag_rel dtr3 on dtr3.data=dtr2.data and dtr3.tag=(select id from tag where tag='copper')
join data d on d.id=dtr3.data
where dtr1.tag=(select id from tag where tag='gold')
(both queries untested)
I have a table "Products" with a product name and id:
id | title
1 product 1
2 product 2
Each product can have a series of tags. Tags are identified in table "Attributes":
id | name | handle
1 Tag One tag-one
2 Tag Two tag-two
3 Tag Three tag-three
4 Tag Four tag-four
etc
The product to tag relationship is another table "Tags":
id | AttId | OwnerId
1 1 1
2 2 1
3 1 2
4 3 2
etc
Ok, so I am trying to select a set of products that all have at least one specific tag, and a possible selection of other tags. Here is what I am working with now:
SELECT products.id
FROM products
WHERE
EXISTS
(
SELECT 1
FROM Tags
INNER JOIN Attributes ON tags.AttId = Attributes.id
WHERE Attributes.handle = 'tag-one'
AND (
Attributes.handle = 'tag-two'
OR
Attributes.handle = 'tag-four'
)
AND products.id = Tags.OwnerId
)
If I remove the AND (OR) section, the query works. As above, it shows no errors, but also no results; How should I write this so I can get a set of products that have one tag for sure, AND have either/or other specified tags by the tag handle?
I like to approach this type of problem using group by and having -- because I find that this method works very well for expressing many different conditions. For your conditions:
select p.*
from products p join
tags t
on t.OwnerId = p.id join
attributes a
on t.attid = a.id
group by p.id
having sum(a.handle = 'tag-one') > 0 and
sum(a.handle in ('tag-two', 'tag-four')) > 0;
Each condition in the having clause counts the number of rows (for a product) that match a condition. The first says there is at least one row with 'tag-one' handle. The second says that there is at least one row with the other two handles.
I think if you perform two separate queries and take the intersection, that will give you what you want.
-- Get all the owner ids that have 'tag-one'
select OwnerId
from Tags t1
where AttId in
(
select id
from Attributes a1
where a1.handle = 'tag-one'
)
intersect
-- Get all the owner ids that have tag-two and tag-four
select OwnerId
from Tags t2
where AttId in
(
select id
from Attributes a2
where a2.handle in ('tag-two', 'tag-four')
)
;
We have a table with around 40000 id's in - some of these IDs are parents of other IDs (and subsequently some of those are parents of others in a different table). I'd like to use magic, persistance and some joins to work out which categories are related by querying against a field that contains all the child IDs (known as arrange).
So the way this works is Category table:
id name arrange
16 Alarms c,119|c,117|c,4607|c,3366|c,709|c,4204|c,624|c,626|c,625|c,4203|c,4201|c,4202
119 Carbon Monoxide i,21434|i,272|i,274|i,28451
Products table then has the i, items from arrange in
id name
272 Aico EI205ENA
274 AICO EI225EN
Basically I am running a query against a third orders table, and would like to create a table using joins which would be as such:
order date id name id name id name quantity price
13-06-2013 16 Alarms 119 Carbon Monoxide 272 Aico EI205ENA 2 10.00
At the moment I have:
select * from cart c
join prods p on p.id = c.item
where order_status = ''
and date_ordered != '0000-00-00'
order by date_added desc;
Simple join where I now want to add the categories from the first example, how on earth do I query an array to get what I want?
(If it helps we have 3 tables I am interested in cart, product and category).
Thanks in advance!
I have now realised how the tables join together.
What you want to do is possible with something like the following. It will be slow and the main reason I am putting it here is to show how nasty and unreadable it is, as an encouragement to normalise the database.
SELECT *
FROM cart c
INNER JOIN prods p
ON p.id = c.item
INNER JOIN Category z1
ON FIND_IN_SET(CONCAT('i-', p.id), REPLACE(REPLACE(z1.arrange, 'i,', 'i-'), '|', ',')) > 0
INNER JOIN Category z2
ON FIND_IN_SET(CONCAT('c-', z1.id), REPLACE(REPLACE(z2.arrange, 'c,', 'c-'), '|', ',')) > 0
WHERE order_status = ''
AND date_ordered != '0000-00-00'
ORDER BY date_added DESC
FIND_IN_SET is a function that looks for a value in a comma separated list. This is taking your delimited list, changing all the occurrences of i, and changing them to i- then changing all the occurrences of | and changing them to commas. Then it looks for i- concatenated with the id in the resulting comma separated list. Then does the same joining against category again to get the match for the parent category.