How to find the exact group that matches to defined values? - mysql

I simplified the table so it is easier to understand.
I have a table with groups and a group exists of multiple values. Here is the table:
VALUE | GROUP
A | 1
B | 1
A | 2
C | 2
B | 3
A | 4
B | 4
A | 5
B | 5
C | 5
I want to give values to my query wich I programmatically build and find the exact group that matches to these values.
For example if I give value A and B to my query I want as a result group 1 and 4
A ---------------> null
A and B ----------> 1 and 4
A , B and C ------> 5
B ---------------> 3
A and C ----------> 2
C ----------------> null

You can use a query like the following (assuming value,group pairs unique):
select `GROUP`
from MyTable
group by `GROUP`
having count(`VALUE`) = count(case when `VALUE` IN ('a','b') then 1 end)
and count(case when `VALUE` IN ('a','b') then 1 end) = #Count;
Where ('a','b') would be the list of values you are testing for, and #Count would be the count of different values in your check set (2 in this case).
Demo: http://www.sqlfiddle.com/#!2/78def/13

Related

Selecting one distinct row from group of entries

ID TYPE DATE
1 A 01/02/2019
1 B 01/21/2019
1 C 02/03/2019
2 A 01/04/2019
2 C 01/29/2019
3 A 01/14/2019
3 B 03/11/2019
So using the table above as an example what I'm trying to do with a similar table is extract a specific Type from each Distinct ID. So let's say I only want to Select Type B from each Distinct ID. Now, the big confusion starts for me when I incorporate the Date. I want to Select Type B from each ID but only if Type B is the most recent date.
So in this instance the only row with Type B as the last date would be the last row.
3 B 03/11/2019
Any suggestions as to what my query should look like?
With NOT EXISTS:
select t.* from tablename t
where t.type = 'B'
and not exists (
select 1 from tablename
where id = t.id and date > t.date
)
See the demo.
Results:
| ID | TYPE | DATE |
| --- | ---- | ------------------- |
| 3 | B | 2019-03-11 00:00:00 |

MySQL : Having doesn't work

I am using MySQL. Here is my schema:
Table b
Column Name | Type | Primary key
id | int | Yes
seq | int | Yes
amt | int
Dummy data
id | seq | amt
1 | 1 | 4000
1 | 2 | 3000
1 | 3 | 2000
1 | 4 | 5000
2 | 1 | 4000
2 | 2 | 3000
3 | 1 | 2000
3 | 2 | 5000
I want to select the record with equivalent id and max value of seq.
HERE is my SQL
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
AND b.seq =
(SELECT max(b.seq) FROM b WHERE b.id = 1)
But I wonder if there is more elegant way of achieving what I want.
For example,
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
HAVING b.seq = max(b.seq)
But it doesn't work as expected. It returns 0 rows.
The HAVING clause is to be used with the GROUP BY clause, which is missing in your query. To add a GROUP BY clause to your query, we'll have to include all the fields in the query that don't have an aggregate function, so everything other than seq:
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
GROUP BY b.id, b.amt
HAVING b.seq = MAX(b.seq)
Now that will obviously not give your the correct results, because you only want to group by id and not amt. Another problem is that you cannot use the fields that are not in the GROUP BY clause in either the SELECT or HAVING clauses, so you cannot use the seq in those two places, and the query above will give you an error.
If your goal is to get the record for id = 1, then your first query is OK, or better to use the query in juergen's answer. But if your real goal is to select one record for each group, then you can do it like this:
SELECT b.id, b.seq, b.amt
FROM b
INNER JOIN (SELECT id, MAX(seq)
FROM b
GROUP BY id) bb ON bb.id = b.id AND bb.seq = b.seq
The result will be:
id | seq | amt
1 | 4 | 5000
2 | 2 | 3000
3 | 2 | 5000
Order the data and take only the first record
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1
ORDER BY seq desc
limit 1
Given your simple example, how about this:
SELECT b.id, b.seq, b.amt
FROM b
WHERE b.id = 1 ORDER BY b.seq DESC limit 1;
SQL HAVING Clause
HAVING filters records that work on summarized GROUP BY results.
HAVING applies to summarized group records, whereas WHERE applies to individual records.
Only the groups that meet the HAVING criteria will be returned.
HAVING requires that a GROUP BY clause is present.
WHERE and HAVING can be in the same query.

sql query to compare values row wise?

i have a table which contains some data as an example:
+----------+-----+------+
| order_id | poi | povi |
+----------+-----+------+
| 1 | A | a |
| 1 | B | b |
| 1 | C | c |
| 2 | A | a |
| 2 | B | b |
| 2 | C | c |
| 3 | A | a |
| 3 | B | b |
| 4 | C | c |
| 5 | A | a |
| 5 | B | b |
| 6 | C | c |
| 7 | A | a |
| 8 | B | b |
| 9 | C | c |
+----------+-----+------+
i have 3 set of values of poi and povi like {A,a},{B,b},{C,c}
i want to get the order_id which contains all three of them, like in the above case the output should be.(order_id which have poi and povi as {A,a} and {B,b} and {C,c} but the problem is that they are diffrent rows)
+----------+
| order_id |
+----------+
| 1 |
| 2 |
+----------+
any idea?
So many times people just getting started ask similar questions to those already asked and answered, including this common scenario. However, not being able to apply know answers to your scenario doesn't help you wrap your head around what is asked, or how the query works in their own scenario... That said, lets look at yours.
You want all DISTINCT orders that have ALL of the following A/a, B/b, C/c entries. Multiple ways to resolve, but the most common is with a where / group by / having.
Start with something simple, looking for any order that has A/a
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
and you would get order 1, 2, 3, 5 and 7. That is simple...
Now, add in your other criteria
select
yt.Order_id
from
YourTable yt
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
and this will give you all rows, but not what you want, but you should be able to see the where criteria is checking for both parts of POI / POIV with an OR between each possible combination. You obviously can not have one record that has a POI of both "A" and "B", that is why the "OR" between each paired ( AND ) criteria. But again, this gives ALL rows. But it is also qualifying only the pieces. So lets add one next step... a group by via the order, but HAVING clause expecting 3 records...
select
ytA.Order_id
from
YourTable ytA
where
( yt.poi = 'A' AND yt.poiv = 'a' )
OR ( yt.poi = 'B' AND yt.poiv = 'b' )
OR ( yt.poi = 'C' AND yt.poiv = 'c' )
group by
yt.Order_id
HAVING
count(*) = 3
The count(*) is to count how many records qualified the WHERE clause and will only return those records that had 3 entries.
Now, what if someone has multiple orders of A/a, A/a, B/b... This COULD give a false answer returned value, but please confirm these queries to meet your needs.
Although accepted, here is another way I would have written the query... somewhat similar to another post below. The premise of this version of the query is to utilize an index and qualify at least 1 record found before trying to find ALL. In this case, it first qualifies for those with an A/a. If an order does not have that, it does not care about looking for a B/b, C/c. If it DOES, then the join qualifies to the next levels too
select
ytA.Order_id
from
YourTable ytA
JOIN YourTable ytB
on ytA.Order_id = ytB.Order_id
AND ytB.poi = 'B'
AND ytB.poiv = 'b'
JOIN YourTable ytC
on ytB.Order_id = ytC.Order_id
AND ytC.poi = 'C'
AND ytC.poiv = 'c'
where
ytA.poi = 'A'
AND ytA.poiv = 'a'
find the "intersection" of lists, each of which contains one set
select id
from
(select id from mytable where poi = 'A' and povi= 'a') t1
inner join
(select id from mytable where poi = 'B' and povi= 'b') t2
using(id)
inner join
(select id from mytable where poi = 'C' and povi= 'c') t3
using(id)
demo

Group and order rows with multiple column MySQL

I want to rows according to same column value.
Suppose this is a table
id name topic
1 A t
2 B a
3 c t
4 d b
5 e b
6 f a
I want result something like this.
id name topic
1 A t
3 c t
2 B a
6 f a
4 d b
5 e b
As you can see these are not order by topic neither by id, it sort about that topic which come first if t come first sort t first, one second a come then sort according to a then b.
if you apply ORDER BY topic it sort a b t or in DESC t b a but required result is t a b
Any suggestion ?
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
,topic CHAR(1) NOT NULL
);
INSERT INTO my_table VALUES
(1,'t'),
(2,'a'),
(3,'t'),
(4,'b'),
(5,'b'),
(6,'a');
SELECT x.*
FROM my_table x
JOIN
( SELECT topic, MIN(id) id FROM my_table GROUP BY topic ) y
ON y.topic = x.topic
ORDER
BY y.id,x.id;
+----+-------+
| id | topic |
+----+-------+
| 1 | t |
| 3 | t |
| 2 | a |
| 6 | a |
| 4 | b |
| 5 | b |
+----+-------+
You can use CASE expression in ORDER BY.
Query
select * from `your_table_name`
order by
case `topic`
when 't' then 1
when 'a' then 2
when 'b' then 3
else 4 end
, `name`;

How to replace a UNION in a query for a view?

I have created the following query to use in a view
SELECT
*
FROM
customers c
JOIN
customer_business cb
ON
c.customer_id = cb.customer_id
union
SELECT
*
FROM
customers c
LEFT JOIN
customer_business
ON
business_id=NULL;
It makes his work perfectly. It shows all customers with the business associated, and at the end, shows all customers with the info of the business in null.
customer_id | business_id
--------------------------------
1 | 1
2 | 1
2 | 2
1 | NULL
2 | NULL
3 | NULL
But the problem es that the UNION makes the view has very poor performace.
I tryed to do it with LEFT JOIN but doesnt shows al the customers with business in null, just the ones without any businesses associated
I know that the solution to speed up my view is to remove that UNION, but i cant figure out how.
Can anyone help me?
Thanks
EDIT
Here's an example
Customer Table
customer_id | name
--------------------------------
1 | test1
2 | test2
3 | test3
Customer_business Table
customer_business_id | customer_id | business_id
----------------------------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 2 | 1
5 | 2 | 2
Expected query result:
name | customer_id | business_id
----------------------------------------------------------
test1 | 1 | 1
test1 | 1 | 2
test1 | 1 | 3
test2 | 2 | 1
test2 | 2 | 2
test1 | 1 | NULL
test2 | 2 | NULL
test3 | 3 | NULL
Updating it based on the comments below and the output you want.
Note that I have used UNION ALL which is faster than UNION as UNION uses DISTINCT to get unique records which in your case doesn't apply. Also, make sure customer_id is PK in Customer table and try adding non-unique index on customer_id in Customer_Business table and it should help with performance.
SELECT name,
C.customer_id,
business_id
FROM Customer C
INNER JOIN Customer_Business CB
ON C.customer_id = CB.customer_id
UNION ALL
SELECT name,
C.customer_id,
NULL
FROM Customer C
Excluding the union which we know that is not performant the other thing that slows down you query is the statement in the second query ON idbusiness = NULL.
I propose to edit you query like this and see the performance as a view:
SELECT c.customer_id, idbusiness
FROM customers c
JOIN customer_business cb ON c.customer_id = cb.customer_id
UNION
SELECT customer_id, NULL
FROM customers c
EDIT:
Looking for an alternative you could try this, it should return the same output (i've changed null values with 0) but i don't think it's faster:
SELECT c.customer_id, idbusiness
FROM customers c
INNER JOIN (
SELECT customer_id, idbusiness
FROM customer_business
UNION
SELECT 0 , 0
)b ON ( c.customer_id = b.customer_id )
OR (
b.idbusiness =0
)
Eventually you could try to put into a view only the subquery b or delete the union by putting the values 0,0 as a record in table customer_business.