Select Query by Pair of fields using an in clause - sql

I have a table called players as follows:
First_Id Second_Id Name
1 1 Durant
2 1 Kobe
1 2 Lebron
2 2 Dwight
1 3 Dirk
I wish to write a select statement on this table to retrieve all rows whose first ids and second ids match a bunch of specified first and second ids.
So for example, I wish to select all rows whose first and second ids are as follows: (1,1), (1,2) and (1,3). This would retreive the following 3 rows:
First_Id Second_Id Name
1 1 Durant
1 2 Lebron
1 3 Dirk
Is it possible to write a select query in a manner such as:
SELECT *
FROM PLAYERS
WHERE (First_Id, Second_Id) IN ((1,1), (1,2) and (1,3))?
If there is a way to write the SQL similar to the above I would like to know. Is there a way to specify values for an IN clause that represents multiple rows as illustrated.
I'm using DB2.

This works on my DB2 (version 9.7 on Linux/Unix/Windows) by using this syntax:
SELECT *
FROM PLAYERS
WHERE (First_Id, Second_Id) IN (VALUES (1,1), (1,2), (1,3))
This syntax won't work on DB2 on the Mainframe (at least in version 9.1) because you can't substitute a sub-select with a VALUES expression. This syntax will work:
SELECT *
FROM PLAYERS
WHERE (First_Id, Second_Id) IN (SELECT 1, 1 FROM SYSIBM.SYSDUMMY1 UNION ALL
SELECT 1, 2 FROM SYSIBM.SYSDUMMY1 UNION ALL
SELECT 1, 3 FROM SYSIBM.SYSDUMMY1)

Here's a very similar solution in postgresql:
SELECT tmp_table.val1, tmp_table.val2
FROM tmp_table
WHERE (tmp_table.val1, tmp_table.val2) not in (select tmp_table2.val1, tmp_table2.val2 from tmp_table2);

With compound primary keys, I would concatenate the two ids and match compound strings.
select id1 + id2 as FullKey, *
from players
where FullKey in ('11','12','13')
(If ids are not strings, simply cast them as such.)

This syntax works in MySQL:
SELECT *
FROM PLAYERS
WHERE (First_Id, Second_Id) IN ((1,1), (1,2), (1,3))

SELECT * FROM <your table> where (<field1>, <field2>, ...) in (SELECT <field1>, <field2>, ... FROM <your table> where <your condition>)
This worked wonder for me.

This type of query works in DB2.
SELECT * FROM A
WHERE (C1, C2) IN (SELECT B1, B2 FROM B WHERE B3=1);

If data is needed in below order:
First_Id
Second_Id
Name
1
1
Durant
1
2
Lebron
1
3
Dirk
2
1
Kobe
2
2
Dwight
then simple and easy way would be:
select * from players order by First_id, second_id;
"where" clause can be used if any condition is needed on any of the column.

Related

Oracle: union all query 1 and query 2 want to minus some rows if query 1 have rowdata

my query as below , i want to minus some rows from query1 when query2 have rowdata , but i don't know how to do:
my query:
with query1 as(
select wm_concat(linkman_name) name,
wm_concat(phone_num) phone,
t.org_id
from (
select linkman_name, phone_num, LINK_ORG_ID, org_id
from TD_SM_LINKMAN
where STATE = '2'
and (LINK_ORG_ID is null or LINK_ORG_ID = '')) t
group by t.org_id) ,
query2 as(
select wm_concat(linkman_name) name,
wm_concat(phone_num) phone,
org_id
from (select linkman_name, phone_num, LINK_ORG_ID, org_id
from TD_SM_LINKMAN
where STATE = '2'
and (LINK_ORG_ID = '55')) t
group by org_id)
select *
from query1
union all
select *
from query2 minus
-- this doesn't work ,i want to minus the rowdata from query 1 when query1.org_id = query2.org_id. the query2 is marked as outer query column.
(select * from query1 where query1.ORG_ID = query2.ORG_ID)
;
sample table
name phone link_org_id org_id
lily 133 1
ming 144 1
hao 333 2
jane 1234 55 2
bob 666 3
herry 555 3
query 1 result:
name phone org_id
lily,ming 133,144 1
hao 333 2
bob,herry 666,555 3
query 2 result:
name phone org_id
jane 1234 2
such like this , jane selected by query2 and hao selected by query 1 . All of them are from a same org which org_id =2 . but i don't need hao ,i just need jane. how to do?
i means if query2 can find result , then no need query1's result. but if query2 can't find any data, then i need query1's data.
The way it is now, you'll first have to split names (and phones) into rows, and then apply set operators (UNION, MINUS) to such a data.
Which means that you shouldn't use WM_CONCAT at all; at least, not at the beginning, because
first you concatenate data
then you'd have to split it back into rows
UNION / MINUS sets
Doing useless job in the first 2 steps.
I'd suggest you to UNION / MINUS data first, then aggregate them using WM_CONCAT. By the way, which database version do you use? WM_CONCAT is a) undocumented, b) doesn't even exist in latest Oracle database versions so you'd rather switch to LISTAGG, if possible.

UNION table in second query based on the result of first query in Oracle sql

I am struggling with following problem (in further I provide pseudocode in order to make my example more understandable)
Assume I have 2 queries which result I want to union with each other
What query1 output looks like:
ID OFFER
1 prod_1
2 prod_2
3 prod_2
4 prod_1
What query2 output looks like:
ID SEGMENT
1 LOW
2 HIGH
3 MED
999 MED
What I need to do is to union results of this 2 queries, but avoid taking row with ID = 999
If there any way to do it using UNION by extracting from query2 rows bases on values of column ID which are present in column ID of query2?
I know that following code is incorrect but it conveys the idea of ​​the question greatly:
--query1
(
SELECT ID, OFFER
FROM TAB1
WHERE ID <= 4
) RES1
UNION
--query2
SELECT ID, SEGMENT
FROM TAB1
WHERE ID IN (SELECT ID FROM RES2)
Result should be as following
ID OFFER
1 prod_1
2 prod_2
3 prod_2
4 prod_1
1 LOW
2 HIGH
3 MED
Appreciate your help
Your pseudo code comes very close. You can use WITH for convenience:
WITH q1 AS (SELECT id, offer FROM tab1 WHERE id <= 4)
, q2 AS (SELECT id, segment FROM tab1 WHERE id IN (SELECT id FROM q1))
SELECT * FROM q1
UNION ALL
SELECT * FROM q2;
(Be aware though that you can get the rows in any order, if you don't specify an ORDER BY clause.)

How to concatenate multiple columns vertically efficiently

for the end goal, I want to create a table that looks like something like this:
Table 1
option_ID person_ID option
1 1 B
2 1
3 2 C
4 2 A
5 3 A
6 3 B
The idea is that a person can choose up to 2 options out of 3 (in this case person 1 only chose 1 option). However, when my raw data format puts the 3 options into one single column, ie:
Table 2
person_ID option
1 B
2 C,A
3 A,B
What I usually do is the use 'Text to Columns' function using the ',' delimiter in Excel, and manually concatenate the 2 columns vertically. However, I find this method to become impractical when faced with more options (say 10 or even 20). Is there a way for me to get from Table 2 to Table 1 efficiently using postgresql or some other methods?
use string_agg() function.
select person_ID, string_agg(option, ',') as option
from table1
group by person_ID
You can use regexp_split_to_table():
select row_number() over () as id,
t.person_id, v.option
from t cross join lateral
regexp_split_to_table(t.option, ',') option
order by person_id, option;
Here is a db<>fiddle.
Actually, if you want the exactly two rows per personid:
select row_number() over () as id, t.person_id, v.option
from t cross join lateral
(values (1, split_part(t.option, ',', 1)), (2, split_part(t.option, ',', 2))) v(pos, option)
order by person_id, pos;

Apply a Substr to a distinct result in oracle

In Oracle, I need to do the average of a column (timeInmillis) from a join query that shows "duplicated" values (relative to the timeInmillis column from table1). I need to mantain the values from the join, but get the right result for the average.
I'm trying to do something like this:
select AVG(SUBSTR(DISTINCT(concat(id1,timeInMillis)),LENGTH(id1)+1,LENGTH(CONCAT(id1,timeInMillis)))), someColumn, otherColumn
FROM table1 t1
LEFT JOIN table2 t2 ON t1.id1 = t2.id1 group by somestuff,someotherStuff;
If I try to do this, I get:
ORA-00936: missing expression
This would be an example:
Table1:
id1 timeInMillis otherColumn
1 5 X
2 15 X
Table2:
id2 id1 otherColumn
--------------------
1 1 X
2 1 X
3 2 X
From my join I get a resultset like this:
id1 id2 timeInmillis moreColumns
--------------------------------
1 1 5 X
1 2 5 X
2 3 15 X
I need to get the average of 5 and 15 (with distinct id1), but I can't modify the where part of the sql (cause of the other values I'm getting)
My result should be:
AVG(TIMEINMILLIS) otherResults
----------------------------------
10 'whatever'
Thanks in advance.
1) Option
select SUBSTR(someColumn,n,m) from (
select DISTINCT someColumn from MYTABLE
);
2) Option
select DISTINCT SUBSTR(someColumn,n,m) from MYTABLE;
*) Queries can return different result.
Your last edit finally explains clearly what you want. You want one line only, showing the avarage of table1's values, but of course without the duplicates that you got because of joining with table 2.
One solution is to get to the final value in two steps:
select avg(distinct_time), sum(sub_sum)
from
(
select max(timeinmillis) as distinct_time, sum(some_other_colum) as sub_sum
from (query)
group by id1
);
The other solution would be to rewrite the query.
Your syntax is wrong. You can try somthing like this either:-
select avg(TimeInMillis), other_cols_as_well
from(SELECT TAB1.id1, TAB2.id2, avg(TimeInMillis) as TimeInMillis
FROM TAB1, TAB2
WHERE TAB1.id1 = TAB2.id1
group by TAB1.id1, TAB2.id2) temp
where temp.id1 <> temp.id2
group by other_cols_as_well
Here is the fiddle
http://sqlfiddle.com/#!4/1fc017/16

Select values in SQL that do not have other corresponding values except those that i search for

I have a table in my database:
Name | Element
1 2
1 3
4 2
4 3
4 5
I need to make a query that for a number of arguments will select the value of Name that has on the right side these and only these values.
E.g.:
arguments are 2 and 3, the query should return only 1 and not 4 (because 4 also has 5). For arguments 2,3,5 it should return 4.
My query looks like this:
SELECT name FROM aggregations WHERE (element=2 and name in (select name from aggregations where element=3))
What do i have to add to this query to make it not return 4?
A simple way to do it:
SELECT name
FROM aggregations
WHERE element IN (2,3)
GROUP BY name
HAVING COUNT(element) = 2
If you want to add more, you'll need to change both the IN (2,3) part and the HAVING part:
SELECT name
FROM aggregations
WHERE element IN (2,3,5)
GROUP BY name
HAVING COUNT(element) = 3
A more robust way would be to check for everything that isn't not in your set:
SELECT name
FROM aggregations
WHERE NOT EXISTS (
SELECT DISTINCT a.element
FROM aggregations a
WHERE a.element NOT IN (2,3,5)
AND a.name = aggregations.name
)
GROUP BY name
HAVING COUNT(element) = 3
It's not very efficient, though.
Create a temporary table, fill it with your values and query like this:
SELECT name
FROM (
SELECT DISTINCT name
FROM aggregations
) n
WHERE NOT EXISTS
(
SELECT 1
FROM (
SELECT element
FROM aggregations aii
WHERE aii.name = n.name
) ai
FULL OUTER JOIN
temptable tt
ON tt.element = ai.element
WHERE ai.element IS NULL OR tt.element IS NULL
)
This is more efficient than using COUNT(*), since it will stop checking a name as soon as it finds the first row that doesn't have a match (either in aggregations or in temptable)
This isn't tested, but usually I would do this with a query in my where clause for a small amount of data. Note that this is not efficient for large record counts.
SELECT ag1.Name FROM aggregations ag1
WHERE ag1.Element IN (2,3)
AND 0 = (select COUNT(ag2.Name)
FROM aggregatsions ag2
WHERE ag1.Name = ag2.Name
AND ag2.Element NOT IN (2,3)
)
GROUP BY ag1.name;
This says "Give me all of the names that have the elements I want, but have no records with elements I don't want"