Postgresql select the two top towns - sql

Have row like:
user | town 1 | town 2 | town 3 | town 4 | town 5 | town 6|
The towns all have integer values where town 3 and town 4 have the largest number
I want to select the two top towns for the user so the end result should be:
user | town 3 | town 4 |

This is the properly normalized model:
create table users (
user_id serial primary key,
user_name varchar(100)
);
create table town (
town_id serial primary key,
town_int int
);
create table user_town (
town_id int references town (town_id),
user_id int references users (user_id),
primary key (user_id, town_id)
);
insert into users (user_name) values ('John');
insert into town (town_int) values (1),(2),(3),(4),(5),(6);
insert into user_town (user_id, town_id) values (1,1),(1,2),(1,3),(1,4),(1,5),(1,6);
How to query it:
select user_id, user_name, town_id, town_int
from
user_town
inner join
users using (user_id)
inner join
town using (town_id)
where user_id = 1
order by town_int desc
limit 2
;
user_id | user_name | town_id | town_int
---------+-----------+---------+----------
1 | John | 6 | 6
1 | John | 5 | 5

Somtimes we have just what we have, poor designed legacy DB for example. Then
WITH t AS (
SELECT 100 as userid, 11 as town1, 23 as town2, 77 as town3, 14 as town4, 15 as town5, 16 as town6
UNION ALL
SELECT 101 as userid, 21 as town1, 235 as town2, 177 as town3, 24 as town4, 25 as town5, 26 as town6
)
SELECT userid, max(r.town) as top1, min(r.town) as top2
FROM t
CROSS JOIN LATERAL(
SELECT town1 as town FROM t t2 WHERE t2.userid=t.userid
UNION ALL
SELECT town2 FROM t t2 WHERE t2.userid=t.userid
UNION ALL
SELECT town3 FROM t t2 WHERE t2.userid=t.userid
UNION ALL
SELECT town4 FROM t t2 WHERE t2.userid=t.userid
UNION ALL
SELECT town5 FROM t t2 WHERE t2.userid=t.userid
UNION ALL
SELECT town6 FROM t t2 WHERE t2.userid=t.userid
ORDER BY town DESC LIMIT 2) AS r
GROUP BY userid;

Related

Earliest timestamp with join before group

I have 3 tables users, levels, attempts in PostgreSQL. I need to select the earliest attempts by attempts.created_at for each user for each level and get sum of attempts.rate for each user.
CREATE TABLE IF NOT EXISTS users
(
id BIGSERIAL PRIMARY KEY,
nickname VARCHAR(255) UNIQUE
);
CREATE TABLE IF NOT EXISTS levels
(
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
);
CREATE TABLE IF NOT EXISTS attempts
(
id BIGSERIAL PRIMARY KEY,
rate INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL,
level_id BIGINT REFERENCES levels (id),
user_id BIGINT REFERENCES users (id)
);
For example attempts content
id | rate | created_at | level_id | user_id
------------------------------------------------------------
1 | 10 | 2022-10-21 16:53:13.818000 | 1 | 1
2 | 20 | 2022-10-21 11:53:13.818000 | 1 | 1
3 | 30 | 2022-10-21 14:53:13.818000 | 1 | 1
4 | 40 | 2022-10-21 10:53:13.818000 | 2 | 1 -- (nickname = 'Joe')
5 | 100 | 2022-11-21 10:53:13.818000 | 1 | 2 -- (nickname = 'Max')
For level 1 and user 1 earliest row with id = 2 for level 2 with id = 4, I need select
nickname | sum
-----------------
Max | 100
Joe | 60
As a result for user Max (user with id = 1) sum of the earliest attempts of all levels = 100. And order by sum descending.
Something like this but how to select only one earliest attempt for each level before summing:
select u.nickname, sum(a.rate) as sum
from attempts a
inner join users u on a.user_id = u.id
inner join levels l on l.id = a.level_id
-- on a.created_at is the earliest for level and user
group by u.id
order by sum desc
select user_id
,sum(rate)
from
(
select distinct on (level_id, user_id) *
from t
order by level_id, user_id, created_at
) t
group by user_id
user_id
sum
2
100
1
60
Fiddle

SQL: Multiple select statements in one query

I want to select information from three SQL tables within one query.
An example could be the following setup.
tblFriends
id | idmother | dayBirth
--------------------------
1 | 1 | 09/09/21
2 | 2 | 09/09/21
3 | 3 | 11/09/21
4 | 3 | 11/09/21
5 | 4 | 07/09/21
... | ... | ...
tblMothers
id | name
---------------
1 | Alice
2 | Samantha
3 | Veronica
4 | Maria
... | ...
tblIsAssignedParty
idMother | codeParty | price
------------------------------
1 | 231 | 15
2 | 645 | 28
3 | 164 | 33
... | ... | ...
I want to have a query that gives me the following:
dayBirth | weekDay | totalFriendsForParty | totalFriendsForPartyPercent | totalFriendsNoParty | totalFriendsNoPartyPercent
-----------------------------------------------------------------------------------------------------------------------------
07/09/21 | Tuesday | 0 | 0 | 1 | 0.??
09/09/21 | Thursday | 2 | 0.?? | 0 | 0
11/09/21 | Saturday | 2 | 0.?? | 0 | 0
Note:
dayBirth = simply the day of birth; I need the friends grouped by this date
weekDay = dayBirth name
totalFriendsForParty = friends who will be attending the party; we know if the mother has a party assigned
totalFriendsForPartyPercent = Percentatge of friends, of the total number of friends who will attend the parties
totalFriendsNoParty = friends who will not attend the party; we know if the mother does not have a party assigned
totalFriendsNoPartyPercent = Percentatge of friends, of the total number of friends who will not attend the parties
I need the number of friends based on whether their mothers are at a party or not. I tried to multiple select statements in Single query but the following code didn't work:
SELECT
(SELECT distinct dayBirth, TO_CHAR(dayBirth, 'DAY') from tblFriends) as firstSecondColumn,
(SELECT dayBirth, count(*) from tblFriends
where idMother IN (
SELECT f.idMother
from tblFriends f
left join tblIsAssignedParty iap
on f.idMother = iap.idMother
where iap.codeParty is not null)
group by dayBirth) as thirdColumn,
(SELECT TRUNC(count(*) / count(thirdColumn.id) , 2) from tblFriends) as quarterColumn,
(SELECT dayBirth, count(*) from tblFriends
where idMother IN (
SELECT f.idMother
from tblFriends f
left join tblIsAssignedParty iap
on f.idMother = iap.idMother
where iap.codeParty is not null)
group by dayBirth) as fifthColumn,
(SELECT TRUNC(count(*) / count(fifthColumn.id) , 2) from tblFriends) as sixthColumn,
order by dayBirth
Any advice on this one? I try to learn, I do what I can :-(
Edit: I can't add inserts because it's a file upload, but I can add an approximation of table creation.
Create tables:
CREATE TABLE tblFriends
(
id NUMBER(*,0),
idMother CHAR(10 CHAR),
CONSTRAINT PK_FRIEND PRIMARY KEY (id, idMother),
CONSTRAINT FK_IDMOTHER FOREIGN KEY (idMother)
REFERENCES tblMothers (id),
dayBirth DATE CONSTRAINT NN_DAY NOT NULL
)
CREATE TABLE tblMothers
(
id CHAR(10 CHAR) CONSTRAINT PK_MOTHER PRIMARY KEY (id),
name VARCHAR2(20 CHAR) CONSTRAINT NN_MNAME NOT NULL
)
CREATE TABLE tblIsAssignedParty
(
idMother CHAR(10 CHAR),
codeParty CHAR(10 CHAR),
CONSTRAINT PK_ASSIGNED PRIMARY KEY (idMother, codeParty),
CONSTRAINT FK_ASSIGNEDMOTHER FOREIGN KEY (idMother)
REFERENCES tblMothers (id),
CONSTRAINT FK_ASSIGNEDPARTY FOREIGN KEY (codeParty)
REFERENCES tblParties (codeParty),
price DECIMAL(10,2)
)
You appear to want to LEFT JOIN the firends and party tables and then use conditional aggregation:
SELECT dayBirth,
TO_CHAR(dayBirth, 'FMDAY', 'NLS_DATE_LANGUAGE=English') AS day,
COUNT(p.idmother)
AS totalFriendsForParty,
COUNT(p.idmother) / COUNT(*) * 100
AS totalFriendsForPartyPercent,
COUNT(CASE WHEN p.idmother IS NULL THEN 1 END) AS totalFriendsNoParty,
COUNT(CASE WHEN p.idmother IS NULL THEN 1 END) / COUNT(*) * 100
AS totalFriendsNoPartyPercent
FROM tblFriends f
LEFT OUTER JOIN tblIsAssignedParty p
ON (f.idmother = p.idmother)
GROUP BY dayBirth
Which, for the sample data:
CREATE TABLE tblFriends (id, idmother, dayBirth) AS
SELECT 1, 1, DATE '2021-09-09' FROM DUAL UNION ALL
SELECT 2, 2, DATE '2021-09-09' FROM DUAL UNION ALL
SELECT 3, 3, DATE '2021-09-11' FROM DUAL UNION ALL
SELECT 4, 3, DATE '2021-09-11' FROM DUAL UNION ALL
SELECT 5, 4, DATE '2021-09-07' FROM DUAL;
CREATE TABLE tblIsAssignedParty (idMother, codeParty, price) AS
SELECT 1, 231, 15 FROM DUAL UNION ALL
SELECT 2, 645, 28 FROM DUAL UNION ALL
SELECT 3, 164, 33 FROM DUAL;
Outputs:
DAYBIRTH
DAY
TOTALFRIENDSFORPARTY
TOTALFRIENDSFORPARTYPERCENT
TOTALFRIENDSNOPARTY
TOTALFRIENDSNOPARTYPERCENT
09-SEP-21
THURSDAY
2
100
0
0
11-SEP-21
SATURDAY
2
100
0
0
07-SEP-21
TUESDAY
0
0
1
100
db<>fiddle here

SQL Server - select every combination

We have two tables below, I am trying to write a query that will select EVERY Purchase for EVERY person on the team. For example, it should show PersonA being associated to PurchaseID 1 and 2 because they are on the same Team as TeamA.
Is this possible? I thought a cross join would work but it seemed to bring back too many columns. I am running SQL Server.
Thank you
Purchases
| PurchaseID | PersonID |
|------------ |---------- |
| 1 | TeamA |
| 2 | TeamA |
| 3 | PersonA |
| 4 | PersonB |
| 5 | TeamB |
Teams
| TeamID | PersonID |
|-------- |---------- |
| 1 | PersonA |
| 1 | TeamA |
| 1 | PersonC |
| 2 | PersonB |
| 2 | TeamB |
Expected results (when filtered on PurchaseID 1):
| PurchaseID | PersonID |
|------------ |---------- |
| 1 | TeamA |
| 1 | PersonA |
| 1 | PersonC |
Your data structure is a little odd, but I think I understand what you want.
If PersonA made a purchase, and PersonA is on TeamA, then everyone on TeamA should be shown as being associated with the purchase, right? Like "I bought these doughnuts for my team, so everyone on my team gets a doughnut".
What you're going to want is to join Purchase to Team on PersonID, as you probably guessed. But then use a CROSS APPLY function, which is in inline table value function, to return all the people on the same team as the person in the "current row".
I used two common table expressions to represent your tables so I could run it. You'll just want the SELECT part:
with Purchases as (
select 1 as PurchaseID, 'TeamA' as PersonID
union select 2 as PurchaseID, 'TeamA' as PersonID
union select 3 as PurchaseID, 'PersonA' as PersonID
union select 4 as PurchaseID, 'PersonB' as PersonID
union select 5 as PurchaseID, 'TeamB' as PersonID
)
, Teams as (
select 1 as TeamID, 'PersonA' as PersonID
union select 1 as TeamID, 'TeamA' as PersonID
union select 1 as TeamID, 'PersonC' as PersonID
union select 2 as TeamID, 'PersonB' as PersonID
union select 2 as TeamID, 'TeamB' as PersonID
)
select Purchases.PurchaseID
, EveryTeamMember.PersonID
from Purchases
join Teams
on Teams.PersonID = Purchases.PersonID
cross apply (
select PersonID
from Teams InnerTable
where InnerTable.TeamID = Teams.TeamID
) as EveryTeamMember
where Purchases.PurchaseID = 1
If you are looking ti get all Team persons when the PersonID starts with Team then i think you should do a CROSS APPLY over all PersonID who starts with Team and UNION (NOT UNION ALL) Single Person purchases:
DECLARE #Purchases TABLE (
PurchaseID INT,
PersonID Varchar(50)
)
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamA', 1);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamA', 2);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('PersonA', 3);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('PersonB', 4);
INSERT INTO #Purchases(PersonID,PurchaseID) VALUES ('TeamB', 5);
DECLARE #Teams TABLE (
TeamID INT,
PersonID Varchar(50)
)
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonA', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('TeamA', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonC', 1);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('PersonB', 2);
INSERT INTO #Teams(PersonID,TeamID) VALUES ('TeamB', 2);
SELECT T1.PurchaseID,TeamPersons.PersonID
FROM #Purchases T1
INNER JOIN #Teams T2
ON T2.PersonID = T1.PersonID AND T1.PersonID LIKE'Team%'
CROSS APPLY (
SELECT PersonID
FROM #Teams T3
WHERE T3.TeamID = T2.TeamID
) AS TeamPersons
UNION
SELECT T1.PurchaseID
, T1.PersonID
FROM #Purchases T1
WHERE T1.PersonID NOT LIKE 'Team%'
Result

select one row from nested table type

my question is there away to select only one record from that column type as default value.
create type t_tel as table of number;
create table users_tel(
user_id number,
user_name varchar2(100),
tel t_tel
) nested table tel store as tel_table;
insert into users_tel(user_id, user_name, tel) values (1, 'Amir', t_tel(987,654,321));
select * from users_tel;
Use a table collection expression to treat the collection in the nested table as if it was a table and join on that. Then you can filter to get one row per user_id:
SQL Fiddle
Oracle 11g R2 Schema Setup:
create type t_tel as table of number;
create table users_tel(
user_id number,
user_name varchar2(100),
tel t_tel
) nested table tel store as tel_table;
insert into users_tel(user_id, user_name, tel)
SELECT 1, 'Amir', t_tel(987,654,321) FROM DUAL UNION ALL
SELECT 2, 'Dave', t_tel(123,456) FROM DUAL UNION ALL
SELECT 3, 'Kevin', t_tel() FROM DUAL;
Query 1:
SELECT user_id,
user_name,
tel_no
FROM (
SELECT u.*,
t.column_value AS tel_no,
ROW_NUMBER() OVER ( PARTITION BY u.user_id ORDER BY ROWNUM ) AS rn
FROM users_tel u
LEFT OUTER JOIN
TABLE( u.tel ) t
ON ( 1 = 1 )
)
WHERE rn = 1
Results:
| USER_ID | USER_NAME | TEL_NO |
|---------|-----------|--------|
| 1 | Amir | 987 |
| 2 | Dave | 123 |
| 3 | Kevin | (null) |
You can use group by to do it:
select u.user_id, u.user_name, min(t.column_value) def_tel
from users_tel u
left join table(u.tel) t on 1=1
group by u.user_id, u.user_name;
Note that i am using left join with the table Type to show records where tel is null.

Displaying whole table after stripping characters in SQL Server

This question has 2 parts.
Part 1
I have a table "Groups":
group_ID person
-----------------------
1 Person 10
2 Person 11
3 Jack
4 Person 12
Note that not all data in the "person" column have the same format.
In SQL Server, I have used the following query to strip the "Person " characters out of the person column:
SELECT
REPLACE([person],'Person ','')
AS [person]
FROM Groups
I did not use UPDATE in the query above as I do not want to alter the data in the table.
The query returned this result:
person
------
10
11
12
However, I would like this result instead:
group_ID person
-------------------
1 10
2 11
3 Jack
4 12
What should be my query to achieve this result?
Part 2
I have another table "Details":
detail_ID group1 group2
-------------------------------
100 1 2
101 3 4
From the intended result in Part 1, where the numbers in the "person" column correspond to those in "group1" and "group2" of table "Details", how do I selectively convert the numbers in "person" to integers and join them with "Details"?
Note that all data under "person" in Part 1 are strings (nvarchar(100)).
Here is the intended query output:
detail_ID group1 group2
-------------------------------
100 10 11
101 Jack 12
Note that I do not wish to permanently alter anything in both tables and the intended output above is just a result of a SELECT query.
I don't think first part will be a problem here. Your query is working fine with your expected result.
Schema:
CREATE TABLE #Groups (group_ID INT, person VARCHAR(50));
INSERT INTO #Groups
SELECT 1,'Person 10'
UNION ALL
SELECT 2,'Person 11'
UNION ALL
SELECT 3,'Jack'
UNION ALL
SELECT 4,'Person 12';
CREATE TABLE #Details(detail_ID INT,group1 INT, group2 INT);
INSERT INTO #Details
SELECT 100, 1, 2
UNION ALL
SELECT 101, 3, 4 ;
Part 1:
For me your query is giving exactly what you are expecting
SELECT group_ID,REPLACE([person],'Person ','') AS person
FROM #Groups
+----------+--------+
| group_ID | person |
+----------+--------+
| 1 | 10 |
| 2 | 11 |
| 3 | Jack |
| 4 | 12 |
+----------+--------+
Part 2:
;WITH CTE AS(
SELECT group_ID
,REPLACE([person],'Person ','') AS person
FROM #Groups
)
SELECT D.detail_ID, G1.person, G2.person
FROM #Details D
INNER JOIN CTE G1 ON D.group1 = G1.group_ID
INNER JOIN CTE G2 ON D.group1 = G2.group_ID
Result:
+-----------+--------+--------+
| detail_ID | person | person |
+-----------+--------+--------+
| 100 | 10 | 10 |
| 101 | Jack | Jack |
+-----------+--------+--------+
Try following query, it should give you the desired output.
;WITH MT AS
(
SELECT
GroupId, REPLACE([person],'Person ','') Person
AS [person]
FROM Groups
)
SELECT Detail_Id , MT1.Person AS group1 , MT2.Person AS AS group2
FROM
Details D
INNER JOIN MT MT1 ON MT1.GroupId = D.group1
INNER JOIN MT MT2 ON MT2.GroupId= D.group2
The first query works
declare #T table (id int primary key, name varchar(10));
insert into #T values
(1, 'Person 10')
, (2, 'Person 11')
, (3, 'Jack')
, (4, 'Person 12');
declare #G table (id int primary key, grp1 int, grp2 int);
insert into #G values
(100, 1, 2)
, (101, 3, 4);
with cte as
( select t.id, t.name, ltrim(rtrim(replace(t.name, 'person', ''))) as sp
from #T t
)
-- select * from cte order by cte.id;
select g.id, c1.sp as grp1, c2.sp as grp2
from #G g
join cte c1
on c1.id = g.grp1
join cte c2
on c2.id = g.grp2
order
by g.id;
id grp1 grp2
----------- ----------- -----------
100 10 11
101 Jack 12