SQL number of distinct values for the value in another column - sql

Let's say I have a table with user logins:
create table usr_logins
(
id int primary key,
logint_date date,
user_name text,
os_ver int
);
insert into usr_logins
values (1, '2018-12-23', 'Jack', 10)
,(2, '2018-12-24', 'Sam', 11)
,(3, '2018-12-24', 'Jack', 10)
,(4, '2018-12-24', 'Ann', 10)
,(5, '2018-12-25', 'Sam', 10)
,(6, '2019-12-26', 'Sam', 10)
I need to get a list of user names with a number of different OS versions used by them.
Note that only Sam has logins from os_ver 10 and 11.
This is what I need:

Since you need to get all usernames and corresponding distinct os_version count displayed (as os_num), you can try the following:
select user_name, count(DISTINCT os_ver) as os_num from usr_logins group by user_name

It's not clear if you are expecting only user Sam or not since you've highlighted in red, but if so you can do
select user_name, Count(distinct os_ver) os_num
from usr_logins
group by User_Name
having Count(distinct os_ver)>1

select user_name, count(distinct os_ver) osCount from usr_logins group by user_name having osCount > 1
Hope I understood you question correctly

Related

Query for value matching in multiple arrays

I have a table containing user experiences, table contains multiple records of same user
JSON example of data
{
user_id : 1,
location: 'india',
company_id: 5,
...other fields
}
{
user_id : 1,
location: 'united kingdom',
company_id: 6
...other fields
}
I want to run a query that gives me results of users who has worked in companies that satisfies IN condition of multiple arrays
E.g
Array1 of company Id: 1,2,4,5,6,7,8,10
Array2 of company Id: 2,6,50,100,12,4
The query should return users who have worked in one of the companies from both arrays, so IN condition of both the arrays should be satisfied
I tried the following query with no luck
select * from <table> where company_id IN(5,7,8) and company_id IN(1,4,3)
and 2 records of a user with company_id 5 and 4 exists in table
create table my_table (user_id int, company_id int);
insert into my_table (user_id, company_id)
values (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 5);
select user_id from my_table where company_id in (5, 7, 8)
intersect
select user_id from my_table where company_id in (1, 4, 3);
As you described, you need to get intersection of users, who are working in two sets of companies.

Get records from table with join where records in join not contain specific value

I have two tables:
Table user:
create table user (
id bigserial not null primary key,
username varchar(256),
active boolean not null default true
);
And table address:
create table address (
id bigserial not null primary key,
user_id integer not null,
country varchar(256),
city varchar(256),
street varchar(256)
);
And some data as example:
insert into user(id, username, active) values (1, 'john', true);
insert into user(id, username, active) values (2, 'alex', true);
insert into user(id, username, active) values (3, 'alice', true);
insert into user(id, username, active) values (4, 'tom', true);
insert into user(id, username, active) values (5, 'dave', true);
insert into address(id, user_id, country, city, street) values (1, 1, 'Germany', 'Berlin', '');
insert into address(id, user_id, country, city, street) values (2, 2, 'Germany', 'Berlin', '');
insert into address(id, user_id, country, city, street) values (3, 2, 'Great Britain', 'London', '');
insert into address(id, user_id, country, city, street) values (4, 3, 'France', 'Paris', '');
insert into address(id, user_id, country, city, street) values (5, 4, 'USA', 'New York', '');
insert into address(id, user_id, country, city, street) values (6, 5, 'South Korea', 'Seoul', '');
Every user can have several addresses. I need to get all users who doesn't have in their set of addresses address with specific country, for example 'Germany'.
What I tried:
select u.* from user u
left join address a on u.id=a.user_id where a.country is not like '%Germany%'
But it returns users, who have address with specific country but also have some other address, which country is different from the specific one, for example with the data used above this is alex, who has two addresses Germany and Great Britain:
id username active
--------------------
2 alex True
3 alice True
4 tom True
5 dave True
Any suggestions how can I do such query?
Your code checks whether each user has at least one address outside of Germany, while you want to ensure that they have none.
I would recommend not exists:
select c.*
from client c
where not exists (
select 1
from address a
where a.user_id = c.id and a.country = 'Germany'
)
This query would take advantage of an index on address(user_id, country).
Note that it is unclear whether your table is called user or client... I used the latter.
Note that this also returns clients that have no address at all. If that's not what you want, then an alternative uses aggregation:
select c.*
from client c
inner join address on a.user_id = c.id
group by c.id
having not bool_or(a.country = 'Germany')
This is the query:
select user_id from address where country = 'Germany'
that returns all the users that you want to filter out.
Use it with NOT IN:
select u.*
from user u
where id not in (select user_id from address where country = 'Germany')
See the demo.
Results:
> id | username | active
> -: | :------- | :-----
> 3 | alice | t
> 4 | tom | t
> 5 | dave | t

Query database for distinct values and aggregate data based on condition

I am trying to extract distinct items from a Postgres database pairing a column from a table with a column from another table based on a condition. Simplified version looks like this:
CREATE TABLE users
(
id SERIAL PRIMARY KEY,
name VARCHAR(255)
);
CREATE TABLE photos
(
id INT PRIMARY KEY,
user_id INTEGER REFERENCES users(id),
flag VARCHAR(255)
);
INSERT INTO users VALUES (1, 'Bob');
INSERT INTO users VALUES (2, 'Alice');
INSERT INTO users VALUES (3, 'John');
INSERT INTO photos VALUES (1001, 1, 'a');
INSERT INTO photos VALUES (1002, 1, 'b');
INSERT INTO photos VALUES (1003, 1, 'c');
INSERT INTO photos VALUES (1004, 2, 'a');
INSERT INTO photos VALUES (1004, 2, 'x');
What I need is to extract each user name, only once, and a flag value for each of them. The flag value should prioritize a specific one, let's say b. So, the result should look like:
Bob b
Alice a
Where Bob owns a photo having the b flag, while Alice does not and John has no photos. For Alice the output for the flag value is not important (a or x would be just as good) as long as she owns no photo flagged b.
The closest thing I found were some self-join queries where the flag value would have been aggregated using min() or max(), but I am looking for a particular value, which is not first, nor last. Moreover, I found out that you can define your own aggregate functions, but I wonder if there is an easier way of conditioning the query in order to obtain the required data.
Thank you!
Here is a method with aggregation:
select u.name,
coalesce(max(flag) filter (where flag = 'b'),
min(flag)
) as flag
from users u left join
photos p
on u.id = p.user_id
group by u.id, u.name;
That said, a more typical method would be a prioritization query. Perhaps:
select distinct on (u.id) u.name, p.flag
from users u left join
photos p
on u.id = p.user_id
order by u.id, (p.flag = 'b') desc;

Replace hard coded values with data from table

Currently, I have 3 affiliations hard-coded in a query. They serve as a heirarchy: 1 = Faculty, 2 = Staff, 3 = Student. If a user from the affiliations_tbl table has more than one affiliation (example: a Staff member who is also a Student), it will use their Staff affiliation since it is higher on the heirarchy that is defined with the partition by and decode().
SELECT x2.emplid,
scc_afl_code
FROM (SELECT x.emplid,
scc_afl_code,
row_number() over(partition BY x.emplid ORDER BY x.affil_order) r
FROM (SELECT t.emplid,
scc_afl_code,
DECODE(scc_afl_code,
'FACULTY',
1,
'STAFF',
2,
'STUDENT',
3,
999) affil_order
FROM affiliations_tbl t
WHERE t.scc_afl_code IN
(SELECT a.scc_afl_code
FROM affiliation_groups_tbl a
WHERE a.group = 'COLLEGE')) x) x2
WHERE x2.r = 1;
I have created a table that will store affiliation groups affiliation_groups_tbl so I can scale this by adding data to the table, rather than changing the hard-coded values in this query. Example: Instead of adding 'CONSULTANT', 4 to the decode() list, I would add it to the table, so I wouldn't have to modify the SQL.
scc_afl_code | group | group_name | sort_order
-------------+---------+------------+-----------
FACULTY | COLLEGE | Faculty | 1
STAFF | COLLEGE | Staff | 2
STUDENT | COLLEGE | Student | 3
I've already updated the latter half of the query to only select scc_afl_code that are in the COLLEGE_GROUP group. How can I properly update the first part of the query to use the table as a hierarchy?
Try a piece of code below instead decode in the select clause of your statement:
coalesce((
select g.sort_order
from affiliation_groups_tbl g
where g.scc_afl_code = t.scc_afl_code ), 999)
You can try like that
create table dictionary
(id number,
code varchar2(32),
name varchar2(32),
sort number);
insert into dictionary (id, code, name, sort) values (16, 'B', 'B name', 1);
insert into dictionary (id, code, name, sort) values (23, 'A', 'A name', 2);
insert into dictionary (id, code, name, sort) values (15, 'C', 'C name', 4);
insert into dictionary (id, code, name, sort) values (22, 'D', 'D name', 3);
select partition,
string,
decode(string, 'B', 1, 'A', 2, 'D', 3, 'C', 4, 999) decode,
row_number() over(partition by partition order by decode(string, 'B', 1, 'A', 2, 'D', 3, 'C', 4, 999)) ordering
from (select mod(level, 3) partition, chr(65 + mod(level, 5)) string
from dual
connect by level <= 8)
minus
-- Alternate --
select partition,
string,
nvl(t.sort, 999) nvl,
row_number() over(partition by partition order by nvl(t.sort, 999)) ordering
from (select mod(level, 3) partition, chr(65 + mod(level, 5)) string
from dual
connect by level <= 8) r
left join dictionary t
on t.code = r.string;

Unable to pivot data in SQL

I have an SQL query like
select name from customers where id in (1,2,3,4,5)
this will return me 5 rows.
What I am trying is
select Q1,Q2,Q3,Q4,Q5 from(select name from customers where id in (1,2,3,4,5)
)d pivot( max(name) for names in (Q1,Q2,Q3,Q4,Q5)
) piv;
to convert these 5 rows into 5 columns.
But I am getting null values in columns.
I don't know where am I doing wrong.
Any kind of help , suggestion would be appreciated.
Thanks
You can try in this way, you are pivoting the name and selecting the name under value also so please first make is sure if there is a value for that ids and retrieve the value for which you are trying pivoting:
create table #customers(id int, name varchar(50), fee int)
insert into #customers values
(1, 'Q1', 100),
(2, 'Q2', 200),
(3, 'Q3', 300),
(4, 'Q4', 400),
(5, 'Q5', 500),
(6, 'Q5', 600),
(7, 'Q5', 700)
select Q1,Q2,Q3,Q4,Q5
from(select name, fee from #customers where id in (1,2,3,4,5)
)d pivot(SUM(fee) for name in (Q1,Q2,Q3,Q4,Q5)
) piv;
OUTPUT:
Q1 Q2 Q3 Q4 Q5
100 200 300 400 500
What respective value you want to display under name, if you mention that then we can provide more exact solution for that. Hope you can change your code accordingly.