Find the same sets of pairs - mysql

I have such scheme in mysql:
TableA (id integer PK, pid integer, mid integer)
Ex. data:
id | pid | mid
1 | 2 | 2
2 | 2 | 4
3 | 3 | 4
4 | 4 | 2
5 | 4 | 4
6 | 3 | 2
7 | 3 | 5
I have pid with some mid's and want to find all pid's with the same set of mid's. In example for pid=2 answer is 2,4
group_concat is not suitable for me
I think it should be simple, but the answer eludes me
UPD:
I have tried group_concat:
SELECT DISTINCT(b.pid) FROM (SELECT pid, group_concat(mid) as concated FROM TableA where pid=100293) as a, (select pid, group_concat(mid) as concated, COUNT(1) as count FROM TableA group by pid) as b where a.concated=b.concated;

Since you are working with integers, instead of group_concat you could generate a bitmask on distinct mid values for each pid and join on that. Then it's just math all the way down:
SELECT DISTINCT pid
FROM (SELECT pid, sum(pow(2,mid)) as midmask FROM (SELECT distinct pid, mid FROM tableA) as t1a GROUP BY pid) as t1
INNER JOIN (SELECT pid, sum(pow(2,mid)) as midmask FROM (SELECT distinct pid, mid FROM tableA) as t2a GROUP BY pid) as t2
ON t1.midmask = t2.midmask
IF mid is already distinct for each pid then you can get rid of the inner-inner subqueries.
Using #GordonLinoff's excellent single-subquery approach where GROUP_CONCAT is only used on the main query (where it won't be so expensive). Instead of the group_concat on the inner query we use the bitmask approach that may be quicker.
SELECT midmask>>1, group_concat(pid)
FROM (SELECT pid, sum(pow(2,mid)) as midmask FROM (SELECT distinct pid, mid FROM tableA) as t1a GROUP BY pid) as t1
GROUP BY midmask;
Results:
+---------+-------------------+
| midmask | group_concat(pid) |
+---------+-------------------+
| 10 | 2,4 |
| 26 | 3 |
+---------+-------------------+
Obviously that midmask in the result set isn't super necessary, but you can pick out the values from the bitmask if you want to see the mid values that contributed to the match if you like.
I'm using the bit right-shift operator to insure that the proper bit is set in the midmask result otherwise you'll be off by one. If you don't care about the output of the midmask, then don't bother with the >>1 portion of the query.

You can use this query. It will give you comma separated pids.
select `mid`, group_concat(`pid`) from `tableA` group by `mid`;

In MySQL, I would approach this using group_concat():
select mids, group_concat(pid)
from (select pid, group_concat(mid order by mid) as mids
from t
group by pid
) t
group by mids;
This solves the general problem, for all pids. Solving for 1 pid is a bit tricky in MySQL (no window functions), but you can try:
select t.pid, t2.pid, count(*)
from t join
t t2
on t.mid = t2.mid and t2.pid = 2
group by t.pid, t2.pid
having count(*) = (select count(*) from t where t.pid = t.pid) and
count(*) = (select count(*) from t where t.pid = t2.pid);
For this, you want indexes on t(mid, pid) and t(pid).

Related

Check if ID from a table exists in another table, and if so, how many times

Let's say I have two tables
Table a
some_ID
1
2
3
4
Table b
some_ID
1
2
1
4
Now what I would like to receive is a table like
id amount
1 | 2
2 | 1
I tried with a following query:
SELECT COUNT(a.some_id) as id
FROM Table_a
INNER JOIN Table_b
ON Table_a.some_id = Table.b.some_id
but that only returned how many id rows there are in both tables.
Any help?
Do the grouping on table_b and then join that result set on table_a
SELECT b.* FROM
(
SELECT id, COUNT(*) AS Cnt
FROM Table_b
GROUP BY id
) b
INNER JOIN Table_a a ON a.id = b.id
SQLFiddle
If you want the zero counts:
SELECT a.some_id AS id, count(b.some_id) as amount
FROM a LEFT JOIN b ON a.some_id = b.some_id
GROUP BY a.some_id
Result:
id | amount
1 | 2
2 | 1
3 | 0
4 | 1
If not:
SELECT a.some_id AS id, count(*) as amount
FROM a INNER JOIN b ON a.some_id = b.some_id
GROUP BY a.some_id
Result:
id | amount
1 | 2
2 | 1
4 | 1
The difference is the join type. Once left outer join. Then inner join. Note that in the first case it is important to count with count(b.some_id). With count(*) the rows with missing b entries would be counted as 1. count(*) counts the rows. count(expression) counts the non-null values.
If I understand correctly, you want a histogram of histograms:
select cnt, count(*) as num_ids
from (select id, count(*) as cnt
from b
group by id
) b
group by cnt;

MySql select next lower number without using limit

Is it possible to select the next lower number from a table without using limit.
Eg: If my table had 10, 3, 2 , 1 I'm trying to select * from table where col > 10.
The result I'm expecting is 3. I know I can use limit 1, but can it be done without that?
Try
SELECT MAX(no) no
FROM table1
WHERE no < 10
Output:
| NO |
------
| 3 |
SQLFiddle
Try this query
SELECT
*
FROM
(SELECT
#rid:=#rid+1 as rId,
a.*
FROM
tbl a
JOIN
(SELECT #rid:=0) b
ORDER BY
id DESC)tmp
WHERE rId=2;
SQL FIDDLE:
| RID | ID | TYPE | DETAILS |
------------------------------------
| 2 | 28 | Twitter | #sqlfiddle5 |
Another approach
select a.* from supportContacts a inner join
(select max(id) as id
from supportContacts
where
id in (select id from supportContacts where id not in
(select max(id) from supportContacts)))b
on a.id=b.id
SQL FIDDLE:
| ID | TYPE | DETAILS |
------------------------------
| 28 | Twitter | #sqlfiddle5 |
Alternatively, this query will always get the second highest number based on the inner where clause.
SELECT *
FROM
(
SELECT t.col,
(
SELECT COUNT(distinct t2.col)
FROM tableName t2
WHERE t2.col >= t.col
) as rank
FROM tablename t
WHERE col <= 10
) xx
WHERE rank = 2 -- <<== means second highest
SQLFiddle Demo
SQLFiddle Demo (supports duplicate values)
If you want to get next lower number from table
you can get it with this query:
SELECT distinct col FROM table1 a
WHERE 2 = (SELECT count(DISTINCT(b.col)) FROM table1 b WHERE a.col >= b.col);
later again if you want to get third lower number you can just pass 3 in place of 2 in where clause
again if you want to get second higher number, just change the condition of where clause in inner query with
a.col <= b.col

SUM of COUNTS MySQL

I have the following sql query
SELECT
(SELECT count(cid) from A where uid=45 group by cid) as cats
(SELECT count(cid) from A where uid=45) as cats_total
The first sub-select produces 4 rows and counts the number of items in each cid. The second sub-select produces only 1 row and counts the numbers of items total.
My problem lies in the second sub-select. SQL is producing an error because they have different amounts of rows. Is there an adjustment I can make so the second sub-select has 4 rows, or to whatever amount of rows the first sub-select produces?
UPDATE: Let me clarify further with a table I need to produce
+------+------------+
| cats | cats_total |
+------+------------+
| 2 | 17 |
| 5 | 17 |
| 1 | 17 |
| 9 | 17 |
+------+------------+
Alternative, you can use UNION ALL,
SELECT SUM(totals) grandTotal
FROM
(
SELECT count(cid) totals from A where uid=45 group by cid
UNION ALL
SELECT count(cid) totals from A where uid=45
) s
Kaf is right.
If someone's interested here is a working version tested via jdbc to an Oracle db:
SELECT cats,cats_total from
(SELECT count(cid) as cats from A where uid=45 group by cid)
cross join
(SELECT count(cid) as cats_total from A where uid=45)
you can try
SELECT cats.total, cats_total.total from
(SELECT count(cid) as total from A where uid=45 group by cid) as cats ,
(SELECT count(cid) as total from A where uid=45) as cats_total
I think you can do a cross join of two sub queries;
SELECT cats, cats_total
FROM (SELECT count(cid) as cats from A where uid=45 group by cid) as c1
CROSS JOIN
(SELECT count(cid) as cats_total from A where uid=45) as c2

Mysql interesting query

I have the next table:
+---------+------------+
| firm_id | service_id |
+---------+------------+
| 6 | 2 |
| 6 | 4 |
| 23 | 7 |
| 23 | 6 |
I want to get ONLY companies who do not have service_id=4 in their service list.
A query of the above table should return only the company with firm_id=23 because firm_id=6 has one record with service_id=4.
I want to make it with one query. Is this possible (without joins)?
Thanks.
P.S. Thanks everyone. User "derobert" suggested very interesting way, what i was looking for.
You can do it several ways. Here is one, with a correlated subquery:
SELECT DISTINCT firm_id FROM table t1
WHERE NOT EXISTS ( SELECT 1 FROM table t2 WHERE t1.firm_id = t2.firm_id AND t2.service_id = 4)
In MySQL-land it is often better to rewrite as a self-join:
SELECT DISTINCT firm_id
FROM table t1 LEFT JOIN table t2 ON (t1.firm_id = t2.firm_id AND t2.service_id = 4)
WHERE t2.firm_id IS NULL
Finally, here is one way to do it that doesn't involve subqueries or joins (but I expect performs worse than either of the above)
SELECT firm_id, CONCAT(',', GROUP_CONCAT(service_id SEPARATOR ','), ',') AS service_ids
FROM table t1
GROUP BY firm_id
HAVING service_ids NOT LIKE '%,4,%'
I confess I haven't actually run these; please forgive typos.
SELECT DISTINCT
firm_id
FROM
TableX AS t
WHERE
NOT EXISTS
( SELECT
*
FROM
TableX AS s
WHERE
s.firm_id = t.firm_id
AND
s.service_id = 4
)
or:
SELECT
firm_id
FROM
TableX
GROUP BY
firm_id
HAVING
COUNT(service_id = 4) = 0
You could try
SELECT * FROM your_table
WHERE firm_id NOT IN
(SELECT DISTINCT firm_id FROM your_table
WHERE service_id = 4) a
As suggested by ypercube (thanks!) you can also try
SELECT DISTINCT firm_id, service_id FROM your_table
WHERE firm_id NOT IN
(SELECT firm_id FROM your_table
WHERE service_id = 4) a

Select every field that fullfil the condition

I have a table call production
factory_id | factory_name | product_id
1 | A | 1
1 | A | 2
1 | A | 3
2 | B | 3
3 | C | 1
3 | C | 2
3 | C | 3
3 | C | 4
3 | C | 5
I'm trying to develop a query that will return two factory name pair such that every product of factory1 is produced by factory2, result looked like:
factory_name_1 | factory_name_2
A | C
B | A
B | C
I have some nested self join and renames, but I can't wrap my head around how I can apply EXISTS or IN for this scenario that does "for each product produced by factory X do condition". Thanks to any help in advanced.
Update:
Sorry that I forgot to paste my query:
select t0.fname0, t1.fname1
from (
select factory_id as fid0, factory_name as fname0, product_id as pid0, count(distinct factory_id, product_id) as pnum0
from production
group by factory_id
) t0
join
(
select factory_id as fid1, factory_name as fname1, product_id as pid1, count(distinct factory_id, product_id) as pnum1
from production
group by factory_id
) t1
where t0.fid0 <> t1.fid1
and t0.pnum0 < t1.pnum1
and t0.pid0 = t1.pid1;
Update 2: production is the only table. Expected output factory1 and factory2 are just the rename of factory_name attribute.
You need to JOIN the table for each factory pairing to make sure they "join" on the same product_ids, otherwise you might end up with similar counts for DISTINCT product_ids but these will not necessarily refer to the same product_ids.
This is my take on it:
SELECT bfna,afna, pcnt FROM (
SELECT a.factory_name afna, b.factory_name bfna, COUNT(DISTINCT b.product_id) commoncnt
FROM tbl a LEFT JOIN tbl b ON b.factory_name!=a.factory_name AND b.product_id=a.product_id
GROUP BY a.factory_name, b.factory_name
) c
INNER JOIN (
SELECT factory_name fna, COUNT(DISTINCT product_id) pcnt
FROM TBL GROUP BY factory_name
) d ON fna=bfna AND commoncnt=pcnt
ORDER BY bfna,afna
You can find a demo here: https://rextester.com/JJGCK84904
It produces:
bfna afna commoncnt
A C 3
B A 1
B C 1
For simplicity I left out the column factory_id as it does not add any information here.
Fun fact: as I am using only "bare-bone" SQL expressions, the above code will run on SQL-Server too without any changes.
You can do it this way:
select A as factory_name_1 , B as factory_name_2
from
(
select A, B, count(*) as Count_
from
(
select a.factory_name as A, b.factory_name as B
from yourtable a
inner join yourtable b
on a.product_id = b.product_id and a.factory_id <> b.factory_id
)a group by A, B
)a
inner join
(select factory_name, count(*) as Count_ from yourtable group by factory_name) b
on a.A = b.factory_name and a.Count_ = b.Count_
Order by 1
Output:
factory_name_1 factory_name_2
A C
B A
B C
The other solutions just seem more complicated than necessary. This is basically a self-join with aggregation:
with t as (
select t.*, count(*) over (partition by factory_id) as cnt
from tbl t
)
select t1.factory_id, t2.factory_id, t1.factory_name, t2.factory_name, count(*)
from t t1 join
t t2
on t1.product_id = t2.product_id and t1.factory_id <> t2.factory_id
group by t1.factory_id, t2.factory_id, t1.factory_name, t2.factory_name, t1.cnt
having count(*) = max(t1.cnt);
Here is a db<>fiddle.