Select query in multi primary key table - mysql

I'm making a database with fitness exercises and their equipment needed.
My database is designed like this
+-------+-----------------+
| id(pk)| equip(pk) |
+-------+-----------------+
| 1 | Barbell |
| 1 | Bench |
| 2 | Dumbbell |
| 2 | Bench |
| 3 | Barbell |
| 4 | Dumbbell |
| ... | ..(many rows).. |
+-------+-----------------+
The id stands for a certain exercise and the equip is needed to select that exercise
So for exercise 1 (id = 1) you need a Barbell and Bench.
But for exercise 3 (id = 3) you only need a Barbell
So if the user want exercises containing Barbell and Bench, id 1 and 3 should be selected
Current Query
SELECT * FROM( SELECT id, GROUP_CONCAT(equip SEPARATOR ', ') equip
FROM equip group by id ) as x
This gives the following result
+-------+-----------------+
| id(pk)| equip(pk) |
+-------+-----------------+
| 1 | Barbell, Bench |
| 2 | Dumbbell, Bench |
| 3 | Barbell |
| 4 | Dumbbell |
| ... | ..(many rows).. |
+-------+-----------------+
So if i want to search for Barbell and Bench, 1 and 3 should be selected
Thank you very much :)

I think rather than explaining your expected outcome, just explain the business rules you're trying to implement and give us some insight into the environment you're working in.
Also, what do you mean by "barbell and bench are true"? Varchar fields cannot be true or false.
For instance, your last line talks about weights and support, which are not included in your data set and would probably help in answering the question. Because I don't have rep to comment, I had to create an answer, so here is my best shot without more information:
select * from (
SELECT id, GROUP_CONCAT(equip SEPARATOR ', ') concatenatedEquip
FROM table GROUP BY id )
where (concatenatedEquip contains('Barbell') or concatenatedEquip
contains('Bench'))
so this query would

Related

Mysql - Compare int field with comma separated field from another table

I have two tables in a MySQL database like this:
User:
userid |userid | Username | Plan(VARCHAR) | Status |
-----------+------------+--------------+---------------+---------+
1 | 1 | John | 1,2,3 |1 |
2 | 2 | Cynthia | 1,2 |1 |
3 | 3 | Charles | 2,3,4 |1 |
Plan: (planid is primary key)
planid(INT) | Plan_Name | Cost | status |
-------------+----------------+----------+--------------+
1 | Tamil Pack | 100 | ACTIVE |
2 | English Pack | 100 | ACTIVE |
3 | SportsPack | 100 | ACTIVE |
4 | KidsPack | 100 | ACTIVE |
OUTPUT
id |userid | Username | Plan | Planname |
---+-------+----------+------------+-------------------------------------+
1 | 1 | John | 1,2,3 |Tamil Pack,English Pack,SportsPack |
2 | 2 | Cynthia | 1,2 |Tamil Pack,English Pack |
3 | 3 | Charles | 2,3,4 |English Pack,Sportspack, Kidspack |
Since plan id in Plan table is integer and the user can hold many plans, its stored as comma separated as varchar, so when i try with IN condition its not working.
SELECT * FROM plan WHERE find_in_set(plan_id,(select user.planid from user where user.userid=1))
This get me the 3 rows from plan table but i want the desired output as above.
How to do that.? any help Please
A rewrite off your query what should work is as follows..
Query
SELECT
all columns you need
, GROUP_CONCAT(Plan.Plan_Name ORDER BY Plan.planid) AS Planname
FROM
Plan
WHERE
FIND_IN_SET(Plan.plan_id,(
SELECT
User.Plan
FROM
user
WHERE User.userid = 1
)
)
GROUP BY
all columns what are in the select (NOT the GROUP_CONCAT function)
You also can use FIND_IN_SET on the ON clause off a INNER JOIN.
One problem is that the join won't ever use indexes.
Query
SELECT
all columns you need
, GROUP_CONCAT(Plan.Plan_Name ORDER BY Plan.planid) AS Planname
FROM
User
INNER JOIN
Plan
ON
FIND_IN_SET(Plan.id, User.Plan)
WHERE
User.id = 1
GROUP BY
all columns what are in the select (NOT the GROUP_CONCAT function)
Like i said in the comments you should normalize the table structures and add the table User_Plan whats holds the relations between the table User and Plan.

Optimize SQL-Query that is using REGEXP in a JOIN

I have the following situation:
Table Words:
| ID | WORD |
|----|--------|
| 1 | us |
| 2 | to |
| 3 | belong |
| 4 | are |
| 5 | base |
| 6 | your |
| 7 | all |
| 8 | is |
| 9 | yours |
Table Sentence:
| ID | SENTENCE |
|----|-------------------------------------------|
| 1 | <<7>> <<6>> <<5>> <<4>> <<3>> <<2>> <<1>> |
| 2 | <<7>> <<8>> <<9>> |
And i want to replace the <<(\d)>> with the equivalent word from the Word-Table.
So the result should be
| ID | SENTENCE |
|----|--------------------------------|
| 1 | all your base are belong to us |
| 2 | all is yours |
What i came up with is the following SQL-Code:
SELECT id, GROUP_CONCAT(word ORDER BY pos SEPARATOR ' ') AS sentence FROM (
SELECT sentence.id, words.word, LOCATE(words.id, sentence.sentence) AS pos
FROM sentence
LEFT JOIN words
ON (sentence.sentence REGEXP CONCAT('<<',words.id,'>>'))
) AS TEMP
GROUP BY id
I made a sqlfiddle for this:
http://sqlfiddle.com/#!2/634b8/4
The code basically is working, but i'd like to ask you pros if there is a way without a derived table or without filesort in the execution plan.
You should make a table with one entry per word, so your sentense (sic) can be made by joining on that table. It would look something like this
SentenceId, wordId, location
2, 7, 1
2, 8, 2
2, 9, 3
They way you have it set up, you are not taking advantage of your database, basically putting several points of data in 1 table-field.
The location field (it is tempting to call it "order", but as this is an SQL keyword, don't do it, you'll hate yourself) can be used to 'sort' the sentence.
(and you might want to rename sentense to sentence?)

MATCH relevance for records spanning multiple rows (cannot use GROUP_CONCAT with MATCH together)

I'm having MySQL problems and I'm in need of a beer lunch. I wanted to do a query like this:
SELECT MATCH(some_string) AGAINST ('beer lunch') FROM (SELECT GROUP_CONCAT(some_column) AS some_string FROM myrealtable) AS mytablealias;
Unfortunately I discovered I cannot do a FULLTEXT MATCH against a GROUP_CONCAT column since the FULLTEXT index exists for the original column (some_column) only and not in the concatenated column (some_string) in the alias table.
I really need to do a FULLTEXT search and generate a relevance score for concatenated strings that are broken across multiple rows in my table.
Here's a little thought experiment that I put together to study the relevance problem. Let's start with a table that has the concatenated strings:
+----------+-------------------------------------------------------------------------------------+
| table_id | concat_string |
+----------+-------------------------------------------------------------------------------------+
| 1 | I like beer Beer is a healthy choice My brother drinks beer for lunch every day |
| 2 | I like juice Juice is a healthier choice My brother drinks beer for lunch every day |
+----------+-------------------------------------------------------------------------------------+
Now I do the following MATCH query on this table: SELECT table_id,MATCH(concat_string) AGAINST('beer lunch') AS score FROM myconcattable; and I get the following relevance scores:
+----------+----------------------------+
| table_id | score |
+----------+----------------------------+
| 1 | 0.000000007543713209656744 |
| 2 | 0.000000003771856604828372 |
+----------+----------------------------+
Clearly the first row is far more relevant than the second when searching for "beer lunch" ... but the problem is that my strings are broken across multiple rows that need to be grouped according to a foreign key (foreign_id). Here's what my table really looks like:
+----------+--------------------------------------------+------------+
| table_id | some_string | foreign_id |
+----------+--------------------------------------------+------------+
| 1 | I like beer | 1 |
| 2 | Beer is a healthy choice | 1 |
| 3 | My brother drinks beer for lunch every day | 1 |
| 4 | I like juice | 2 |
| 5 | Juice is a healthier choice | 2 |
| 6 | My brother drinks beer for lunch every day | 2 |
+----------+--------------------------------------------+------------+
So now let's try the query (SELECT table_id,MATCH(some_string) AGAINST('beer lunch') AS score, foreign_id FROM mybrokentable;) on this table:
+----------+----------------------+------------+
| table_id | score | foreign_id |
+----------+----------------------+------------+
| 1 | 0.031008131802082062 | 1 |
| 2 | 0.031008131802082062 | 1 |
| 3 | 0.25865283608436584 | 1 |
| 4 | 0 | 2 |
| 5 | 0 | 2 |
| 6 | 0.25865283608436584 | 2 |
+----------+----------------------+------------+
OK, so if I add up the scores as a sum, then foreign_id 1 looks a bit more relevant than foreign_id 2, but it is not very accurate compared to when the strings were concatenated into one table.
Ideally, I'd like to devise a query that would generate a relevance score for the foreign ids like this:
+----------------------------+------------+
| score | foreign_id |
+----------------------------+------------+
| 0.000000007543713209656744 | 1 |
| 0.000000003771856604828372 | 2 |
+----------------------------+------------+
Any ideas of what I should do?
You could try summing the scores in your individual phrase matches, like so, with a subquery. You'll have to experiment to see if it gives you the result you need.
SELECT SUM(score) AS total_score,
foreign_id
FROM (
SELECT table_id,
MATCH(some_string) AGAINST('beer lunch') AS score,
foreign_id
FROM phrase
) AS scores
GROUP BY foreign_id
ORDER BY total_score DESC
I used BOOLEAN MODE in this fiddle http://sqlfiddle.com/#!2/355b1/1/0 because ordinary fulltext search does odd things with stopwords on small samples of text.

SQL 'COUNT' not returning what I expect, and somehow limiting results to one row

Some background: an 'image' is part of one 'photoshoot', and may be a part of zero or many 'galleries'. My tables:
'shoots' table:
+----+--------------+
| id | name |
+----+--------------+
| 1 | Test shoot |
| 2 | Another test |
| 3 | Final test |
+----+--------------+
'images' table:
+----+-------------------+------------------+
| id | original_filename | storage_location |
+----+-------------------+------------------+
| 1 | test.jpg | store/test.jpg |
| 2 | test.jpg | store/test.jpg |
| 3 | test.jpg | store/test.jpg |
+----+-------------------+------------------+
'shoot_images' table:
+----------+----------+
| shoot_id | image_id |
+----------+----------+
| 1 | 1 |
| 1 | 2 |
| 3 | 3 |
+----------+----------+
'gallery_images' table:
+------------+----------+
| gallery_id | image_id |
+------------+----------+
| 1 | 1 |
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
| 4 | 1 |
+------------+----------+
What I'd like to get back, so I can say 'For this photoshoot, there are X images in total, and these images are featured in Y galleries:
+----+--------------+-------------+---------------+
| id | name | image_count | gallery_count |
+----+--------------+-------------+---------------+
| 3 | Final test | 1 | 1 |
| 2 | Another test | 0 | 0 |
| 1 | Test shoot | 2 | 4 |
+----+--------------+-------------+---------------+
I'm currently trying the SQL below, which appears to work correctly but only ever returns one row. I can't work out why this is happening. Curiously, the below also returns a row even when 'shoots' is empty.
SELECT shoots.id,
shoots.name,
COUNT(DISTINCT shoot_images.image_id) AS image_count,
COUNT(DISTINCT gallery_images.gallery_id) AS gallery_count
FROM shoots
LEFT JOIN shoot_images ON shoots.id=shoot_images.shoot_id
LEFT JOIN gallery_images ON shoot_images.image_id=gallery_images.image_id
ORDER BY shoots.id DESC
Thanks for taking the time to look at this :)
You are missing the GROUP BY clause:
SELECT
shoots.id,
shoots.name,
COUNT(DISTINCT shoot_images.image_id) AS image_count,
COUNT(DISTINCT gallery_images.gallery_id) AS gallery_count
FROM shoots
LEFT JOIN shoot_images ON shoots.id=shoot_images.shoot_id
LEFT JOIN gallery_images ON shoot_images.image_id=gallery_images.image_id
GROUP BY 1, 2 -- Added this line
ORDER BY shoots.id DESC
Note: The SQL standard allows GROUP BY to be given either column names or column numbers, so GROUP BY 1, 2 is equivalent to GROUP BY shoots.id, shoots.name in this case. There are many who consider this "bad coding practice" and advocate always using the column names, but I find it makes the code a lot more readable and maintainable and I've been writing SQL since before many users on this site were born, and it's never cause me a problem using this syntax.
FYI, the reason you were getting one row before, and not getting and error, is that in mysql, unlike any other database I know, you are allowed to omit the group by clause when using aggregating functions. In such cases, instead of throwing a syntax exception, mysql returns the first row for each unique combination of non-aggregate columns.
Although at first this may seem abhorrent to SQL purists, it can be incredibly handy!
You should look into the MySQL function group by.

Joining from another table multiple times in a MySQL query

I am trying to do multiple joins on the same MySQL table, but am not getting the results that I expect to get. Hopefully someone can point out my mistake(s).
Table 1 - cpe Table
|id | name
|----------
| 1 | cat
| 2 | dog
| 3 | mouse
| 4 | snake
-----------
Table 2 - AutoSelect
|id | name | cpe1_id | cpe2_id | cpe3_id |
|-----------------------------------------------
| 1 | user1 | 1 | 3 | 4 |
| 2 | user2 | 3 | 1 | 2 |
| 3 | user3 | 3 | 3 | 2 |
| 4 | user4 | 4 | 2 | 1 |
------------------------------------------------
I would like to see an output of
user1 | cat | mouse | snake |
user2 | mouse | snake | dog |
..etc
Here is what I have tried
SELECT * FROM AutoSelect
LEFT JOIN cpe ON
( cpe.id = AutoSelect.cpe1_id ) AND
( cpe.id = AutoSelect.cpe2_id ) AND
( cpe.id = AutoSelect.cpe3_id )
I get blank results. I thought i knew how to do these joins, but apparently when I'm trying to match cpe?_id with the name of the cpe table.
Thanks in advance for any assistance.
You need left join 3 times as well. Currently your query only joins 1 time with 3 critieria as to the join. This should do:
SELECT a.name, cpe1.name, cpe2.name, cpe3.name FROM AutoSelect as a
LEFT JOIN cpe as cpe1 ON ( cpe1.id = a.cpe1_id )
LEFT JOIN cpe as cpe2 ON ( cpe2.id = a.cpe2_id )
LEFT JOIN cpe as cpe3 ON ( cpe3.id = a.cpe3_id )
And you probably mean to INNER JOIN rather than LEFT JOIN unless NULL values are allowed in your AutoSelect table.
I think your design is wrong.
With tables like that, you get it the way it's meant to be in relational databases :
table 1 : animal
id name
1 cat
2 dog
3 mouse
4 snake
table 2 : user
|id | name |
|--------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
| 4 | user4 |
table 3 : association
|id_user | id_animal|
|--------------------
| 1 | 1 |
| 1 | 3 |
| 1 | 4 |
| 2 | 3 |
| 2 | 1 |
| 2 | 2 |
| 3 | 3 |
| 3 | 2
| 4 | 4 |
| 4 | 2 |
| 4 | 1 |
---------------------
Then :
select u.name, a.name from user u, animal a, association ass where ass.user_id = u.id and ass.animal_id = a.id;
In this case, your solution won't produce a good dynamic database. There are other ways to make combinations of multiple tables. I can show you by my own database what you should use and when you should use this solution. The scheme is in dutch, but you'll probably understand the keywords.
Like you, I had to combine my windmills with a kWh-meter, which has to measure the energyproduction of my windmills. What you should do, is this case, is making another table(in my case molenkWhlink). Make sure your tables are INNODB-types(for making Foreign keys). What I've done is combining my meters and mills by putting a pointer(a foreign key) of their ID(in Dutch Volgnummer) in the new table. An advantage you may not need, but I certainly did, is the fact I was able to extend the extra table with connection and disconnection info like Timestamps and metervalues when linking or unlinking. This makes your database way more dynamic.
In my case, I Also had a table for meassurements(metingoverzicht). As you can see in the scheme, I've got 2 lines going from Metingoverzicht to molenkwhlink. The reason for this is quite simple. All meassurements I take, will be saved in table Metingoverzicht. Daily meassurements(which are scheduled) will have a special boolean put on, but unscheduled meassurements, will also me saved here, with the bollean turned off. When switching meters, I need the endvalue from the leaving meter and the startvalue from the new meter, to calculate the value of todays eneryproduction. This is where your solution comes in and an extra table won't work. Usually, when you need just one value from another table a JOIN will be used. The problem in this case is, I've got 2 meassurementIDs in 1 link(1 for connecting and 1 for disconnecting). They both point to the same tablecolumn, because they both need to hold the same type of information. That is when you can use a double JOIN from one table towards the other. Because, both values will only be used once, and just needed to be saved in a different place to avoid having 1 action stored on different locations, which should always be avoided.
http://s1101.photobucket.com/user/Manuel_Barcelona/media/schemedatabase.jpg.html