creating groups under specific conditions - teradata

I've the following table A (this is just sample) which presents the channel the customer make a reservation:
custNum channelType reservationNumber
1 web 1
2 Iphone 2
1 Android 3
3 web 4
and I'm trying to separate between 3 groups of customers (I'd like to get their cust numbers):
1. customers who used only the web
2. customers who used only the cellular channels
3. customers who used both
under the sample above, the results should be:
1. web only (custNum=3)
2. cellular only (custNum=2)
3. web+cellular (custNum=1)
I'd tried the following for web usesr only (but I know it's wrong):
sel custNum from A where channelType in ('web');
any help will be appreciated.

You need to do some conditional aggregation like this:
select custNum,
case
when max(case when channelType = 'web' then 1 else 2 end) = 1
then 'web only'
when max(case when channelType = 'web' then 2 else 1 end) = 1
then 'celular only'
else 'both'
end
from tab
group by custNum

Related

How do I select and join from a single table with 2 unique IDs based on one ID and concatenate the options?

I don't have much experience with SQL so forgive me if it is a simple answer...
I am working on a personal project in Python and I am using sqlite3. I currently have a table created like this:
CREATE TABLE IF NOT EXISTS table (id text, username text, score integer, UNIQUE(id,username))
where the idea is two users can score the same ID differently, and also the same user can score multiple ID's.
ID
Username
Score
1
User1
5
1
User2
7
1
User3
10
2
User1
6
2
User2
2
How to I select to find common ID's among users as well as their scores? Is there a select command I can string up to return something like this? (i.e. common between users User1 and User2)
ID
Score User1
Score User2
1
5
7
2
6
2
Use conditional aggregation:
SELECT id,
MAX(CASE WHEN username = 'User1' THEN score END) ScoreUser1,
MAX(CASE WHEN username = 'User2' THEN score END) ScoreUser2
FROM tablename
WHERE username IN ('User1', 'User2')
GROUP BY id
HAVING COUNT(*) = 2;
See the demo.

Case statement based on partition of data

I'm trying to categorize my data based on the existence of text within a variable for each id that I have. For example:
ID Groupname
1 A
1 B
1 F
2 D
2 B
2 C
The result I want would be
ID Groupname Category
1 A AF
1 B AF
1 F AF
2 D D
2 B D
2 C D
I want to do something like the following but I cannot get it to work
Select * ,
CASE WHEN A,F in groupname (partition by id) THEN AF
WHEN D in groupname (partition by id) THEN D
....
ELSE null END
FROM table
Answers in postgres/redshift would greatly be appreciated!
EDIT:
I fixed the problem with F that JNevill brought up.
EDIT2:
One user suggested a simple solution that works if you only need 1 combination. E.g. If i see F or A then it's AF. But if I need to see A and F it will not work.
Here is the simple solution:
WHEN sum(CASE WHEN combo in (20,28,19,27) then 1 end) over (partition by log_id)=1 then 1000
If i want combinations I have to do the following which creates very long code. Is there an easier way to do this?
WHEN sum(CASE WHEN combo in (20,28,19,27) then 1 end) over (partition by log_id)=1
and sum(CASE WHEN groupid in (1048,598,428) then 1 end) over (partition by log_id)=1
then 1000
you're right about case with partitioning, it just requires conditional aggregate
select
*
,case
when sum(case when groupname in ('A','F') then 1 end) over (partition by id)=1 then 'AF'
when sum(case when groupname in ('D') then 1 end) over (partition by id)=1 then 'D'
end as category
from t
one note is that if you want only the pair of A and F to make AF category it's not possible with this syntax... count(distinct case...) over () returns an error and requires more complex query, but from what I see you don't need it since F is not in your sample data

combining IDs under GROUP BY?

I have an sqlite table "log" that looks like this:
ID p_id viewer
----------------------
1 1 100
2 1 200
3 1 300
4 3 550
5 3 230
6 5 420
7 2 320
8 2 203
9 9 10
10 9 55
And I want to get the average viewers from each p_id. That'd be
SELECT avg(viewer) FROM log GROUP BY p_id
But I want to treat p_id 1 and 5 as the same, so I'd get the average viewers of p_id 1 and 5 combined. How do I do that? Note that the table is much larger, and I need to treat two p_id's as the same multiple times. Can I still do a "group by" to achieve this, or are there other ways?
I tried this and it works with mySQL at least.
SELECT avg(viewer) FROM log GROUP BY CASE p_id WHEN 5 THEN 1 ELSE p_id END;
Edit: When using an alias-table named 'aliases' with the fields 'alias_from_id' and 'aliased_as':
SELECT
CASE ISNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)))
WHEN 1 THEN log.pid ELSE aliased_as END AS the_id,
SUM(value) AS value_sum
FROM log
LEFT JOIN aliases ON (alias_from_id = pid)
GROUP BY
CASE ISNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)))
WHEN 1 THEN log.pid ELSE aliased_as END
I've tested this with mySQL and it works like a charm. It might be possible to simplify this SQL-query a bit but this is the best I can do at the moment :)
Edit2: Changed ISNULL to the corresponding SQLite IFNULL function
SELECT
IFNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)), log.pid) AS the_id,
SUM(value) AS value_sum
FROM log
LEFT JOIN aliases ON (alias_from_id = pid)
GROUP BY
IFNULL((SELECT aliased_as FROM aliases WHERE (alias_from_id = log.pid)), log.pid)
I don't know if SQLite supports the AS keyword, if it doesn't then just remove that keyword - the functionality should be the same.

Filtering in Oracle based on a group of values contained in a list of values

I have following two tables:
ID_PERSON NAME
-----------------
1 John
2 Joe
3 Peter
ID_PERSON ID_SPECIALIZATION
------------------------------
1 5
1 6
1 7
2 5
2 1
3 6
3 10
I need to filter data based on group of ids ID_SPECIALIZATION that will be provided. For example
I want to display only those persons who has specialization in 5 and 6 so it will return only first person. In ASP.NET Web form there will be two listboxes, left and right button, in first LB there will be all possible specializations and user will choose some of them to second LB as filtering options. I have no idea how to put this filtering condition in sql query. Thanks for help.
You could use the following:
SQL> SELECT p.id_person, p.NAME
2 FROM person p
3 JOIN person_spe s ON p.id_person = s.id_person
4 WHERE id_specialization IN (5, 6)
5 GROUP BY p.id_person, p.NAME
6 HAVING COUNT(*) = 2;
ID_PERSON NAME
---------- -----
1 John
One way to do it:
SELECT
ID_PERSON
, NAME
FROM
Person AS p
WHERE EXISTS
( SELECT *
FROM
PersonSpecialization AS ps
WHERE ps.ID_PERSON = p.ID_PERSON
AND ps.ID_SPECIALIZATION = 5
)
AND EXISTS
( SELECT *
FROM
PersonSpecialization AS ps
WHERE ps.ID_PERSON = p.ID_PERSON
AND ps.ID_SPECIALIZATION = 6
)
SELECT d1.id_person, d1.name FROM tbl_table1 d1
INNER JOIN tbl_table2 d1
ON d1.ID_PERSON=d2.ID_PERSON
WHERE ID_SPECILIZATION = ?
Theres the query but I'm not sure how asp.net works and passing in the value. It might be work looking up bind variables which allows you to use place holders in the sql which oracle then caches the query and just uses the values that you pass in at run tuime using EXECUTE IMMEDIATE.

generate 6 numbers between 1 and 2 in a 2:1 ratio in SQL or PL/SQL

how can i generate 6 numbers between 1 and 2 where 4 of the numbers will be 1 and the other 2 will be 2 in a random order i.e.
results
1
2
1
1
1
2
and also in a different ratio i.e. 3:2:1 for numbers between 1 and 3 for 12 numbers
i.e.
results
1
1
2
3
1
2
1
3
1
1
3
3
results don't have to be in this order but in the ratios as above in oracle SQL or PL/SQL
To get the ratios perfect you could do something like this - generate all the numbers, then sort in random order:
SELECT r
FROM (SELECT CASE
WHEN ROWNUM <=4 THEN 1
ELSE 2
END AS r
FROM DUAL
CONNECT BY LEVEL <= 6)
ORDER BY DBMS_RANDOM.value;
R
----------------------
2
1
1
2
1
1
I think this will work in straight SQL; it's horrifically inefficient, and a PL/SQL one might be less so. It's also completely static; differing ratios call for a different number of values selected.
select value
from (
select mod(value, 2) + 1 as value,
row_number() over (partition by
case mod(value, 2) = 1
then 1
else 0
end) as twos_row,
row_number() over (partition by
case mod(value, 2) = 0
then 1
else 0
end) as ones_row
from (select dbms_crypto.randominteger as value
from dba_objects
order by object_id
)
)
where twos_rows <= 2
or ones_rows <= 4
The inner-most select grabs a big stack of random numbers. The next query out determines whether that random value would be a 2 or a 1 by mod'ing the earlier random value. The last level of nesting just filters out all the rows after the correct number of that type of row has been returned.
This is untested and fragile. If you need a solution that's reliable and performance, I'd recommend PL/SQL, where you
loop
pick off random numbers
determine what partition in your set of values they'd fit into
keep them if that partition hasn't been satisfied
exit when all partitions have been satisfied.

Resources