MYSQL match multiple rows across same table with multiple ids - mysql

I'm working out a way to process orders where I would have to satisfy the order from multiple vendors, and I want to know which 2 vendors could supply the given order.
The product_ids in my order are 10,20,30,40,50
SELECT *
FROM vendors
WHERE product_id IN (10,20,30,40,50)
gives me all vendors who have at least 1 of the products
vendor_id | product_id
1234 10
1234 20
1234 30
1234 40
1235 10
1235 40
1236 20
1236 30
1236 40
1237 50
9876 10
9876 20
9876 30
9876 40
9877 10
9877 40
9877 50
9878 10
9878 20
9878 30
9878 50
After several crazy subqueries (too long to post here) I can get to a table that shows me what each vendor is missing, and my thought is to then JOIN to that table based on those missing items and then show the vendors who have the refined list.
result should look like (comma separated or not doesn't matter, just easier to read)
vender_1 | product_id | missing_product_id | vendor_2
1234 10,20,30,40 50 9876,9878
1235 10,40 20,30,50 9878
1236 20,30,40 10,50 9877
1237 50 10,20,30,40 1234
or
vender_1 | product_id | missing_product_id | vendor_2
1234 10
1234 20
1234 30
1234 40
1234 50 9876
1234 50 9878
etc...

You want group by and having:
SELECT v.vendor_id
FROM vendors v -- Shouldn't this be called vendorProducts ?
WHERE v.product_id IN (10, 20, 30, 40, 50)
GROUP BY v.vendor_id
HAVING COUNT(DISTINCT v.product_id) = 5;
For multiple vendors, you can extend the above logic. The idea is to join the table together to get a list of pairs of vendors and all the products that they together have. Then, do the same logic as above:
SELECT v.vendor_id1, v.vendor_id2
FROM (SELECT DISTINCT v1.vendor_id as vendor_id1, v2.vendor_id as vendor_id2,
(CASE WHEN n.n = 1 THEN v1.product_id ELSE v2.product_id END) as product_id
FROM vendors v1 JOIN
vendors v2
ON v1.product_id <> v2.product_id AND
v1.vendor_id < v2.vendor_id CROSS JOIN
(SELECT 1 as n UNION ALL SELECT 2) n
UNION ALL
-- Then include the singletons, just in case
SELECT v.vendor_id, NULL, v.product_id
FROM vendors v
) v
WHERE v.product_id IN (10, 20, 30, 40, 50)
GROUP BY v.vendor_id1, v.vendor_id2
HAVING COUNT(DISTINCT v.product_id) = 5;
You can actually do the product filtering in the subquery -- to make the query more efficient. As for making this more general, the "5" is the number of items. I don't know how the ultimate query is being constructed.
EDIT II:
This is a hard problem with a lot of data. Here is another approach that might work better if you have lots of products and few vendors:
select v1.*, v2.*
from (select vendor_id,
max(product_id = 1) as p1,
max(product_id = 2) as p2,
max(product_id = 3) as p3,
max(product_id = 4) as p4,
max(product_id = 5) as p5,
from vendors
where product_id in (1, 2, 3, 4, 5)
group by vendor_id
) v1 join
(select vendor_id,
max(product_id = 1) as p1,
max(product_id = 2) as p2,
max(product_id = 3) as p3,
max(product_id = 4) as p4,
max(product_id = 5) as p5,
from vendors
where product_id in (1, 2, 3, 4, 5)
group by vendor_id
) v2
on (v1.p1 + v2.p1) > 0 and
(v1.p2 + v2.p2) > 0 and
(v1.p3 + v2.p3) > 0 and
(v1.p4 + v2.p4) > 0 and
(v1.p5 + v2.p5) > 0;
Note: If one vendor has all the products, then it will appear paired with every other vendor.

Related

Super complicated mysql query

The database (Mysql) witch i do query comes from an telephony system, and i need to read how many agents (event_parties.agent_id) is logged into different group (event_groups.group_id).
Each time an agent logges in to an group an new record is entered inside events table with event_id=29, if logout event_id=30 at the same time an new entry in table event_parties appears with same g_event_id and the agent_id representing the agent,
also in table events_groups an new entry appears with same g_event_id and group_id representing th egroup that the agents logges in/out to(inside the table event_groups the same g_event_id could the same for more than one entry if agent logges in/out more than one group at the same time).
So my thinking is that i could get the logged in agents in and group_id by selecting all records where there are no newer entry (event_time) with same event_groups.group_id and same event_parties.agent_id and the events.event_id is between 29 and 30.
events.event_id =29 means that agents logges in.
events.event_id =30 means that agents logges out.
I have some serious difficulties to design such an mysql select :(
Here are some example data in each table.
Table:
events
g_event_id event_id event_time
---------- -------- ----------
7816 31 2016-11-03 09:46:18
7815 30 2016-11-03 09:45:18
7814 31 2016-11-03 09:44:18
7813 29 2016-11-03 09:43:18
7812 30 2016-11-03 09:42:18
7811 29 2016-11-03 09:41:18
7810 31 2016-11-03 09:40:18
7809 29 2016-11-03 09:39:18
7808 31 2016-11-03 09:38:18
7807 7 2016-11-03 09:37:18
7806 29 2016-11-03 09:36:18
7805 30 2016-11-03 09:35:18
7804 30 2016-11-03 09:34:18
7803 29 2016-11-03 09:33:18
7802 29 2016-11-03 09:32:18
Table:
event_parties
g_event_id agent_id
---------- --------
7816 1
7815 1
7814 1
7813 1
7812 1
7811 1
7810 2
7809 2
7808 2
7807 3
7806 3
7805 3
7804 3
7803 3
7802 3
Table:
event_groups
g_event_id group_id
---------- --------
7816 1
7815 1
7814 1
7813 1
7813 2
7813 3
7813 4
7812 1
7811 1
7810 1
7809 1
7808 1
7807 1
7806 1
7806 3
7805 4
7804 1
7804 2
7803 4
7802 1
7802 2
From tables above i want my select statement result to be:
group_id agent_id
-------- --------
4 1
3 1
2 1
1 2
1 3
3 3
Is such a query possible, is there any sql genius out there :)
/ Kristian
SELECT group_id, agent_id
FROM (SELECT agent_id, eg.group_id, if(event_id = 29, 1, -1) AS transitions
FROM event_parties ep
JOIN `events` e ON ep.g_event_id = e.g_event_id
JOIN event_groups eg ON ep.g_event_id = eg.g_event_id
WHERE e.event_id IN (29, 30)) AS t
GROUP BY agent_id, group_id
HAVING sum(transitions) > 0
ORDER BY agent_id, group_id DESC
Link to SQL Fiddle
I think that this will do what you are saying. For every agent/group combination, it sets number of transitions to 1 if they login and -1 if they log out. Looking over the whole data set, if they have logged in and then logged out, the sum will be 0 for a specific agent group, which is calculated in the outer query.
This does depend on not starting with a log out event for a specific agent/group combination. If the data set you are looking starts with a log out event, then the user will never appear to be logged out.
Alternatively, you could get the same result by looking at the last record, and determining if it's a 29 or a 30, and only displaying the ones that are 29.
SELECT group_id, agent_id
FROM (SELECT agent_id, group_id, max(e.g_event_id) AS last_event_id
FROM event_parties ep
JOIN `events` e ON ep.g_event_id = e.g_event_id
JOIN event_groups eg ON ep.g_event_id = eg.g_event_id
WHERE e.event_id IN (29, 30)
GROUP BY agent_id, group_id) AS last_event
JOIN `events` e ON e.g_event_id = last_event.last_event_id
WHERE e.event_id = 29;
This is less dependent on where you are starting in the series, but the join is slightly more complex.
Link to SQL Fiddle
FWIW syntax style change using natural join:
select group_id, agent_id
from ( select agent_id, group_id,
max( g_event_id ) as g_event_id
from event_parties natural join `events` natural join event_groups
where event_id in (29, 30)
group
by agent_id, group_id ) as last_event
natural join `events`
where event_id = 29;
I just make a Demo now clear me what you want
select g.group_id,p.agent_id from event_groups g
join event_parties p on g.g_event_id=p.g_event_id
join events e on p.g_event_id =e.g_event_id where e.event_id=29

Select query result inside WHERE clause

Hello I am trying to make a WHERE clause where the condition is the id of the previous selection, example:
SELECT
,P1.caseid
,(SELECT SUM(P1.amount) FROM table_s P1 WHERE P1.status = 4 AND P1.caseid = 20)
as variable
FROM table_s P1 GROUP BY P1.caseid";
let's say each iteration the P1.caseid have value of
20,
45,
20,
How I can insert this value to be the condition of the WHERE clause here: WHERE P1.status = 4 AND P1.caseid = 20
Instead of P1.caseid to be = to 20 it have to be equal to the actual caseid inside the database for each row.
So for each row it will be:
WHERE P1.caseid = 20
WHERE P1.caseid = 45
WHERE P1.caseid = 35
In this case the number is eqaul to the caseid inside the DB.
TABLE NAME: table_s
id | caseid | amount | status
-- | ------------------------
1 | 20 | 10 | 4
2 | 45 | 10 | 4
3 | 20 | 10 | 4
DB is as follows, the result should be:
1 ROW = caseid: 20 amount: 20 status 4
2 ROW = caseid: 45 amount: 10 status 4
Or
$variable = 20
$variable = 10
I think I've worked out what you're asking...
The important note here is to use different aliases for your table in the outer and inner queries. Otherwise you have a serious scope problem. (If two instances of the same entity have the same name, how can MySQL ever know which one you're referring to? It will choose the one in the nearest scope. So, instead, call one of them, for example, lookup.)
SELECT
P1.*,
(
SELECT SUM(lookup.amount)
FROM table_s lookup
WHERE lookup.status = 4
AND lookup.caseid = P1.caseid
)
correlated_sub_query_total_by_caseid
FROM
table_s P1
But that itself can be re-written without the correlated sub-query...
SELECT
P1.*,
SUM(CASE WHEN status = 3 THEN amount END) AS status_3_total,
SUM(CASE WHEN status = 4 THEN amount END) AS status_4_total
FROM
table_s P1
INNER JOIN
table_s lookup
ON lookup.caseid = P1.caseid
GROUP BY
P1.primary_key
That said, you added another comment that seems to contract your question...
the idea is to select the sum of the amount for each caseid and display it. as caseid - sum
For that you just need an aggregation...
SELECT
caseid,
SUM(amount)
FROM
table_s
GROUP BY
caseid
And if you only want to aggregate where the status is 3 or 4...
SELECT
caseid,
SUM(CASE WHEN status = 3 THEN amount ELSE 0 END) status_3_total
SUM(CASE WHEN status = 4 THEN amount ELSE 0 END) status_4_total
FROM
table_s
GROUP BY
caseid

How to join three tables to get Sum

I have three tables: Products, Purchase, Invoice
Product table:
Producct_no Name
1 A
2 B
3 C
Purchase table:
Purchase_no Product_no Qty
001 1 81
002 2 150
003 3 10
Invoice table:
Invoice_no Product_no Qty
001 1 20
002 2 10
003 3 10
I want to get each product's purchase quantity and invoice quantity, I used following query
SELECT PR.product_no, sum(P.qty),sum(I.qty)
FROM products PR
LEFT JOIN invoice I ON I.product_no=PR.product_no
LEFT JOIN purchase P ON P.product_no=PR.product_no
group by PR.product_no
product_no sum(P.qty) sum(I.qty)
001 162 160
002 150 50
003 10 10
EDIT: Expected results
product_no sum(P.qty) sum(I.qty)
001 81 20
002 150 10
003 10 10
My query is giving me wrong response (sum of quantities are wrong), please help me to correct my query to get the results properly. thanks
I don't think your sample data is really what you have based on the information provided. My best guess here is that your query is doing a fan-out on either or both of those joins which is messing up your sums. You need to sum them separately, else additional rows in either on of those joins will fan out the other join, duplicating your results in the sum. This is evident in your result since 001 looks to be double (even though your sample data doesn't show it).
Something like this would ensure sums independent of each other:
SELECT PR.product_no,
( SELECT sum(I.qty)
FROM invoice I
WHERE I.product_no=PR.product_no ) invoice_qty,
( SELECT sum(P.qty)
FROM purchase P
WHERE P.product_no=PR.product_no ) purchase_qty
FROM products PR
I think you have a problem with GROUP BY there. I would do something like this in this case
SELECT P.Product_no, Inv.InvProdSum, Pur.PurProdSum
FROM Product P
LEFT JOIN (SELECT Product_no, SUM(Qty) AS InvProdSum
FROM Invoice
GROUP BY Product_no) AS Inv
ON P.Product_no = Inv.Product_no
LEFT JOIN (SELECT Product_no, SUM(Qty) AS PurProdSum
FROM Purchase
GROUP BY Product_no) AS Pur
ON P.Product_no = Pur.Product_no
Here is SQL FIddle for that http://sqlfiddle.com/#!9/731a5/1
NOTE i add some extra value here to test how it's work...
GL!
Here is my solution without subqueries:
with
product(product_no, name) as (values
(1, 'A'),
(2, 'B'),
(3, 'C')
),
purchase(purchase_no, product_no, qty) as (values
('001', 1, 81),
('002', 2, 150),
('003', 3, 10),
('004', 1, 1000)
),
invoice(invoice_no, product_no, qty) as (values
('001', 1, 20),
('002', 2, 10),
('003', 3, 10),
('004', 2, 5000)
),
mixed as
(select d.product_no,
purchase_no, p.qty as qty_purchase,
invoice_no, i.qty as qty_invoice,
row_number() over(partition by purchase_no) as rn_purchase,
row_number() over(partition by invoice_no) as rn_invoice
from product d
left join purchase p on p.product_no = d.product_no
left join invoice i on i.product_no = d.product_no
)
select product_no,
sum((case when rn_purchase = 1 then 1 else 0 end) * qty_purchase) as sum_purchase,
sum((case when rn_invoice = 1 then 1 else 0 end) * qty_invoice) as sum_invoice
from mixed
group by product_no
order by product_no
;
And results:
product_no|sum_purchase|sum_invoice|
----------|------------|-----------|
1| 1081| 20|
2| 150| 5010|
3| 10| 10|

SQL query with different averages for different columns for the same data

The SQL challenge I'm working on is to build a query to display the name of an individual and their average performance over 10 iterations of data in one column and their average over 50 iterations of data in the next column. Grouped by name of course. The iterations progress in order therefore the average of the past 10 for an individual would be an average score of the 10 highest ID numbers for that individual. The raw dataset looks like this:
ID, Name, Score
1, Joe, 10
2, Bob, 13
3, Joe, 9
4, Kim, 6
5, Rob, 8
6, Han, 9
7, Kim, 12
There is about 1000 rows like this with about 50 names. The end goal is to run a query that returns something like this:
Name, AvgPast10, AvgPast50
Bob, 8, 10
Joe, 7, 9
Kim, 6, 10
Han, 9, 6
Rob, 7, 5
When I tried to do this I realized that there might be a different ways of doing this. Maybe a self join back onto itself, perhaps nested select statements. I tried and realized that I was getting in over my head. Also, my boss is a real stickler for query optimization. For some reason he despises nested select statements. If I need them then I better have a compelling reason or at least have some idea about how optimization was built into the query.
Admittedly this one uses a nested select (or a subquery):
SELECT Name, AVG(CASE WHEN Rank <= 10 THEN Score END) AvgPast10,
AVG(CASE WHEN Rank <= 50 THEN Score END) AvgPast50
FROM (
SELECT Name,
#rank := IF(#Name = Name, #rank+1, 1) as Rank,
#Name := Name, Score
FROM tbl
ORDER BY Name, ID DESC
) A
GROUP BY Name
See my Demo that uses Past 3 and Past 5 for simplicity.
Your sample is very small so I have used 2 and 50 below, but hopefully the process is clear regardless of numbers or how many averages
| NAME | AVG_2 | AVG_50 |
|------|-------|--------|
| Bob | 13 | 13 |
| Han | 9 | 9 |
| Joe | 9 | 9.5 |
| Kim | 12 | 9 |
| Rob | 8 | 8 |
SELECT
name
, sum_2 / (count_2 * 1.0) AS avg_2
, sum_50 / (count_50 * 1.0) AS avg_50
FROM (
SELECT
name
, COUNT(CASE
WHEN rn <= 2 THEN score END) count_2
, SUM(CASE
WHEN rn <= 2 THEN score END) sum_2
, COUNT(CASE
WHEN rn <= 50 THEN score END) count_50
, SUM(CASE
WHEN rn <= 50 THEN score END) sum_50
FROM (
SELECT
*
, ROW_NUMBER() OVER (PARTITION BY name ORDER BY ID DESC) AS rn
FROM Scores
) x
GROUP BY
name
) y
ORDER BY
name
I wasn't sure what you wanted to do if the number of observations is less than the quantity required (e.g. a count of 20 but average is for 50), I have used the actual count in this example.
see: http://sqlfiddle.com/#!3/84cf6/2

count in mysql query not correct because using pivot tables - converting rows to columns

I have the following query that returns count incorrectly due to pivot table use (3-4 rows in additional value table causes count to be incorrect)
SQL:-
select
count(o.model_id) as qty,
o.model_id,
bd.brand_name,
pd.product_name,
MAX(IF(oinfokey.name = 'Colour' , oinfokeyvalue.value , NULL)) AS colourName,
MAX(IF(oinfokey.name = 'Grade' , oinfokeyvalue.value , NULL)) AS gradeName,
MAX(IF(oinfokey.name = 'Network' , oinfokeyvalue.value , NULL)) AS networkName,
MAX(IF(oinfokey.name = 'Condition' , oinfokeyvalue.value , NULL)) AS conditionName
From
`order` o
INNER JOIN
product pd
ON
pd.id = o.model_id
INNER JOIN
brand bd
ON
bd.id = pd.brand_id
INNER JOIN
order_additional_information oinfo
ON
oinfo.order_id = o.id
INNER JOIN
order_additional_information_key oinfokey
ON
oinfokey.id = oinfo.order_additional_information_key_id
INNER JOIN
order_additional_information_value oinfokeyvalue
ON
oinfokeyvalue.id = oinfo.order_additional_information_value_id
GROUP BY
o.model_id
Output is:-
qty model_id brand_name product_name colourName gradeName networkName conditionName
3 320 LG KP235 Brown Brand New Unknown
3 393 Blackberry Curve 8520 Black 14 Day 3 (Three)
4 854 Apple iPhone 4S 16GB Green Brand New Orange POT - GOOD LCD
3 1087 Apple iPad 4 64GB WiFi Bronze Grade B Unknown
3 1182 Samsung Ch#t 357 S3570 Black Grade B Unlocked
6 1713 Nokia 5500 Sport Blue Grade C Vecton
How can I correctly get the count from order table, the count result should be:-
Count - Model ID
1 - 320,
1 - 393,
2 - 854,
1 - 1087,
1 - 1182,
2 - 1713
Use DISTINCT in select:
count(DISTINCT o.id) as qty,