Select unique row using several columns unique combination excluding not applicable values - mysql

I have a table with three columns, lets define it and fill it with sample data:
A | B | C |
------ | ----- | ------|
A1 | NA | NA |
A1 | B1 | NA |
NA | B1 | NA |
NA | B1 | C1 |
NA | NA | C1 |
As we can see combination (A,B,C) is unique. I need to have a select which would select exact one row using parameters :A :B :C. For example:
1) If I have :A = A1, :B = SOME_B, :C = SOME_C. It should select first row because column A has exact match while columns B and C are not applicable.
2) If I have :A = SOME_A, :B = SOME_B, :C = SOME_C. It should not return anything.
First try was:
SELECT *
FROM SAMPLE_TABLE
WHERE (A = :A OR A = 'NA') AND (B = :B OR B = 'NA') AND (C = :C OR C = 'NA');
This returned first two rows. Wrong, as expected. That is not what I want.
Second try:
SELECT *
FROM SAMPLE_TABLE
WHERE (A = :A OR (A = 'NA' AND NOT EXISTS(SELECT 1 FROM SAMPLE_TABLE WHERE A = :A AND (B = :B OR B = 'NA') (C = :C OR C = 'NA')) AND (B = :B OR B = 'NA') AND (C = :C OR C = 'NA');
It works and logic is correct, but for B and C I need to do same stuff. After that I need to modify NOT EXISTS for column A which should include NOT EXISTS for B and C, which also should include NOT EXISTS for column A and so on. I get a loop.
Any ideas how can we write simple SQL for solving such problem? Or I just miss some silly thing?
EDIT:
Oh, it's my fault, wasn't able to be clear enought. Let's look into business example (table TEAM_MATRIX):
COUNTRY|PRIORIT| TEAM |
------ | ----- | ------|
NA | NA | GLOB_T|
UK | LOW | UK_T1 |
UK | MED | UK_T2 |
UK | HIGH | UK_T3 |
US | NA | US_T |
US | LOW | US_T1 |
Coulmns country and priority can be treated as input parameters. Column team should be treated as output. Several examples:
1) Suppose we have parameters: country = FR, priority = LOW. Then output team should be GLOB_T because there is no such combination FR and LOW. (NA means that we do not care).
2) If country is US and priority is LOW => ouput team should be US_T1 because we have combination US and LOW.
3) If country is US and priority is MED => output team should be US_T because we have combination US and NA (NA means that we do not care).
Is there a way to have SQL which follows this logic?
SELECT TEAM
FROM TEAM_MATRIX
WHERE (COUNTRY = ?1 OR COUNTRY = 'NA') AND (PRIORIT = ?2 OR PRIORIT = 'NA');
Here ?1 - country parameter, ?2 - priority parameter. This does not work, for example combination ((NA, NA) -> GLOB_T) will always be selected.
Thanks!

Related

How to select multiple columns based on a condition in SQL?

I have a table, the columns are like this:
| id | position | A | B | C | D | E |
I'm selecting by id. If the position is '1', it should return column A, B, C.
If not, it should return column D, E
For example, if the table is:
| id | position | A | B | C | D | E |
| 0 | 1 | a | b | c | d | e |
| 1 | 2 | a | b | c | d | e |
When the query selects id=0, the result should be:
| 0 | 1 | a | b | c |
When the query selects id=2, the result should be:
| 1 | 2 | d | e |
How should I write the SQL query?
you can't write a query to select columns according to selected rows.
but if you want specific columns you can try this:
SELECT id, position, A, B, C
FROM tablename
WHERE id = 1
Try this...
select id, position,
if (id=0, A, D),
if (id=0, B, E),
if (id=0, C, "")
from Table1
It's a little inflexible but it's a start. You can play with it here: http://sqlfiddle.com/#!9/d47c33/10/0
You cannot have a result set with a different number of columns per row, so it will have to be | 1 | 2 | d | e | NULL |.
To get what you want you can make two queries and UNION them together. Otherwise you could make a CASE statement for each column, but don’t do this if you don’t have to.
If you have any choice in the matter, I would recommend just making two queries and process the results separately.
But the UNION:
select id, position, a, b, c
from cool_table
where id in (1, 2, 3, 4, 5)
and position = 1
UNION
select id, position, d, e, null
from cool_table
where id in (1, 2, 3, 4, 5)
and position <> 1
Really though, this seems like you’re going “against the grain”. Perhaps some improvement can be made elsewhere that will make this unnecessary.
A database query is not the place to put display logic. As others have said, a query has a fixed number of fields.
You could use if or case to null fields depending on the position field but it would be simpler to select all the fields, send them to the front end, and let it sort of out how to show the results.

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

MySQL select all left entries from a table which is joined from another table

I have the following MySQL-Statement:
SELECT norm.NormID, norm.NormName
FROM (assignment
INNER JOIN norm
ON assignment.NID = norm.NormID )
INNER JOIN wire
ON assignment.LID = wire.WireID
WHERE wire.WireID= 109
ORDER BY norm.NormName;
Now what I got are the entries from the table assignment with the NormID and NormName for that WireID.
What I want to get are the entries from the table norm, which are not setted for this WireID.
E.g.:
WireID has the norm assignment A, B, D, G.
The table norm has the entries A, B, C, D, E, F, G, H.
What I want to get from the MySQL-Statment are the entries C, E, F, H.
How can I select those left norm entries for this WireID?
With the above statement I would get:
-----------------------
| NormID | NormName |
-----------------------
| 1 | A |
| 2 | B |
| 4 | D |
| 7 | G |
-----------------------
I want to have this Table:
-----------------------
| NormID | NormName |
-----------------------
| 3 | C |
| 5 | E |
| 6 | F |
| 8 | H |
-----------------------
I think (if I understood what you asked) you can try this :
SELECT norm.NormID, norm.NormName
FROM assignment
INNER JOIN norm ON assignment.NID = norm.NormID
LEFT JOIN wire ON assignment.LID = wire.WireID
WHERE assignment.LID= 109
AND wire.wireID IS NULL
ORDER BY norm.NormName;
Edit after your comments.
I think you could use:
SELECT A.NormID, A.NormName
FROM norm A
LEFT JOIN (SELECT NID FROM assignment WHERE LID = 109) B ON B.NID = A.NormID
WHERE B.NID IS NULL
ORDER BY A.NormName;
OR
SELECT A.NormID, A.NormName
FROM norm A
WHERE NOT EXISTS (SELECT 1 FROM assignment WHERE LID = 109 AND ASSIGNMENT.NID = A.NormID)
ORDER BY A.NormName;
Try this:
select norm.NormID,norm.NormName from norm
Inner JOIN
assignment on assignment.NID = norm.NormID
where assignment.LID in(select wireID from Wire where WireID = 109)
Im not so sure coz i dont have your data
after you added a sample data the entries that are not setted with 109 wireid are these:
SELECT norm.NormID, norm.NormName
FROM assignment
inner JOIN norm
ON assignment.NID = norm.NormID
INNER JOIN wire
ON assignment.LID = wire.WireID
WHERE wire.WireID <> 109
ORDER BY norm.NormName;

MySQL: select one row in subquery similar to MAX, only with custom order priority

Say I have two tables a and b; the column id is unique in table a but has multiple entries in table b, with different values for column x as well as other columns in that table. The primary key of table b is (id,x).
I need to be able to join a single row from b to the SELECT query as I could do with MAX like so:
SELECT * FROM a
INNER JOIN b USING(id)
WHERE b.x = (SELECT MAX(x) FROM b WHERE b.id = a.id)
With MAX, this works no problem. But I don't need MAX. I actually need something like this:
SELECT * FROM a
INNER JOIN b USING(id)
WHERE b.x = (SELECT x FROM b
WHERE x IN (2,3,7) OR x IS NULL
ORDER BY FIELD(x,3,2,7) DESC
LIMIT 1)
This query fails because my MySQL version does not support LIMIT in this subquery. Is there another way to make sure to join at least and exactly one row in the order I provided?
Schematic overview of what I'm trying to do would be:
Select * from table a for each id
Join table b where x = 7
If no entry in b exists where a.id=b.id and x = 7, join b where x = 2
If no entry in b exists where a.id=b.id and x IN (2,7), join b where x = 3
If no entry in b exists where a.id=b.id and x IN (2,3,7), join b where x IS NULL
I have this need because:
I can't use more than one query in this particular bit of code I need; it has to be one magic all-in-one query
I can't use INNER JOIN (SELECT *) statements
I can't have a query where any id is present more than once (so DISTINCT is not option because the values in table b might differ)
I know for sure that table b has an entry for a.id where x is either 2, 3, 7 or NULL, but I don't know which and I also don't know how many entries there are for a.id in table b.
Upgrading MySQL is not the best option since this code would have to work on generic servers of any of my customers
So, in short, my question is: is there a function similar to MAX that can take a specific order into account instead of the MAX value?
Thanks in advance!
You can do this by specifying manually the ordering weight of each X.
mysql> select * from aa;
+----+------+
| id | name |
+----+------+
| 1 | John |
| 2 | Ted |
| 3 | Jill |
| 4 | Jack |
+----+------+
4 rows in set (0.00 sec)
mysql> select * from bb;
+------+------+------------+
| id | x | class |
+------+------+------------+
| 1 | 7 | HighPriori |
| 1 | 2 | MediumPrio |
| 1 | 3 | LowPriorit |
| 2 | 2 | Medium |
| 2 | 3 | Low |
| 3 | 3 | Low only |
+------+------+------------+
5 rows in set (0.00 sec)
select version();
+-------------+
| version() |
+-------------+
| 5.5.25a-log |
+-------------+
SELECT aa.name, bb.x, bb.class
FROM aa LEFT JOIN bb ON (aa.id = bb.id AND bb.x IN (2,3,7)
AND bb.x = ( SELECT x FROM bb WHERE bb.id = aa.id AND x IN (2,3,7)
ORDER BY CASE
WHEN x = 7 THEN 100
WHEN x = 2 THEN 200
WHEN x = 3 THEN 300
ELSE 400
END LIMIT 1 )
);
The innermost SELECT will choose the most suitable value of X based on priority: 7 if available, else 2, else 3. This works also if, as in this case, the "priorities" are not in order, i.e., 7 is higher than 3, but 2 is also higher than 3.
Then the LEFT JOIN will match that one record, if it exists, or NULL, if it does not.
John has 2,3 and 7 and gets the 7-record, while Ted has 2 and 3, and gets the 2:
+------+------+------------+
| name | x | class |
+------+------+------------+
| John | 7 | HighPriori |
| Ted | 2 | Medium |
| Jill | 3 | Low only |
| Jack | NULL | NULL |
+------+------+------------+
(Strictly speaking, the IN (2,3,7) in the inner SELECT and the ELSE in the CASE are redundant; either will do).
In CASE of need...
If the X field already establishes an order, e.g., you want the values 3,2,7 in either 2 - 3 - 7 or 7 - 3 - 2 numeric order, you can do without the CASE: instead of
ORDER BY CASE
WHEN x = 7 THEN 100
WHEN x = 2 THEN 200
WHEN x = 3 THEN 300
ELSE 400
END
you can just specify
ORDER BY x
or
ORDER BY x DESC
...the performance improvement is slight, even if x is indexed on, but if you have a great many values of X, then specifying them all in the CASE can be awkward, and studying exceptions may be lengthy.
But let's imagine you wanted the objects to be in this order
First Last
20,21,22,23,40,41,42,9,1,2,3,4,5
which typically arises when X is declared unsigned with values 1-5 ("we will never need more classes and 1 is always going to be first!"), then some weeks later someone adds a "this is a new exciting product, must go first!" exception and assigns 9 to it, and finally it happens again with "let's add a whole new XY class starting with 2X and 4X for the forthcoming product series...", you could do this as
WHEN x <= 5 THEN 900+x
WHEN x = 9 THEN 809
ELSE 700 + x
which translates the above jumbled set in the ordered sequence
720,721,722,723,740,741,742,809,901,902,903,904,905
and with three WHENs you do all the work.
SELECT * FROM a
INNER JOIN b USING(id)
WHERE b.x = (SELECT x FROM b
WHERE x IN (2,3,7) OR x IS NULL
ORDER BY FIELD(x,3,2,7) DESC
LIMIT 1)
switch the subquery to a join
SELECT * FROM a
join ( SELECT x FROM b
WHERE x,id IN (2,3,7) OR x IS NULL
ORDER BY FIELD(x,3,2,7) DESC
LIMIT 1) as X on X.id = a.id

How to create left joins without repetition of rows on the left side

I have a scenario where there are two tables (tables A and B) linked in a one to many relationship. For a row in table A, the maximum number of linked rows in B is two, and these two rows (if they exist) are different from each other through a type column whose value is either x or y.
Aid | Name Bid | type | Aid
1 | name1 1 | x | 1
2 | name2 2 | x | 2
3 | name3 3 | y | 2
Now, what I want is to have a join query for the two tables in such a way that all rows in A will be displayed (no repetition) and two columns called type x and type y will hold a boolean / integer value to show the existence of types x and y for each row in A. i.e,
Aid | Name | Type X | Type Y |
1 | name1 | X | NULL |
2 | name2 | X | Y |
3 | name3 | NULL | NULL |
My DBMS is MySql.
Thanks.
You have to use two joins:
SELECT A.*, b1.type AS typeX, b2.type as typeY
FROM A
LEFT JOIN B b1
ON A.aid = b1.aid
AND b1.type = 'x'
LEFT JOIN B b2
ON a.aid = b2.aid
AND b2.type = 'y'
Well, this happens because your second table uses the EAV-model. If you had two tables, one for type_x and one for type_y, your relational schema would be a lot cleaner.
Offcourse, EAV does work, be it more clumsily:
SELECT a.aid, a.name, bx.type, by.type
FROM table_a a
LEFT JOIN table_b bx
ON a.aid = bx.aid
AND bx.type = 'x'
LEFT JOIN table_b by
ON a.aid = by.aid
AND by.type = 'y'