MySQL Order results by field uniquness - mysql

I have a website listing products from various vendors (hundreds of different vendors). When you search for a specific product I return a bunch of products which can be sorted by price, etc.
I want the default sort that I show the user to showcase as many unique vendors as possible, without using groupby and removing multiple products from the same user.
So if a given search query returns 10 products from vendor a, 5 products from vendor b, 5 products from vendor C, etc -- the default sort would be to show one from each of them (in any order, really), and then the rest of the results.
EDIT: example dataset:
+----+-----------+--------------+
| id | vendor_id | product_name |
+----+-----------+--------------+
| 1 | 1 | Product 1 |
| 2 | 1 | Product 2 |
| 3 | 1 | Product 3 |
| 4 | 2 | Product 4 |
| 5 | 2 | Product 5 |
| 6 | 2 | Product 6 |
| 7 | 3 | Product 7 |
| 8 | 3 | Product 8 |
| 9 | 3 | Product 9 |
+----+-----------+--------------+
Desired Results:
+----+-----------+--------------+
| id | vendor_id | product_name |
+----+-----------+--------------+
| 1 | 1 | Product 1 |
| 4 | 2 | Product 4 |
| 7 | 3 | Product 7 |
| 2 | 2 | Product 2 | * any sort after initial unique results*
| 3 | 2 | Product 3 |
+----+-----------+--------------+
Really, ANY sort after the initial sort order.

You can do this by enumerating the results for each vendor and then sorting on that.
select s.*
from (select s.*,
(#rn := if(#v = vendor, #rn + 1,
if(#v := vendor, 1, 1)
)
) as seqnum
from (<your query here>) s cross join
(select #v := '', #rn := 0) params
order by vendor
) s
order by (seqnum = 1) desc,
price asc; -- or whatever you want to sort by

Related

Mysql fetch rows limited be another table column value

I have list of products and seller who added this products
Each Seller has a limit to show the products.
I need to write a query to list all the products with pagination 100 and show only seller products limited to that seller
`Seller Table`
| seller id| Seller Name | limit |
|:-------- |:-----------:| ------:|
| 1 | Seller One | 1 |
| 2 | Seller Two | 3 |
| 3 | Seller Three| 2 |
`Products Table
| product id| seller id | Product Name |
|:-------- |:----------:| ------------:|
| 1 | 1 | Product 1 |
| 2 | 2 | Product 2 |
| 3 | 1 | Product 3 |
| 4 | 1 | Product 4 |
| 5 | 2 | Product 5 |
| 6 | 2 | Product 6 |
| 7 | 2 | Product 7 |
| 8 | 3 | Product 8 |
| 9 | 3 | Product 9 |
| 9 | 3 | Product 10 |
| 9 | 3 | Product 11 |
(Output or Result I am expecting)
| product id| seller id | Product Name |
|:-------- |:----------:| ------------:|
| 1 | 1 | Product 1 |
| 2 | 2 | Product 2 |
| 5 | 2 | Product 5 |
| 6 | 2 | Product 6 |
| 8 | 3 | Product 8 |
| 9 | 3 | Product 9 |
Seller 1 = Product 1
Seller 2 = Product 2 , Product 5 and Product 6
Seller 3 = Product 8 and Product 9
I want to get this products
How do I write a query?
and also is it possible to selected random products of the seller
I have query only to fetch only the products.
$products = Products::paginate(100);
```
I need modify it based on Seller Model
Enable WHERE clause when searching particular seller and product otherwise disable it. Use LIMIT and OFFSET for pagination. Please try this
-- MySQL (v5.8)
SELECT t.product_id, s.seller_id, t.product_name
FROM seller s
INNER JOIN (SELECT product_id
, seller_id
, product_name
, ROW_NUMBER() OVER (PARTITION BY seller_id ORDER BY product_id) row_num
FROM Products
-- WHERE seller_id = 1
-- AND product_id = 1
) t
ON s.seller_id = t.seller_id
AND t.row_num <= s.t_limit;
Please check from url https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=d142f50fb24763ec9fc1e937f8b8d6a7
If needed comma separated product_name then try this one
SELECT CONCAT(MAX(s.seller_name), ' = ', GROUP_CONCAT(t.product_name)) expected_output
FROM seller s
INNER JOIN (SELECT product_id
, seller_id
, product_name
, ROW_NUMBER() OVER (PARTITION BY seller_id ORDER BY product_id) row_num
FROM Products
-- WHERE seller_id = 1
-- AND product_id = 1
) t
ON s.seller_id = t.seller_id
AND t.row_num <= s.t_limit
GROUP BY s.seller_id;
Please check from https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=0e4cfa7dbb744e7aa416b79d6f964775
As per #RahulBiswas sql code I wrote a Laravel Query. It is working
$subq = Products::select(DB::raw('ROW_NUMBER() OVER (PARTITION BY seller_id ORDER BY id) row_num, *'));
$query = Sellers::select('t.*',)->join(\DB::raw('('.$subq->toSql().') as t'), function ($join) use ($subq) {
$join->on('sellers.user_id','t.seller_id')
$join->on('t.row_num', '<=', "sellers.limit")
})

SQL query that finds the an id that is not associated with another id

I'm currently learning the ropes of SQL and i have an tutorial from school that goes like this:
All stores (storeid) sells (productid, storeid) some products (productid)  
A store is considered a monopoly if every product they sell is not sold by any other store.
How do I find the monopolies?
I was thinking of selecting the storeid from 2 of the same tables, but I'm not sure how to continue from there on.
Tables are below:
Store:
+-----------+
| storeid |
+-----------+
| --------- |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
+-----------+
Products:
+-------------+
| productid |
+-------------+
| --------- |
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
+-------------+
Sells:
+--------------------------+
| productid | storeid |
+--------------------------+
| -----------+------------ |
| 1 | 1 |
| 2 | 1 |
| 2 | 2 |
| 3 | 2 |
| 1 | 2 |
| 3 | 3 |
| 2 | 4 |
| 4 | 4 |
| 5 | 5 |
| 6 | 5 |
+--------------------------+
So by my count, only store 5 is considered a monopoly, because they sell products that are not available in other stores.
We can try a self join approach combined with aggregation:
SELECT t1.storeid
FROM yourTable t1
LEFT JOIN yourTable t2
ON t2.productid = t1.productid AND
t2.store_id <> t1.storeid
GROUP BY t1.storeid
HAVING COUNT(t2.storeid) = 0;
The approach here is to try to match each row in Sells to some other row on the condition that it is the same product, but is being sold by some other store. A matching store is one for which none of its products are being sold by other stores, so the count of the second table column in the join should be zero.
Use window functions and aggregation:
select s.storeid
from (select s.*,
count(*) over (partition by productid) as num_stores
from sells s
) s
group by s.storeid
having max(num_stores) = 1;
This should be much faster than a self-join. It is also almost a direct translation of your question. The subquery counts the number of stores where each product is sold. The outer query selects stores where all products are sold in one store.

MySQL Query - Update field with order/count but start at 1 again based on another field

I have a table of products. This table was created with a SELECT from X ORDER by Y query. I want to add sequential row count or order (1,2,3..).
However, I want this count to reset to 1 when the product category or vendor changes. (I'll end up with a order to sort by when querying a combination of product category and vendor).
This problem is simplification of a sub-problem related to a larger issue. So, other solutions involving php aren't relevant.
Here's a sample table:
+--------------+------------------+-----------+-----------+
| product_name | product_category | vendor_id | sortorder |
+--------------+------------------+-----------+-----------+
| Product 1 | A | 1 | 0 |
| Product 2 | A | 1 | 0 |
| Product 3 | A | 1 | 0 |
| Product 4 | B | 1 | 0 |
| Product 5 | B | 1 | 0 |
| Product 6 | C | 2 | 0 |
| Product 7 | C | 2 | 0 |
| Product 8 | C | 2 | 0 |
| Product 9 | C | 2 | 0 |
| Product 10 | C | 2 | 0 |
+--------------+------------------+-----------+-----------+
This is how it should look if the query is run successfully:
+--------------+------------------+-----------+-----------+
| product_name | product_category | vendor_id | sortorder |
+--------------+------------------+-----------+-----------+
| Product 1 | A | 1 | 1 |
| Product 2 | A | 1 | 2 |
| Product 3 | A | 1 | 3 |
| Product 4 | B | 1 | 1 |
| Product 5 | B | 1 | 2 |
| Product 6 | C | 2 | 1 |
| Product 7 | C | 2 | 2 |
| Product 8 | C | 2 | 3 |
| Product 9 | C | 2 | 1 |
| Product 10 | C | 2 | 1 |
+--------------+------------------+-----------+-----------+
I have tried a TON of different queries related to this answer, mostly to try and get this result from the initial query, but to no avail:
Using LIMIT within GROUP BY to get N results per group?
I could run a query like this to get it ordered 1,2,3,10):
SET #pos = 0;
UPDATE testtable SET sortorder = ( SELECT #pos := #pos + 1 );
But, that doesn't accomplish what I want, which is the count to start over again at 1 when the 'product_category' changes between Product 3 and Product 4.
In bad syntax, this is what I want to do:
SET #pos = 0;
UPDATE testtable SET sortorder =
// { if (product_category != [last product_category]
// OR
// if (vendor_id != [last vendor_id])
// }
// THEN SET sortorder = 1
// ELSE SET sortorder = (1+ [last sortorder]
;
Thanks as always...
EDIT-9.12.2016
Trying the solution from #Fancypants. Actually, at first it appears not to work, but it has to do with the "product_name" field sort order. It puts Product 10 before product 5 (1 comes before 5). Once I account for that by using an integer field instead, the result is perfect.
I assume you have an error in your desired result. Sortorder for Product 9 and 10 should be 4 and 5, right?
Here's how you can do it:
UPDATE t
JOIN (
SELECT
t.*
, #rc := IF(#prevpc != product_category OR #prevv != vendor_id, 1, #rc + 1) AS so
, #prevpc := product_category
, #prevv := vendor_id
FROM
t
, (SELECT #prevpc := NULL, #prevv := NULL, #rc := 0) var_init_subquery
ORDER BY product_category, vendor_id, product_name
) sq ON t.product_name = sq.product_name
SET t.sortorder = sq.so;
see it working live in an sqlfiddle

SQL select count from multiple tables

I'm a starter at SQL and I have the following tables, ORDER_PRODUCTS, listing the products of an order and EXCHANGE_PRODUCTS, listing products that will be exchanged.
Both have the same fields, and I need to make a selection counting the amount of products in both tables, distinguishing them by the order_id, does anyone knows how I can do this?
ORDER_PRODUCTS
+-----+------------+----------+---------+
| id | product_id | order_id | amount |
+-----+------------+----------+---------+
| 1 | 5 | 1 | 2 |
| 2 | 7 | 1 | 1 |
| 3 | 13 | 5 | 1 |
| 4 | 18 | 8 | 3 |
| 5 | 45 | 11 | 4 |
+-----+------------+----------+---------+
EXCHANGE_PRODUCTS
+-----+------------+----------+---------+
| id | product_id | order_id | amount |
+-----+------------+----------+---------+
| 1 | 5 | 1 | 1 |
| 2 | 7 | 1 | 2 |
| 3 | 13 | 5 | 1 |
| 4 | 3 | 8 | 2 |
| 5 | 2 | 11 | 1 |
+-----+------------+----------+---------+
You want to use union all to combine the tables and then aggregate them. I might recommend:
select order_id, sum(ordered) as ordered, sum(exchanged) as exchanged,
sum(exchanged + ordered) as total
from ((select order_id, amount as ordered, 0 as exchanged
from order_products
) union all
(select order_id, 0 as ordered, amount as exchanged
from exhange_products
)
) oe
group by order_id;
It is important to use union all rather than union, because union removes duplicates (which can result in bad numbers). Union also incurs overhead that is unnecessary.
And, by "count amount" I assume you really mean to take the sum.
I think this query should do what you Need:
select sum(amount), order_id from (
select amount,order_id from order_products
union
select amount,order_id from Exchange_products)
group by order_id

Join with positions

I have got tables baskets, fruits and basket_fruits (join-table: basket_id-fruit_id).
How can I return a position of each fruit in basket so I will get something like
+---------------------------------------+
| basket_id | fruit_id | fruit_position |
|---------------------------------------|
| 1 | 2 | 1 |
| 1 | 5 | 2 |
+---------------------------------------+
Fruit position is just a number of a row in a returned joined table (it is not a column).
Schema:
baskets: id, title
fruits: id, title
basket_fruits: id, basket_id, fruit_id
MySQL does not support ranging functions so you'll have to use subqueries:
SELECT basket_id, fruit_id,
(
SELECT COUNT(*)
FROM basket_fruit bfi
WHERE bfi.basket_id = bf.basket_id
AND bfi.fruit_id <= bf.fruit_id
) AS fruit_position
FROM basket_fruit bf
WHERE basket_id = 1
or use session variables (faster but relies on implementation details which are not documented and may break in future releases):
SET #rn = 0;
SELECT basket_id, fruit_id, #rn := #rn + 1 AS fruit_position
FROM basket_fruit bf
WHERE basket_id = 1
ORDER BY
fruit_id
I do not see any column in basket_fruits table that I would consider weightable. If you simply want to add some numbers to the data in that table, you could try this (this allows each basket to have its own weights counting from 1):
SET #current_group = NULL;
SET #current_count = NULL;
SELECT
id, basket_id, fruit_id,
CASE
WHEN #current_group = basket_id THEN #current_count := #current_count + 1
WHEN #current_group := basket_id THEN #current_count := 1
END AS fruit_position
FROM basket_fruits
ORDER BY basket_id, id
Sample input:
+----+-----------+----------+
| id | basket_id | fruit_id |
+----+-----------+----------+
| 2 | 2 | 5 |
| 6 | 2 | 1 |
| 9 | 1 | 2 |
| 15 | 2 | 3 |
| 17 | 1 | 5 |
+----+-----------+----------+
Sample output:
+----+-----------+----------+----------------+
| id | basket_id | fruit_id | fruit_position |
+----+-----------+----------+----------------+
| 9 | 1 | 2 | 1 |
| 17 | 1 | 5 | 2 |
| 2 | 2 | 5 | 1 |
| 6 | 2 | 1 | 2 |
| 15 | 2 | 3 | 3 |
+----+-----------+----------+----------------+
SQL provides no guarantees on the order of the returned rows. Therefore fruit_position is likely to be different when queried from time to time. Most likely this will happen due to DML activity on your table.
If you really need some ordering, you should pick:
Use existing columns as ordering key, like fruit name (if exists)
Create a special field, like seq_nr that will specify ordering for your fruits.