MySQL - How to use GROUP BY / ORDER BY with "nested" dataset? - mysql

My (sub)query results in following dataset:
+---------+------------+-----------+
| item_id | version_id | relevance |
+---------+------------+-----------+
| 1 | 1 | 30 |
| 1 | 2 | 30 |
| 2 | 3 | 22 |
| 3 | 4 | 30 |
| 4 | 5 | 18 |
| 3 | 6 | 30 |
| 2 | 7 | 22 |
| 1 | 8 | 30 |
| 5 | 9 | 48 |
| 4 | 10 | 18 |
| 5 | 11 | 48 |
| 3 | 12 | 30 |
| 3 | 13 | 31 |
| 4 | 14 | 19 |
| 2 | 15 | 22 |
| 1 | 16 | 30 |
| 5 | 17 | 49 |
| 2 | 18 | 22 |
+---------+------------+-----------+
18 rows in set (0.00 sec)
Items and versions are stored in separate InnoDB-tables.
Both tables have auto-incrementing primary keys.
Versions have a foreign key to items (item_id).
My question: How do I get a subset based on relevance?
I would like to fetch the following subset containing the most relevant versions:
+---------+------------+-----------+
| item_id | version_id | relevance |
+---------+------------+-----------+
| 1 | 16 | 30 |
| 2 | 18 | 22 |
| 3 | 13 | 31 |
| 4 | 14 | 19 |
| 5 | 17 | 49 |
+---------+------------+-----------+
It would be even more ideal to fetch the MAX(version_id) in case of equal relevance.
I tried grouping, joining, ordering, etcetera in many ways but I'm not able to get the desired result.
Some of the things I tried is:
SELECT item_id, version_id, relevance
FROM (subquery) a
GROUP BY item_id
ORDER BY relevance DESC, version_id DESC
But of course the ordering happens after the fact, so that both relevance and MAX(version_id) information is lost.
Please advice.

This is how you can do this:
SELECT t1.item_id, max(t1.version_id), t1.relevance FROM t t1
LEFT JOIN t t2 ON t1.item_id = t2.item_id AND t1.relevance < t2.relevance
WHERE t2.relevance IS NULL
GROUP BY t1.item_id
ORDER BY t1.item_id, t1.version_id
Output:
| ITEM_ID | VERSION_ID | RELEVANCE |
|---------|------------|-----------|
| 1 | 16 | 30 |
| 2 | 18 | 22 |
| 3 | 13 | 31 |
| 4 | 14 | 19 |
| 5 | 17 | 49 |
Fiddle here.

Related

calculate unit in stock of a product

This is the question:
To find
units in stock of a product, use the InventoryTransactions table, find total quantity
purchased and subtract quantities sold and on hold.
The Inventory Transaction table:
This are the transaction type for each transaction id:
transaction id 1 = purchased, trasaction id 2 = sold, transaction id 3 = on hold
+----+-----------+
| ID | TypeName |
+----+-----------+
| 1 | Purchased |
| 2 | Sold |
| 3 | On Hold |
| 4 | Waste |
+----+-----------+
4 rows in set (0.00 sec)
The product id, quantity, and type of transaction:
SELECT productid, quantity, transactiontype FROM Inventory_Transactions ORDER BY productid;
+-----------+----------+-----------------+
| productid | quantity | transactiontype |
+-----------+----------+-----------------+
| 1 | 40 | 1 |
| 1 | 15 | 2 |
| 1 | 25 | 3 |
| 3 | 100 | 1 |
| 3 | 50 | 2 |
| 4 | 40 | 1 |
| 4 | 10 | 2 |
| 4 | 30 | 2 |
| 5 | 40 | 1 |
| 5 | 25 | 2 |
| 6 | 100 | 1 |
| 6 | 10 | 2 |
| 6 | 90 | 2 |
| 7 | 40 | 1 |
| 7 | 10 | 2 |
| 7 | 30 | 2 |
| 8 | 40 | 1 |
| 8 | 17 | 2 |
| 8 | 25 | 1 |
| 8 | 25 | 2 |
| 8 | 20 | 2 |
| 8 | 3 | 2 |
| 14 | 40 | 1 |
| 17 | 40 | 1 |
| 17 | 40 | 2 |
| 19 | 20 | 1 |
| 19 | 20 | 2 |
| 19 | 30 | 1 |
| 19 | 30 | 2 |
| 19 | 25 | 1 |
| 19 | 10 | 2 |
| 19 | 10 | 1 |
| 19 | 25 | 2 |
| 20 | 40 | 1 |
| 20 | 40 | 2 |
| 21 | 20 | 1 |
| 21 | 20 | 2 |
| 34 | 60 | 1 |
| 34 | 100 | 1 |
| 34 | 100 | 2 |
| 34 | 12 | 3 |
| 34 | 10 | 3 |
| 34 | 1 | 3 |
| 34 | 50 | 1 |
| 34 | 300 | 1 |
| 34 | 300 | 2 |
| 34 | 87 | 2 |
| 40 | 120 | 1 |
| 40 | 50 | 2 |
| 40 | 30 | 2 |
| 40 | 40 | 2 |
| 41 | 40 | 1 |
| 41 | 200 | 1 |
| 41 | 200 | 2 |
| 41 | 30 | 2 |
| 41 | 50 | 1 |
| 41 | 50 | 2 |
| 41 | 10 | 2 |
| 43 | 100 | 1 |
| 43 | 20 | 2 |
| 43 | 300 | 1 |
| 43 | 300 | 2 |
| 43 | 25 | 3 |
| 43 | 250 | 1 |
| 43 | 300 | 3 |
| 43 | 5 | 2 |
| 48 | 100 | 1 |
| 48 | 10 | 2 |
| 48 | 100 | 1 |
| 48 | 100 | 2 |
| 48 | 10 | 2 |
| 48 | 40 | 2 |
| 48 | 40 | 2 |
| 51 | 40 | 1 |
| 51 | 10 | 2 |
| 51 | 30 | 2 |
| 52 | 100 | 1 |
| 52 | 40 | 2 |
| 56 | 120 | 1 |
| 56 | 110 | 3 |
| 57 | 80 | 1 |
| 57 | 100 | 1 |
| 57 | 100 | 2 |
| 65 | 40 | 1 |
| 66 | 80 | 1 |
| 72 | 40 | 1 |
| 72 | 50 | 1 |
| 72 | 50 | 2 |
| 72 | 40 | 2 |
| 74 | 20 | 1 |
| 74 | 20 | 2 |
| 77 | 60 | 1 |
| 80 | 75 | 1 |
| 80 | 30 | 2 |
| 80 | 10 | 2 |
| 80 | 20 | 3 |
| 80 | 15 | 2 |
| 81 | 125 | 1 |
| 81 | 200 | 1 |
| 81 | 200 | 2 |
| 81 | 50 | 3 |
| 81 | 25 | 3 |
+-----------+----------+-----------------+
102 rows in set (0.00 sec)
I will need to calculate unit of stock for each productid by SUM(quantity purchased) - SUM(quantity sold) - SUM(quantity on hold)
My take:
CREATE VIEW purchased AS
SELECT productid, SUM(quantity) quantity
FROM Inventory_Transactions
WHERE transactiontype = 1
GROUP BY productid
ORDER BY productid;
CREATE VIEW sold AS
SELECT productid, SUM(quantity) quantity
FROM Inventory_Transactions
WHERE transactiontype = 2
GROUP BY productid
ORDER BY productid;
CREATE VIEW onhold AS
SELECT productid, SUM(quantity) quantity
FROM Inventory_Transactions
WHERE transactiontype = 3
GROUP BY productid
ORDER BY productid;
SELECT pur.productid, pur.quantity - so.quantity - on.quantity
FROM purchased pur, sold so, onhold on
WHERE pur.productid = so.productid = on.productid;
But I got this error
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'on WHERE pur.productid = so.productid = on.productid' at line 1
These are the individual VIEW I created above:
SELECT * FROM purchased;
+-----------+----------+
| productid | quantity |
+-----------+----------+
| 1 | 40 |
| 3 | 100 |
| 4 | 40 |
| 5 | 40 |
| 6 | 100 |
| 7 | 40 |
| 8 | 65 |
| 14 | 40 |
| 17 | 40 |
| 19 | 85 |
| 20 | 40 |
| 21 | 20 |
| 34 | 510 |
| 40 | 120 |
| 41 | 290 |
| 43 | 650 |
| 48 | 200 |
| 51 | 40 |
| 52 | 100 |
| 56 | 120 |
| 57 | 180 |
| 65 | 40 |
| 66 | 80 |
| 72 | 90 |
| 74 | 20 |
| 77 | 60 |
| 80 | 75 |
| 81 | 325 |
+-----------+----------+
28 rows in set (0.00 sec)
SELECT * FROM sold;
+-----------+----------+
| productid | quantity |
+-----------+----------+
| 1 | 15 |
| 3 | 50 |
| 4 | 40 |
| 5 | 25 |
| 6 | 100 |
| 7 | 40 |
| 8 | 65 |
| 17 | 40 |
| 19 | 85 |
| 20 | 40 |
| 21 | 20 |
| 34 | 487 |
| 40 | 120 |
| 41 | 290 |
| 43 | 325 |
| 48 | 200 |
| 51 | 40 |
| 52 | 40 |
| 57 | 100 |
| 72 | 90 |
| 74 | 20 |
| 80 | 55 |
| 81 | 200 |
+-----------+----------+
23 rows in set (0.01 sec)
SELECT * FROM onhold;
+-----------+----------+
| productid | quantity |
+-----------+----------+
| 1 | 25 |
| 34 | 23 |
| 43 | 325 |
| 56 | 110 |
| 80 | 20 |
| 81 | 75 |
+-----------+----------+
6 rows in set (0.00 sec)
I am using the northwind database
Here are all the table relationship in northwind database.
May I know how do I solve this?
The approach I would take is using something called "Conditional aggregation"
use a summed case expression to determine if it's a purchase
use a summed 2nd case expression to determine if it's a sale/hold
subtract the two
group by product
.
SELECT ProductID,
SUM(case when TransactionType = 1 then quantity else 0 end) -
SUM(case when TransactionType in (2,3) then quantity else 0 end) as AvailableInventory
FROM Inventory_Transactions
GROUP BY ProductID
As to the nature of your error:
you have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'on WHERE pur.productid = so.productid = on.productid' at line 1
on is a reserved word use onh or onhold or something for the alias
using , joins is and old technique use Inner, outer, full outer, cross join and use the on notation to specify how he tables relate
equality checks within where clauses must return true false. To date they are not trinary. If A=B but <> C what would you expect to happen? so break out your productID's so they handle all the needed joins.
you're going to have problems of matching records because not all 3 may have a value so if you match on productID for sold or hold, you may not have a product and it would fall out of your results... thus outer joins are needed if you continue with your current approach.
.
ORIGINAL
SELECT pur.productid, pur.quantity - so.quantity - on.quantity
FROM purchased pur, sold so, onhold on
WHERE pur.productid = so.productid = on.productid;
should be:
SELECT coalesce(pur.productid, so.productid,onhold.ProductID) as productid,
coalesce(pur.quantity,0) - coalesce(so.quantity,0) -
coalesce(onhold.quantity) as AvailableInventory
FROM purchased pur
FULL OUTER JOIN sold so
on pur.productid = so.productid
FULL OUTER JOIN onhold
on pur.productid = onhold.productid
OR so.productid = onhold.productid
GROUP BY coalesce(pur.productid, so.productid,onhold.ProductID);
Breaking this down
the coalesce of productID is because we don't know in which of the 1,2,3 a productID will exist. But, we need the product id to show up for any of the 3 not just purchases.
the coalesce on quantity is because if we try to subtract a NULL value, we get a NULL so we need to ensure a numeric value exists. pretend for a second product ID 1 exists in purchases but has had no sales or holds. 10 purchases - NULL = NULL... not what we want. now if the productID 2 is in sales but not purchases we'd have NULL-10-NULL... again not what we want. Also if we just used the productid from purchases it would be NULL on this record...; also not good.
we replaced the , notation which is a cross join to a full outer so we don't lose records. Now cross join would work but it takes # of purchases * #of sales * number of on hold then limits the records based on your where clause this is WAY more work than the database needs to do and on a large dataset would be VERY slow.
Discussed earlier, where clause can't be trinary it must be a binary result comparing just 2 values.
Now full outer vs left vs inner vs right....
FULL OUTER: include all records from both tables and line them up where they match on Key (productID in our example)
LEFT JOIN include all records from the first table and any that match from the 2nd
INNER Include only records which exist in both tables.
RIGHT include all records from the 2nd table and any that match from the 1st.
CROSS JOIN: all records related to all records (Very slow but there are uses; just not here)
and more...
If we can't assume each of your products has a value in each group: pur, sold, onhold; we need to use full outer joins as above; so we don't exclude any records. However even this can be an issue because we have to ensure each productID ties back to a related product in another table if it exists.
To do this we have to use an OR on the last match (kinda ugly) and as we don't know the source of productID... we have to find one using coalesce.
If we can assume all products must first exist as a purchase, we can left join the other two. A bit cleaner, but we still have to handle the situation where there may be no data in sold or on hold, so we have to coalesce the 0 in for the quantities. we don't have to coalesce the productid since we know a purchase has to exist.
SELECT pur.productid, sum(coalesce(pur.quantity,0) - coalesce(so.quantity,0) -
coalesce(onhold.quantity)) as AvailableInventory
FROM purchased pur
LEFT OUTER JOIN sold so
on pur.productid = so.productid
LEFT OUTER JOIN onhold
on pur.productid = onhold.productid
--Note: we tie back to pur both times as we know it exists there.
GROUP BY pur.productid;
Hopefully you can see the conditional aggregation is the better approach as it simplifies and reduces joins and avoids much of the coalesing we do.

Sort Columns by Column_Totals through Alter Table or Select Query in MySQL at Runtime

I want to alter or generate Select Query of the Source_Table below at runtime by getting the column total (sum) first then sort according to its result:
Source_Table:
+----+------------+-----------+-----------+-----------+-----------+-----------+
| ID | Name | Field_1 | Field_2 | Field_3 | Field_4 | Field_5 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 1 | abc | 10 | 18 | 5 | 21 | 6 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 2 | ghq | 22 | 14 | 12 | 11 | 23 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
| 3 | xyz | 35 | 8 | 16 | 7 | 4 |
+----+------------+-----------+-----------+-----------+-----------+-----------+
The Result_Table I am looking at is:
|--------------- sorted fields based on total --------------|
+------------+-----------+-----------+-----------+-----------+-----------+
| Name | Field_5 | Field_3 | Field_4 | Field_2 | Field_1 |
+------------+-----------+-----------+-----------+-----------+-----------+
| abc | 4 | 5 | 21 | 18 | 10 |
+------------+-----------+-----------+-----------+-----------+-----------+
| ghq | 23 | 12 | 11 | 14 | 22 |
+------------+-----------+-----------+-----------+-----------+-----------+
| xyz | 4 | 16 | 7 | 8 | 35 |
+------------+-----------+-----------+-----------+-----------+-----------+
| Total | 31 | 33 | 39 | 40 | 67 | --> get column sum and sort from lowest to highest
+------------+-----------+-----------+-----------+-----------+-----------+
I am not so sure if this is possible with MySQL as I am not able to find good reference in the internet for this case. But I will try..

MySQL get multiple rows into columns

I have a table called visits where concat(s_id, c_id) is unique and id is the primary key. s_id is the ID number of a website and c_id is a campaign ID number. I want to show all the hits each campaign is getting and group by the site. I want each site on a single row
+-----+------+------+------+
| id | s_id | c_id | hits |
+-----+------+------+------+
| 1 | 13 | 8 | 245 |
| 2 | 13 | 8 | 458 |
| 3 | 13 | 3 | 27 |
| 4 | 13 | 4 | 193 |
| 5 | 14 | 1 | 320 |
| 6 | 14 | 1 | 183 |
| 7 | 14 | 3 | 783 |
| 8 | 14 | 4 | 226 |
| 9 | 5 | 8 | 671 |
| 10 | 5 | 8 | 914 |
| 11 | 5 | 3 | 548 |
| 12 | 5 | 4 | 832 |
| 13 | 22 | 8 | 84 |
| 14 | 22 | 1 | 7 |
| 15 | 22 | 3 | 796 |
| 16 | 22 | 4 | 0 |
+----+------+------+-------+
I would like to have the following result set:
s_id | hits | hits | hits| hits
13 | 245 | 458 | 27 | 193
14 | 320 | 183 | 783 | 226
5 | 671 | 914 | 548 | 832
22 | 84 | 7 | 796 | 0
Here is what I have tried which does not pull all the hits columns back.
SELECT v.*, v2.* FROM visits v
INNER JOIN visits v2 on v.s_id = v2.s_id
GROUP BY s_id
How can I get multiple rows into columns?
If your'e data set is not crazy huge and you are just trying to get the multiple rows as a single row.... one way to do this...
SELECT
s_id,
GROUP_CONCAT(hits SEPARATOR ',') as hits_list
FROM
visits
GROUP BY s_id
Since it doesn't use any joins or subqueries etc, i find this way to be quite fast.
you can later split/explode the data based on the ',' separator in PHP or whatever language you are using.
$hits = explode($hits_list, ','); //get them in an array

select with a multiple row in pivot table

I really don't know how to retrieve a specific row from a pivot table.
Here's my pivot table:
+----+-------------+--------+
| id | peticion_id | tag_id |
+----+-------------+--------+
| 1 | 3 | 15 |
| 2 | 3 | 21 |
| 3 | 3 | 28 |
| 4 | 8 | 21 |
| 5 | 8 | 28 |
| 6 | 44 | 21 |
| 7 | 44 | 28 |
+----+-------------+--------+
I wanna make maybe i think is a dynamic query where for example if i do:
SELECT peticion_id where tag_id in (21,28,15);
The result is:
+-----+-------------+--------+
| id | peticion_id | tag_id |
+-----+-------------+--------+
| 1 | 3 | 15 |
| 2 | 3 | 21 |
| 6 | 44 | 21 |
| 4 | 8 | 21 |
| 3 | 3 | 28 |
| 7 | 44 | 28 |
| 5 | 8 | 28 |
+-----+-------------+--------+
I need the result to be only peticion_id = 3.
I don't know how to formulate the query, maybe I'm looking for the equivalent for(where in) to be an AND clause.
Hope somebody helps.
Thanx
Try this:
SELECT peticion_id
FROM tbl
WHERE tag_id in (21,28,15)
GROUP BY peticion_id
HAVING count(*) = 3

join with a group by?

i have a table called rc_language_type_table with:
id language
1 english
2 Xhosa
3 afrikaans
etc
then i have a table rc_language_type_assoc_table with:
profile_id | language_type_id |
+------------+------------------+
| 3 | 1 |
| 13 | 1 |
| 15 | 1 |
| 16 | 1 |
where i have profiles and each profile is connected to a language id in a 1 to many
so then i did:
select *,count(*) from rc_language_type_assoc_table group by language_type_id;
+------------+------------------+----------+
| profile_id | language_type_id | count(*) |
+------------+------------------+----------+
| 3 | 1 | 96 |
| 3 | 2 | 19 |
| 3 | 3 | 18 |
| 64 | 4 | 51 |
| 94 | 5 | 10 |
| 37 | 6 | 26 |
| 3 | 7 | 21 |
| 3 | 8 | 4 |
| 3 | 9 | 6 |
| 88 | 10 | 4 |
| 3 | 11 | 3 |
+------------+------------------+----------+
what i want now is: instead having the language_type_id i want to display the actual language...how would i do this please???
i tried:
select *, count(*)
from rc_language_type_assoc_table, rc_language_type_table
group by language_type_id
where rc_language_type_assoc_table.language_type_id = rc_language_type_table.id;
but i get a syntax error...
please help??
thank you
GROUP BY should be "after" the WHERE statement and not before
select *, count(*)
from rc_language_type_assoc_table, rc_language_type_table
where rc_language_type_assoc_table.language_type_id = rc_language_type_table.id
group by language_type_id ;