mysql query - conditional statements? - mysql

Here are my tables:
files
+----+--------+
| id | name |
+----+--------+
| 2 | file_1 |
| 3 | file_2 |
| 5 | file_3 |
+----+--------+
files_already_viewed
+----+---------+----------+------+
| id | file_id | category | user |
+----+---------+----------+------+
| 1 | 3 | 5 | 1 |
| 2 | 2 | 1 | 1 |
| 3 | 5 | 1 | 1 |
+----+---------+----------+------+
categories_files_join
+--------+---------+
| cat_id | file_id |
+--------+---------+
| 1 | 2 |
| 1 | 5 |
| 5 | 3 |
| 1 | 3 |
+--------+---------+
file_2 (which has an id of 3) has two categories associated with it, cat_id 5 and cat_id 1.
It has been viewed once by a user searching for files under the category 5.
But now the user is searching for files under the category 1.
I need a query that won't show file_2 under the "1" category until all the other files with a category id of 1 have been viewed first, since the user already viewed file_2. Basically putting file_2 at the end of the list.
Here is my query so far:
SELECT name FROM files
WHERE files.id NOT IN (
SELECT file_id FROM files_already_viewed
WHERE user='1')
ORDER BY most_viewed DESC
LIMIT 1
I order my search by the most popular viewed file. But i don't want to show files that have already been viewed regardless of category until all other files have been viewed with in that specific category.
Any help would be greatly appreciated. thanks!

Actually, your query will not show files already shown. What you want to do is to order those already shown files at the bottom. So, basically you'll have to sets of data: first, the data that matches the needed criteria and that the user has not been shown yet and then the data that matches the needed criteria but the user has been shown.
The way I'm handling the sets is by adding a customSort id for each set and then ordering by it. Now the short explanation is that the first group I get it by faking a MINUS operation with a left join: I get all the files that have not yet been seen. Then, the second group is a bit easier as it just needs to get all the files that have already been seen. So, the UNION of both sets in the customSort order would be the result you're looking for so now you just need to filter that result by the current query criteria (category = 1 in this case).
select file_id from (
select distinct cf1.cat_id, cf1.file_id, 1 as customSort
from CategoriesFiles1 cf1
left join FilesViewed1 fv1 on (fv1.file_id = cf1.file_id)
where (fv1.file_id is null)
union
select distinct cf2.cat_id, cf2.file_id, 2 as customSort
from CategoriesFiles2 cf2
join FilesViewed2 fv2 on (fv2.file_id = cf2.file_id)
) FinalResult
where (FinalResult.cat_id = 1)
order by customSort
Here is a link with the example. You can comment each data insert in files_already_viewed to see how, after viewing a file, the result changes. Besides, changing select file_id from to select * from will allow you to clearly see which set each row belongs to.
Let me know if this works.

Related

SQL: many-to-many relationship and the 'ALL' clause

I have a table products and a table locations which are linked together in a many-to-many relationship with a table products_locations. Now a client can select a set of products, and I want to run a query that selects only the locations, where ALL of the selected products are available.
This seemed pretty straight forward at first, but I see myself being quite baffled by how to achieve this. I initially thought I could get all the correct location-ids with something like
SELECT location_id
FROM products_locations
WHERE product_id = ALL [the user selected product ids]
But on second thought that does not appear to make sense either (the structure of products_locations is quite simply [product_id, location_id].
Any suggestion on how to structure such a query would be appreciated. I feel like I am overlooking something basic..
EDIT: I am using mysql syntax/dialect
Quick sample: Given the following tables
| products | | locations | | products_locations |
| id | name | | id | name | | product_id | location_id |
|------------| |-----------| |--------------------------|
| 1 | prod1 | | 1 | locA | | 1 | 2 |
| 2 | prod2 | | 2 | locB | | 2 | 1 |
| 3 | prod3 | |-----------| | 2 | 2 |
|------------| | 3 | 1 |
|--------------------------|
If a user selects products 1 and 2, the query should return only location 2. If the user selects products 2 and 3, the query should return location 1. For 1, 2, and 3, no location would be valid, and for product 2, both locations would be valid.
I figured out a query that achieves what I need. Though it is not as clean as I had hoped, it seems to be a robust approach to what I'm trying to query:
SELECT t.location_id
FROM (SELECT location_id, COUNT(*) as n_hits
FROM products_locations
WHERE product_id IN [the user selected products]
GROUP BY location_id) t
WHERE n_hits = [the number of user selected products];
Explanation:
I create a temporary table t which contains every location_id that has at least one matching product in the user's selection, together with the number of times that location matches a product in the user's selection. This is achieved by grouping the query by location_id.
I select the location_id(s) from that temporary table t, where the number of hits is equal to the number of products the user had selected. If that number is lower, I know that at least one product did not match that location.

Returns distinct record in a joins query - Rails 4

I'm trying to get and display an order list including the current status.
#orders = Order.joins(order_status_details: :order_status)
.order('id DESC, order_status_details.created_at DESC')
.select("orders.id, order_status_details.status_id, order_statuses.name, order_status_details.created_at")
It works good but is returning all the rows with order ids duplicated like this:
+----+-----------+----------------------+---------------------+
| id | status_id | name | created_at |
+----+-----------+----------------------+---------------------+
| 8 | 1 | Pending | 2016-01-31 16:33:30 |
| 7 | 3 | Shipped | 2016-02-01 05:01:21 |
| 7 | 2 | Pending for shipping | 2016-01-31 05:01:21 |
| 7 | 1 | Pending | 2016-01-31 04:01:21 |
+----+-----------+----------------------+---------------------+
The correct answer must return uniques ids, for the example above should be the first and second row.
I was already trying with distinct on select, .distinct, .uniq and .group but I'm getting an error.
Thanks.
First of all, I believe your model is "An Order has many OrderStatusDetail". So that is the reason why you have several different name in your result.
So you can modify the query like this:
#orders = Order.joins(order_status_details: :order_status)
.order('id DESC, order_status_details.created_at DESC')
.where('order_status_details.id IN (SELECT MAX(id) FROM order_status_details GROUP BY order_id)')
.select("orders.id, order_status_details.status_id, order_statuses.name, order_status_details.created_at")
Ideally, the where condition is used for selecting just the expected id of order_status_details, I use min_id for example, you can modify it as needed

query to get most matched likes first in mysql

I have table like:
user :
uid | course_id | subjects
---------------------------
1 | 1 | html,php
2 | 1 | java,html,sql
3 | 1 | java
4 | 1 | fashion,html,php,sql,java
I want to run a query which can return most liked subjects in query and then second most and so on...
For Example :
select * from user where subjects like '%java%' or '%php%' or '%html%';
this query will return data like this:
uid | course_id | subjects
---------------------------
2 | 1 | java,html,sql
3 | 1 | java
4 | 1 | fashion,html,php,sql,java
but i want output like this :
uid | course_id | subjects
---------------------------
4 | 1 | fashion,html,php,sql,java
2 | 1 | java,html,sql
1 | 1 | html,php
3 | 1 | java
so the most matched subjects 1st then 2nd most matched subjects and so on....
Is there any modification in my query so that i can get this type of sorted output.
Never, never, never store multiple values in one column!
Like you see now this will only give you headaches. Normalize your user table. Then you can select normally.
It should look like this
uid | course_id | subjects
---------------------------
1 | 1 | html
1 | 1 | php
2 | 1 | java
2 | 1 | html
2 | 1 | sql
3 | 1 | java
...
or better introduce an new table subjects and then make a mapping table called course_subjects
subject
id | name
------------
1 | html
2 | sql
3 | java
...
course_subjects
uid | course_id | subject_id
---------------------------
1 | 1 | 1
1 | 1 | 2
...
Based on the way you want your results, it looks like you want to order by the number of subjects (or tags) within subject. This can be accomplished by counting the number of , (commas).
The way to count the number of occurances of a character is to subtract the original length by the length when the character is removed.
Example:
SELECT *
FROM USER
WHERE subjects LIKE '%java%'
OR '%php%'
OR '%html%'
ORDER BY ( Length(subjects) - Length(Replace(subjects, ',', '')) ) DESC;
SQLFiddle: http://sqlfiddle.com/#!2/cc793/4
Result:
UID COURSE_ID SUBJECTS
4 1 fashion,html,php,sql,java
2 1 java,html,sql
3 1 java
Note:
As juergen says it is a bad idea to store multiple values in one column.
With MyISAM storage engine you can do match against.
The simplest example:
SELECT *,
MATCH (subjects) AGAINST ('java php html') AS relevance
FROM `user`
WHERE MATCH (subjects) AGAINST ('java php html')
ORDER BY relevance DESC
In MySQL 5.6 full-text search is available with InnoDB too but needs a bit extra to make it work. For more info checkout the following post: http://www.mysqlperformanceblog.com/2013/03/04/innodb-full-text-search-in-mysql-5-6-part-2-the-queries/

SELECT from Union x 3 using filter of another table

Background
I have a web application which must remove entries from other tables, filtered through a selection of 'tielists' from table 1 -> item_table 1, table 2, table 3.... now basically my result set is going to be filthy big unless I use a filter statement from another table, using a user_id... so can someone please help me structure my statement as needed? TY!
Tables
cars_belonging_to_user
-----------------------------
ID | user_id | make | model
----------------------------
1 | 1 | Toyota | Camry
2 | 1 |Infinity| Q55
3 | 1 | DMC | DeLorean
4 | 2 | Acura | RSX
Okay, Now the three 'tielists'
name:tielist_one
----------------------------
id | id_of_car | id_x | id_y|
1 | 1 | 12 | 22 |
2 | 2 | 23 | 32 |
-----------------------------
name:tielist_two
-------------------------------
id | id_of_car | id_x | id_z|
1 | 3 | 32 | 22 |
-----------------------------
name: tielist_three
id | id_of_car | id_x | id_a|
1 | 4 | 45 | 2 |
------------------------------
Result Set and Code
echo name_of_tielist_table
// I can structure if statements to echo result sets based upon the name
// Future Methodology: if car_id is in tielist_one, delete id_x from x_table, delete id_y from y_table...
// My output should be a double select base:
--SELECT * tielists from WHERE car_id is 1... output name of tielist... then
--SELECT * from specific_tielist where car_id is 1.....delete x_table, delete y_table...
Considering the list will be massive, and the tielist equally long, I must filter the results where car_id(id) = $variable && user_id = $id....
Side Notes
Only one car id will appear once in any single tielist..
This select statement MUST be filtered with user_id = $variable... (and remember, i'm looking for which car id too)
I MUST HAVE THE NAME of the tielist it comes from able to be echo'd into a variable...
I will only be looking for one single id_of_car at any given time, because this select will be contained in a foreach loop.
I was thinking a union all items would do the trick to select the row, but how can I get the name of the tielist the row is in, and how can the filter be used from the user_id row
If you want performance, I would suggest left outer join instead of union all. This will allow the query to make efficient use of indexes for your purpose.
Based on what you say, a car is in exactly one of the lists. This is important for this method to work. Here is the SQL:
select cu.*,
coalesce(tl1.id_x, tl2.id_x, tl3.id_x) as id_x,
tl1.y, tl2.idz, tl3.id_a,
(case when tl1.id is not null then 'One'
when tl2.id is not null then 'Two'
when tl3.id is not null then 'Three'
end) as TieList
from Cars_Belonging_To_User cu left ouer join
TieList_One tl1
on cu.id_of_car = tl1.id_of_car left outer join
TieList_Two tl2
on cu.id_of_car = tl2.id_of_car left outer join
TieList_Three tl3
on cu.id_of_car = tl3.id_of_car;
You can then add a where clause to filter as you need.
If you have an index on id_of_car for each tielist table, then the performance should be quite good. If the where clause uses an index on the first table, then the joins and where should all be using indexes, and the query will be quite fast.

Mysql query Max not working

What i want to happen is group by parentid first, then group by position, which i have done. In that group i want the name with the highest rating to be displayed, which isn't happening. Instead the lowest id for each group is being displayed. The results should be tv1,tv3,tv5,tv7; as these are the highest rated values for each group.
id | name| parentid| position| rating |
1 | tv1 | 1 | 1 | 6 |
2 | tv2 | 1 | 2 | 5 |
3 | tv3 | 1 | 2 | 7 |
4 | tv4 | 1 | 2 | 3 |
5 | tv5 | 5 | 1 | 8 |
6 | tv6 | 5 | 1 | 2 |
7 | tv7 | 3 | 1 | 9 |
8 | tv8 | 3 | 1 | 3 |
$getquery = mysql_query("SELECT name,MAX(rating) FROM outcomes GROUP BY position,parentid") or die(mysql_error());
while($row=mysql_fetch_assoc($getquery)) {
$name = $row['name'];
$rating = $row['rating'];
echo "<p>Name: $name - $rating</p><p></p>";
}
It's not that the lowest id is being displayed -- you're not actually selecting the id column. Probably what you are seeing is the first entry in the name column for each group.
SELECT name, MAX(rating)
doesn't do what you think it does -- it doesn't instruct MySQL to pick the maximum value from the rating column, and also return the name that is associated with that row (aside: what do you think it would return if there was a tie for the maximum rating? What do you think it would return if you used AVERAGE rather than MAX?)
What it does instead is return the correctly calculated MAX(rating), and then one of the names out of that group. It doesn't guarantee which one gets returned, and it can change depending on how it decides to execute the query.
In fact, because of the undefined nature of a query such as this, it's not even legal SQL in other databases. (Try this in Postgres, and you'll get an error. Heck, try it in MySQL with the ONLY_FULL_GROUP_BY option enabled, and you'll get a similar error)
If what you want to do is find the maximum rating for each group, and then find the name associated with it, you'll have to do something like this:
SELECT name, max_rating FROM outcomes
JOIN (SELECT position, parentid, MAX(rating) AS max_rating from outcomes group by position, parentid) AS aggregated_table
USING (position, parentid)
WHERE rating = max_rating
(There are four or five other ways to do this, searching this site for mysql and aggregation will likely turn them up)