sql add columns in group dynamically - sql

It is necessary to build a summary table based on data about the customer and their payments, where the columns will be the sequential number of the contract (contact_number) and the year (year) grouped by gender. The main condition is that contact_number and year should be dynamically generated.
Test data:
CREATE TABLE loans
(
loan_id int,
client_id int,
loan_date date
);
CREATE TABLE clients
(
client_id int,
client_name varchar(20),
gender varchar(20)
);
INSERT INTO CLIENTS
VALUES (1, arnold, 'male'),
(2, lilly, 'female'),
(3, betty, 'female'),
(4, tom, 'male'),
(5, jim, 'male');
INSERT INTO loans
VALUES (1, 1, '20220522'),
(2, 2, '20220522'),
(3, 3, '20220525'),
(4, 4, '20220525'),
(5, 1, '20220527'),
(6, 2, '20220527'),
(7, 3, '20220601'),
(8, 1, '20220603'),
(9, 2, '20220603'),
(10, 1, '20220603');
Formation of columns can be done using the case when construct, but this option is not suitable due to the need to constantly add new lines in the query when adding data.
My code:
with cte as
(
select
l.client_id,
loan_date,
extract(year from loan_date) as year,
client_name,
gender,
row_number() over (partition by l.client_id order by loan_date asc) as serial_number_contact
from
loans l
inner join
client c on l.client_id = c.client_id
)
select
gender
, year
, contract_number
, count(*)
from cte
group by gender, year, contract_number
order by year, contract_number
expected Output :
sex
1 contract, 2022
2 contract, 2022
3 contract, 2022
male
2
2
1
female
4
1
1
RDMBS - postgres

Related

Select rows that contain a range of values while excluding values from other columns

Hitting a small wall with a query here. trying to see if transactions contain type 01 while excluding transactions that contain item 23 or 25.
here's a reprex.
In SQL fiddle
create table purchases (
transaction_id int,
item int,
type int,
customer char(1)
);
insert into purchases values (1, 23, 01, "A");
insert into purchases values (1, 25, 01, "A");
insert into purchases values (2, 23, 01, "B");
insert into purchases values (2, 25, 01, "B");
insert into purchases values (2, 1, 01, "B");
insert into purchases values (3, 3, 01, "A");
insert into purchases values (4, 23, 01,"B");
insert into purchases values (4, 25, 01,"B");
insert into purchases values (5, 23, 01,"A");
insert into purchases values (6, 4, 02,"C");
insert into purchases values (7, 9, 03,"C");
Here's the query to identify transactions that only have items 23 and 25 but nothing else, it works, (should be transactions, 1,4 & 5).
select transaction_id from purchases where item in (23,25)
and transaction_id not in (select transaction_id from purchases where item not in (23,25));
However, when I'm struggling to single out the transactions that have type 01 but not items 23 and 25.
I tried this, but it gives out transactions 2 & 3 when it should only be 3 since 2 does contain items 23 & 25.
here's the query I was going with, based on the first one.
select * from purchases where type = 1 and transaction_id not in (select transaction_id from purchases where item in (23,25)
and transaction_id not in (select transaction_id from purchases where item not in (23,25)));
expected result
transaction_id item type customer
3 3 01 A
Based on your updated question, i'd suggest you use the NOT EXISTS clause like below
select * from purchases p1 where not exists
(
select 1 from purchases p2 where p1.transaction_id=p2.transaction_id
and p2.item in (23,25))
and type=1
fiddle demo link
I see that you have already changed the expected result in the question several times (while the query itself does not change), so I'm not sure what exactly you want to get.
In any case, you can take this dbfiddle example, and using arrays, filtered by distinct sorted elements:
You want one row per transaction, so aggregate and GROUP BY transaction_id. Then use the HAVING clause and COUNT conditionally.
select transaction_id
from purchases
group by transaction_id
having count(*) filter (where item = 23) = 0
and count(*) filter (where item = 25) = 0
and count(*) filter (where type = 1) > 0
order by transaction_id;
Demo: https://dbfiddle.uk/?rdbms=postgres_14&fiddle=520755370f13d41ba35ca12e7eb5277e
If you want to show all rows matching above transaction IDs:
select * from purchases where transaction_id in ( <above query> );
Here is one option
select p.*
from purchases p
join (
select transaction_id
from purchases
group by transaction_id
having count(case when item in (25,23) then 1 end)=0
and count(case typ when 1 then 1 end)>0
)x
on p.transaction_id=x.transaction_id
For your sample data:
insert into purchases values (1, 23, 01, 'A');
insert into purchases values (1, 25, 01, 'A');
insert into purchases values (2, 23, 01, 'B');
insert into purchases values (2, 25, 01, 'B');
insert into purchases values (2, 1, 01, 'B');
insert into purchases values (3, 3, 01, 'A');
insert into purchases values (4, 23, 01,'B');
insert into purchases values (4, 25, 01,'B');
insert into purchases values (5, 23, 01,'A');
insert into purchases values (6, 4, 02,'C');
insert into purchases values (7, 9, 03,'C');
Result:
3 3 1 A

In Postgres SQL how to convert values as range and get the range with maximum records

Having a table of students with their name and age as follows how to convert values of age as a range of
7-9
9-11
11-13
13-15
15-17
17-19
and get the age range with maximum students
Creating table:
CREATE TABLE students (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
age FLOAT NOT NULL
);
Inserting values:
INSERT INTO students
VALUES
(1, 'Ryan', 12),
(2, 'Joanna', 12.5),
(3, 'James', 11),
(4, 'Karen', 10),
(5, 'Holmes', 11.2),
(6, 'Garry', 12.1),
(7, 'Justin', 14.5),
(8, 'Emma', 15),
(9, 'Andy', 10),
(10, 'Claren', 9.5),
(11, 'Dennis', 9),
(12, 'Henna', 16),
(13, 'Iwanka', 15.4),
(14, 'June', 8.1),
(15, 'Kamila', 7.5),
(16, 'Lance', 17);
Expected Output should be range with max count of records:
Range | Count
10-12 | 5
--construct numrange table.
CREATE TABLE age_range (
id serial,
agerange numrange
);
INSERT INTO age_range (agerange)
VALUES ('[7,9]'), ('[10,12]'), ('[13,15]'), ('[15,17]'), ('[17,19]');
--cte with window function.
WITH a AS (
SELECT
age,
name,
agerange
FROM
students s,
age_range b
WHERE
age <# agerange IS TRUE
)
SELECT
*,
count(agerange) OVER (PARTITION BY agerange)
FROM
a
ORDER BY
agerange,
name;
You can try to use an aggregate function with CASE WHEN expression for your logic, then use ORDER BY COUNT DESC to get max count of records
SELECT (CASE WHEN age BETWEEN 7 AND 9 THEN '7-9'
WHEN age BETWEEN 10 AND 12 THEN '10-12'
WHEN age BETWEEN 13 AND 15 THEN '13-15'
WHEN age BETWEEN 15 AND 17 THEN '15-17'
WHEN age BETWEEN 17 AND 19 THEN '17-19' END) as range,
COUNT(*) cnt
FROM students
GROUP BY CASE WHEN age BETWEEN 7 AND 9 THEN '7-9'
WHEN age BETWEEN 10 AND 12 THEN '10-12'
WHEN age BETWEEN 13 AND 15 THEN '13-15'
WHEN age BETWEEN 15 AND 17 THEN '15-17'
WHEN age BETWEEN 17 AND 19 THEN '17-19' END
ORDER BY COUNT(*) DESC
LIMIT 1
edit
if you range number has a logic and you want a generic range solution
you can try to use generate_series generate a range number with your range logic then do outer join.
For your sample data I would use generate_series(7,17,2) create a range number which you expect the calutaion start and end number
SELECT CONCAT(t1.startnum,'-',t1.endnum) as range,
COUNT(*) cnt
FROM students s
INNER JOIN (
SELECT v startnum,v+2 endnum
FROM generate_series(7,17,2) v
) t1 ON s.age BETWEEN t1.startnum AND t1.endnum
GROUP BY CONCAT(t1.startnum,'-',t1.endnum)
ORDER BY COUNT(*) DESC
LIMIT 1
sqlfiddle

Joining vertical and horizontal table

How can I do a join that takes the two tables and get the table in result. Having trouble thinking about it because one is a horizontal table and the other is a vertical table I believe. The other answers on SO are not clear to me because I have to join a value in a row with a column name. How can I do that?
CREATE TABLE forecast (
year integer,
week integer,
model varchar(50),
category varchar(50),
subcategory varchar(50)
);
insert into forecast (year, week, model, category, subcategory) values (2021, 1, 'AAA', 'CategoryA', 'SubcategoryA');
insert into forecast (year, week, model, category, subcategory) values (2021, 1, 'BBB', 'CategoryA', 'SubcategoryA');
insert into forecast (year, week, model, category, subcategory) values (2021, 1, 'CCC', 'CategoryB', 'SubcategoryB');
insert into forecast (year, week, model, category, subcategory) values (2021, 1, 'DDD', 'CategoryA', 'SubcategoryC');
CREATE TABLE translation (
type varchar(50),
name varchar(50),
translated varchar(50)
);
insert into translation (type, name, translated) values ('category', 'CategoryA', 'TranslatedCategoryA');
insert into translation (type, name, translated) values ('category', 'CategoryB', 'TranslatedCategoryB');
insert into translation (type, name, translated) values ('subcategory', 'SubcategoryA', 'TranslatedSubcategoryA');
insert into translation (type, name, translated) values ('subcategory', 'SubcategoryB', 'TranslatedSubcategoryB');
insert into translation (type, name, translated) values ('subcategory', 'SubcategoryC', 'TranslatedSubcategoryC');
CREATE TABLE result (
year integer,
week integer,
model varchar(50),
category varchar(50),
subcategory varchar(50)
);
insert into result (year, week, model, category, subcategory) values (2021, 1, 'AAA', 'TranslatedCategoryA', 'TranslatedSubcategoryA');
insert into result (year, week, model, category, subcategory) values (2021, 1, 'BBB', 'TranslatedCategoryA', 'TranslatedSubcategoryA');
insert into result (year, week, model, category, subcategory) values (2021, 1, 'CCC', 'TranslatedCategoryB', 'TranslatedSubcategoryB');
insert into result (year, week, model, category, subcategory) values (2021, 1, 'DDD', 'TranslatedCategoryA', 'TranslatedSubcategoryC');
This
select * from forecast f
left join translation t
on t.name = f.category or t.name = f.subcategory
translates one at a time which makes sense, but I can't get two columns out of it that translate each column
Double left join on the type and the name.
And a coalesce to default to the original name if there's no translation.
select f.year, f.week, f.model
, coalesce(cat.translated, f.category) as category
, coalesce(subcat.translated, f.subcategory) as subcategory
from forecast f
left join translation cat
on cat.name = f.category
and cat.type = 'category'
left join translation subcat
on subcat.name = f.subcategory
and subcat.type = 'subcategory'
order by f.year, f.week, f.model;
year
week
model
category
subcategory
2021
1
AAA
TranslatedCategoryA
TranslatedSubcategoryA
2021
1
BBB
TranslatedCategoryA
TranslatedSubcategoryA
2021
1
CCC
TranslatedCategoryB
TranslatedSubcategoryB
2021
1
DDD
TranslatedCategoryA
TranslatedSubcategoryC
db<>fiddle here
We have to do a multiple join. One for each column.
select f.year, f.week, f.model, t.translated as 'category', t2.translated as 'subcategory'
from forecast f
left join translation t
on t.name = f.category
left join translation t2
on t2.name = f.subcategory

Select TOP columns from table1, join table2 with their names

I have a TABLE1 with these two columns, storing departure and arrival identifiers from flights:
dep_id arr_id
1 2
6 2
6 2
6 2
6 2
3 2
3 2
3 2
3 4
3 4
3 6
3 6
and a TABLE2 with the respective IDs containing their ICAO codes:
id icao
1 LPPT
2 LPFR
3 LPMA
4 LPPR
5 LLGB
6 LEPA
7 LEMD
How can i select the top count of TABLE1 (most used departure id and most used arrival id) and group it with the respective ICAO code from TABLE2, so i can get from the provided example data:
most_arrivals most_departures
LPFR LPMA
It's simple to get ONE of them, but mixing two or more columns doesn't seem to work for me no matter what i try.
You can do it like this.
Create and populate tables.
CREATE TABLE dbo.Icao
(
id int NOT NULL PRIMARY KEY,
icao nchar(4) NOT NULL
);
CREATE TABLE dbo.Flight
(
dep_id int NOT NULL
FOREIGN KEY REFERENCES dbo.Icao(id),
arr_id int NOT NULL
FOREIGN KEY REFERENCES dbo.Icao(id)
);
INSERT INTO dbo.Icao (id, icao)
VALUES
(1, N'LPPT'),
(2, N'LPFR'),
(3, N'LPMA'),
(4, N'LPPR'),
(5, N'LLGB'),
(6, N'LEPA'),
(7, N'LEMD');
INSERT INTO dbo.Flight (dep_id, arr_id)
VALUES
(1, 2),
(6, 2),
(6, 2),
(6, 2),
(6, 2),
(3, 2),
(3, 2),
(3, 2),
(3, 4),
(3, 4),
(3, 6),
(3, 6);
Then do a SELECT using two subqueries.
SELECT
(SELECT TOP 1 I.icao
FROM dbo.Flight AS F
INNER JOIN dbo.Icao AS I
ON I.id = F.arr_id
GROUP BY I.icao
ORDER BY COUNT(*) DESC) AS 'most_arrivals',
(SELECT TOP 1 I.icao
FROM dbo.Flight AS F
INNER JOIN dbo.Icao AS I
ON I.id = F.dep_id
GROUP BY I.icao
ORDER BY COUNT(*) DESC) AS 'most_departures';
Click this button on the toolbar to include the actual execution plan, when you execute the query.
And this is the graphical execution plan for the query. Each icon represents an operation that will be performed by the SQL Server engine. The arrows represent data flows. The direction of flow is from right to left, so the result is the leftmost icon.
try this one:
select
(select name
from table2 where id = (
select top 1 arr_id
from table1
group by arr_id
order by count(*) desc)
) as most_arrivals,
(select name
from table2 where id = (
select top 1 dep_id
from table1
group by dep_id
order by count(*) desc)
) as most_departures

How do I sort results from a nested select while keeping the rollup on the last row?

How do I sort the results into the following example by the sellers name while keeping the rollup at the bottom?
Since the the grouping is applied to the nested SELECT I can't use ORDER BY and since the grouping isn't applied at the top level I can't use the GROUPING either.
Click here to see the working example in SQL Fiddle.
CREATE TABLE Sales
(
SellerID INT
, StoreID INT
, Price MONEY
);
CREATE TABLE Sellers
(
SellerID INT
, Name VARCHAR(50)
)
INSERT INTO Sales VALUES
(1, 1, 100),
(1, 1, 100),
(1, 1, 100),
(2, 2, 200),
(2, 2, 200),
(3, 2, 250),
(3, 2, 250),
(3, 2, 250),
(3, 2, 250);
INSERT INTO Sellers VALUES
(1, 'C. Thirdplace'),
(2, 'A. Firstplace'),
(3, 'B. Secondplace');
SELECT s.Name AS Seller_Name
, x.TotalSales AS Total_Sales
FROM
(
SELECT s.SellerID AS SellerID
, SUM(s.Price) AS TotalSales
FROM Sales s
GROUP BY s.SellerID
WITH ROLLUP
) x
LEFT JOIN Sellers s
ON s.SellerID = x.SellerID;
Which produces the following result:
SELLER_NAME TOTAL_SALES
--------------- -----------
C. Thirdplace 300
A. Firstplace 400
B. Secondplace 1000
(null) 1700
ORDER BY
CASE WHEN seller_name IS NULL THEN 1 ELSE 0 END,
seller_name