sqlite nested query with division - sql

I'm a bit new to SQL and am wondering the best way to do this. basically one query returns the denominator, and the outer query needs to return the numerator/denominator as a percent. the same tables are essentially used for each statement.
create table games(
id integer NOT NULL,
name TEXT NOT NULL,
category TEXT NOT NULL
);
create table game_sets(
id integer NOT NULL,
name TEXT NOT NULL,
theme_id integer NOT NULL
);
INSERT INTO games (id, name, category)
VALUES (1, "star wars", "top game"),
(2, "pokemon", "top game"),
(3, "zelda", "top game"),
(4, "crazy cats", "sucky game");
INSERT INTO game_sets(id, name, theme_id)
VALUES (1, "star wars set 1", 1),
(2, "star wars set 2", 1),
(3, "star wars set 3", 1),
(4, "pikachu set 1", 2),
(5, "narf set 1", 4),
(6, "narf set 2", 4),
(7, "narf set 1", 4),
(8, "narf set 1", 4),
(9, "narf set 1", 4),
(10, "narf set 1", 4);
CREATE VIEW top_games AS
SELECT id, name
FROM games
WHERE category ='top game';
--i hard coded 200 below, but it needs to be dynamic
select top_games.name as theme, printf("%.2f", cast(count(game_sets.name)as float)/200) as num_sets_percent from top_games
join game_sets
where top_games.id = game_sets.theme_id
group by top_games.id
order by num_sets desc
limit 2;
--below here is the number i need for the first query to divide
--i have it hard coded as 4 b/c 4 total sets in the game_sets table, but it needs to be dynamic with this query
(select count(game_sets.name) as num_sets from game_sets
join top_games
where top_games.id = game_sets.theme_id) as divide_by_this
output:
star wars, .3 (because 3 star wars sets out of 10 total sets and star wars is a top game)
pokemon, 0.1 (because 1 pokemon set out of 10 total sets and is also a top set)
and last we limited it to only the 2 top sets so the zelda set doesn't show up.

If you have SQLite 3.25.0+ you can use window functions:
select distinct
g.name,
1.0 * count(g.id) over (partition by g.id) / count() over () num_sets_percent
from game_sets s left join top_games g
on s.theme_id = g.id
order by num_sets_percent desc
limit 2
See the demo.
Results:
| name | num_sets_percent |
| --------- | ---------------- |
| star wars | 0.3 |
| pokemon | 0.1 |

Related

Use select in group by statement in Firebird

I'm using a Firebird database which has the following tables:
ARTICULOS
ProductId
longSKU
1
A22121000125
2
A22121000138
3
A22123001508
4
A22124002001
TALLESPORARTICULOS
ProductId
position
Sizes
1
1
Small
1
2
Medium
1
3
Large
1
4
Xtra Large
1
5
XXtra Large
2
1
Small
2
2
Medium
2
3
Large
2
4
Xtra Large
2
5
XXtra Large
3
1
02
3
2
04
3
3
06
3
4
08
and
RANGOSTALLE
ProductId
FromPosition
ToPosition
Price
1
1
3
500
1
4
5
600
2
1
3
500
2
4
5
600
3
1
4
200
I want to be able to group by a substring (shortSKU) of the longSKU and be able to get for each shortSKU the corresponding ranges and prices.
like this example:
ShortSKU
SizeFrom
SizeTo
Price
A221210001
small
large
500
A221210001
xtra large
xxtra large
600
A221230015
02
08
200
I'm using the following cobe but I get the error:
Dynamic SQL Error.
SQL error code = -104.
Invalid expression in the select list (not contained in either an aggregate function or the >GROUP BY clause).
CREATE OR ALTER VIEW RANGOSPARACOSTOSYPRECIOS(
SHORTSKU,
SIZEFROM,
SIZETO,
PRICE ) AS select substring(ar.codigoparticular from 1 for 10) AS SHORTSKU,
( Select TAL.SIZE
From tallesporarticulos TAL
Where TAL.productid=Ar.productid
and TAL.position= RT.FromPosition) as SIZEFROM,
( Select TAL.SIZE
From tallesporarticulos TAL
Where TAL.productid=Ar.productid
and TAL.position= RT.ToPosition) as SIZETO,
max(RT.PRICE)
from Articulos Ar
Inner Join tallesporarticulos TA On Ar.productId = TA.productId
Inner Join rangostalle RT On AR.productId = RT.productId
GROUP BY SHORTSKU, SIZEFROM, SIZETO ;
The following code works, but I need to replace the "fromposition" and "ToPosition" values with the size value like the code above, and that's when I get the error message.
CREATE OR ALTER VIEW RANGOSPARACOSTOSYPRECIOS(
SHORTSKU,
SIZEFROM,
SIZETO,
PRICE ) AS select substring(ar.codigoparticular from 1 for 10) AS SHORTSKU,
RT.FromPosition as SIZEFROM,
RT.ToPosition as SIZETO,
max(RT.PRICE)
from Articulos Ar
Inner Join tallesporarticulos TA On Ar.productId = TA.productId
Inner Join rangostalle RT On AR.productId = RT.productId
GROUP BY SHORTSKU, SIZEFROM, SIZETO ;
For anyone interested in helping, here you have the insert data from the tables above.
CREATE TABLE articulos (
ProductId INTEGER PRIMARY KEY,
LongSKU varchar(12) NOT NULL
);
INSERT INTO articulos VALUES (1, 'A22121000125');
INSERT INTO articulos VALUES (2, 'A22121000138');
INSERT INTO articulos VALUES (3, 'A22123001508');
INSERT INTO articulos VALUES (4, 'A22124002001');
CREATE TABLE TALLESPORARTICULOS (
ProductId INTEGER NOT NULL,
Position INTEGER NOT NULL,
Sizes varchar(12) NOT NULL
);
INSERT INTO TALLESPORARTICULOS (ProductId, position, Sizes) VALUES
(1, 1, 'SMALL'),
(1, 2, 'MEDIUM'),
(1, 3, 'LARGE'),
(1, 4, 'XTRALARGE'),
(1, 1, 'XXTRALARGE'),
(2, 2, 'SMALL'),
(2, 3, 'MEDIUM'),
(2, 4, 'LARGE'),
(2, 5, 'XTRALARGE'),
(2, 5, 'XXTRALARGE'),
(3, 1, '02'),
(3, 2, '03'),
(3, 3, '04'),
(3, 4, '05');
CREATE TABLE RANGOSTALLE (
ProductId INTEGER NOT NULL,
FromPosition INTEGER NOT NULL,
ToPosition INTEGER NOT NULL,
Price double not null
);
INSERT INTO RANGOSTALLE (ProductId,FromPosition,ToPosition,Price) VALUES
(1, 1,3,500),
(1, 4,5,600),
(2, 1,3,500),
(2, 4,5,600),
(3, 1,4,200);
Your script contains quite a few errors. After fixing them the query is rather trivial:
select substring(LongSKU from 1 for 10), low.sizes, high.sizes, avg(price)
from articulos join RANGOSTALLE on articulos.ProductId = RANGOSTALLE.ProductId
join TALLESPORARTICULOS low on RANGOSTALLE.ProductId = low.ProductId and RANGOSTALLE.FromPosition = low.Prodposition
join TALLESPORARTICULOS high on RANGOSTALLE.ProductId = high.ProductId and RANGOSTALLE.ToPosition = high.Prodposition
group by 1,2,3
https://dbfiddle.uk/?rdbms=firebird_3.0&fiddle=ae54a7d897da4604396775e3ddc4b764
This query can be optimized by moving grouping into a derived table but such optimization highly depends on the real table structure and query requirements.

Summing Results in a Table for Repeated Values

I'm currently in a tricky situation that I have been unable to figure out, and I was hoping you all might be able to help me solve my issue below:
I have a data set that includes a large amount of columns, however I am only going to show the columns pertinent to my issue (and I renamed them and put them in an excel doc).
What I am trying to do is develop a SQL query to calculate the total amount of PASS results and then the amount of FAIL Results for a given House Name. Each Result corresponds with a specific Resident ID and each Resident ID corresponds with a specific House Name/House ID. Unfortunately, the value Room ID needs to be in this data set, and each unique Room ID also corresponds with a specific House Name/House ID. Therefore, for every unique Room ID that exists for a given House Name, the Resident ID is being repeated.
For Example, if there are 7 Room IDs associated with a specific House Name/House ID, each unique Resident ID associated with that specific House Name/House ID will be repeated 7 times, once for every unique Room ID. Therefore, the Results are also all repeated 7 times. I have attached an example of what the data looks like below.
Note: Not all the data is included here. There are a few more rows to the AAAAAA data not shown, and there are a number of other House Names/House IDs.
Any thoughts would be much appreciated!
What you are looking for is GROUP BY.
Without looking at your data it is hard to come up with the exact query but i have created some test data.
create table House (HouseId int, HouseName nvarchar(max));
insert into House (HouseId, HouseName) values (1,'House A'), (2, 'House B'), (3,'House C');
create table Room (RoomId int, RoomName nvarchar(max), HouseId int);
insert into Room (RoomId, RoomName, HouseId)
values
(1,'Room 1 in house A', 1), (2,'Room 2 in house A', 1),
(3,'Room 3 in house B', 2),(4,'Room 4 in house B', 2),
(5,'Room 5 in house C', 3),(6,'Room 6 in house C', 3)
create table Resident (ResidentId int, ResidentName nvarchar(max), RoomId int, Result int);
insert into Resident (ResidentId, ResidentName, RoomId, Result)
values
-- House A = 4 passed, 0 failed
(1, 'Resident 1 in Room 1', 1, 82), (2, 'Resident 2 in Room 1', 1, 76),
(3, 'Resident 3 in Room 2', 2, 91), (4, 'Resident 4 in Room 2', 2, 67),
-- House B = 2 passed, 2 failed
(5, 'Resident 5 in Room 3', 3, 60), (6, 'Resident 6 in Room 3', 3, 64),
(7, 'Resident 7 in Room 4', 4, 28), (8, 'Resident 8 in Room 4', 4, 42),
-- House C = 3 passed, 1 failed
(9, 'Resident 9 in Room 5', 5, 99), (10, 'Resident 10 in Room 5', 5, 57),
(9, 'Resident 11 in Room 6', 6, 75), (10, 'Resident 12 in Room 6', 6, 38)
Then your query would look something like:
select
HouseName,
[Passed] = SUM(x.Passed),
[Failed] = SUM(x.Failed)
from
Resident re
outer apply (
--// Logic to determine if they passed or failed
--// I arbitrarily chose the number 50 to be the threshold
select [Passed] = case when re.Result >= 50 then 1 else 0 end,
[Failed] = case when re.Result < 50 then 1 else 0 end
) x
inner join Room r on r.RoomId = re.RoomId
inner join House h on h.HouseId = r.HouseId
group by
h.HouseName
here is a fiddle: http://sqlfiddle.com/#!18/30894/1

Retrieve a variable number of subsets with different conditions

I have the following database:
CREATE TABLE IF NOT EXISTS city (
id serial primary key,
name character varying UNIQUE NOT NULL
);
CREATE TABLE IF NOT EXISTS inhabitants (
id serial primary key,
fullname character varying UNIQUE NOT NULL,
home integer REFERENCES city
);
INSERT INTO city (name) VALUES
('michigan'),
('washington'),
('new york'),
('london'),
('los angeles')
ON CONFLICT DO NOTHING;
INSERT INTO inhabitants (fullname, home) VALUES
('flannigan, amy', 1),
('hannigan, leon', 1),
('shennanigan, frank', 1),
('catcher, floyd', 2),
('rice, amy', 2),
('black, joe', 2),
('higgins, simon', 3),
('stewart, rick', 3),
('white, frank', 3),
('henson, ben', 5),
('hedge, tim', 5),
('wilson, bill', 5),
('moriarty, doc', 4),
('fletcher, dolores', 4),
('fletcher, hank', 4),
('williamson, ann', 1),
('stewart, mary', 3)
ON CONFLICT DO NOTHING;
I want to extract a varying number of subsets with a varying number of inhabitants for each subset. Currently I am using a query for each subset, e.g., if I need two subsets I may use these two queries:
select fullname, home from inhabitants i
where home = (SELECT id FROM city WHERE name = 'michigan')
ORDER BY random() LIMIT 2;
and
select fullname, home from inhabitants i
where home = (SELECT id FROM city WHERE name = 'london')
ORDER BY random() LIMIT 1;
The results may look like this:
fullname | home
-----------------+------
hannigan, leon | 1
williamson, ann | 1
(2 rows)
and
fullname | home
-------------------+------
fletcher, dolores | 4
(1 row)
I join those two results in Bash, so they look what I actually want:
fullname | home
-------------------+------
hannigan, leon | 1
williamson, ann | 1
fletcher, dolores | 4
(3 rows)
I would like to minimize the number of database calls.
Is there a way to do this with one query (or function) or at least a better way than what I am doing currently?
Use window functions:
select fullname, home
from (select i.*,
row_number() over (partition by home order by random()) as seqnum
from inhabitants i
) i join
city c
on c.id = i.home
where (name = 'michigan' and seqnum <= 2) or
(name = 'london' and seqnum <= 1)

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

Querying based on a set of Named Attributes/Values

I am working with a set of what is essentially Attribute/Value pairs (there's actually quite a bit more to this, but I'm simplifying for the sake of this question). Effectively you can think of the tables as such:
Entities (EntityID,AttributeName,AttributeValue) PK=EntityID,AttributeName
Targets (TargetID,AttributeName,AttributeValue) PK=TargetID,AttributeName
How would you query with SQL the set of EntityID,TargetID for which an Entity has all the attributes for a target as well as the corresponding value?
EDIT (DDL as requested):
CREATE TABLE Entities(
EntityID INTEGER NOT NULL,
AttributeName CHAR(50) NOT NULL,
AttributeValue CHAR(50) NOT NULL,
CONSTRAINT EntitiesPK PRIMARY KEY (EntityID,AttributeName)
);
CREATE TABLE Targets(
TargetID INTEGER NOT NULL,
AttributeName CHAR(50) NOT NULL,
AttributeValue CHAR(50) NOT NULL,
CONSTRAINT TargetsPK PRIMARY KEY (TargetID,AttributeName)
);
Okay, I think after several tries and edits, this solution finally works:
SELECT e1.EntityID, t1.TargetID
FROM Entities e1
JOIN Entities e2 ON (e1.EntityID = e2.EntityID)
CROSS JOIN Targets t1
LEFT OUTER JOIN Targets t2 ON (t1.TargetID = t2.TargetID
AND e2.AttributeName = t2.AttributeName
AND e2.AttributeValue = t2.AttributeValue)
GROUP BY e1.EntityID, t1.TargetID
HAVING COUNT(e2.AttributeValue) = COUNT(t2.AttributeValue);
Test data:
INSERT INTO Entities VALUES
-- exact same attributes, should match
(1, 'Foo1', '123'),
(1, 'Bar1', '123'),
-- same attributes but different values, should not match
(2, 'Foo2', '456'),
(2, 'Bar2', '456'),
-- more columns in Entities, should not match
(3, 'Foo3', '789'),
(3, 'Bar3', '789'),
(3, 'Baz3', '789'),
-- fewer columns in Entities, should match
(4, 'Foo4', '012'),
(4, 'Bar4', '012'),
-- same as case 1, should match Target 1
(5, 'Foo1', '123'),
(5, 'Bar1', '123'),
-- one attribute with different value, should not match
(6, 'A', 'one'),
(6, 'B', 'two');
INSERT INTO Targets VALUES
(1, 'Foo1', '123'),
(1, 'Bar1', '123'),
(2, 'Foo2', 'abc'),
(2, 'Bar2', 'abc'),
(3, 'Foo3', '789'),
(3, 'Bar3', '789'),
(4, 'Foo4', '012'),
(4, 'Bar4', '012'),
(4, 'Baz4', '012'),
(6, 'A', 'one'),
(6, 'B', 'twox');
Test results:
+----------+----------+
| EntityID | TargetID |
+----------+----------+
| 1 | 1 |
| 4 | 4 |
| 5 | 1 |
+----------+----------+
To respond to your comment, here is a query with the tables reversed:
SELECT e1.EntityID, t1.TargetID
FROM Targets t1
JOIN Targets t2 ON (t1.TargetID = t2.TargetID)
CROSS JOIN Entities e1
LEFT OUTER JOIN Entities e2 ON (e1.EntityID = e2.EntityID
AND t2.AttributeName = e2.AttributeName
AND t2.AttributeValue = e2.AttributeValue)
GROUP BY e1.EntityID, t1.TargetID
HAVING COUNT(e2.AttributeValue) = COUNT(t2.AttributeValue);
And here's the output, given the same input data above.
+----------+----------+
| EntityID | TargetID |
+----------+----------+
| 1 | 1 |
| 3 | 3 |
| 5 | 1 |
+----------+----------+
I like these kind of questions but I think it is not unreasonable to hope that the OP provides at least create scripts for the table(s) and maybe even some sample data.
I like to hear who agrees and who disagrees.
SELECT *
FROM (
SELECT eo.total,
(
SELECT COUNT(*)
FROM Entities e, Targets t
WHERE e.EntityID = eo.EntityID
AND t.TargetID = e.EntityID
AND t.AttributeName = e.AttributeName
AND t.AttributeValue = e.AttributeValue
) AS equal
FROM (
SELECT e.EntityID, COUNT(*) as total
FROM Entities e
GROUP BY
e.EntityID
) eo
)
WHERE total = equal
select distinct entityid,targetid
from entities ent
, targets tar
where not exists
( select attributename, AttributeValue
from targets tar2
where tar.targetid = tar2.targetid
minus
select attributename, AttributeValue
from entities ent2
where ent2.entityid = ent.entityid)
and not exists
( select attributename, AttributeValue
from entities ent2
where ent2.entityid = ent.entityid
minus
select attributename, AttributeValue
from targets tar2
where tar.targetid = tar2.targetid)
order by entityid,targetid
/
edit1:
If it is OK to have rows in the target table that have no match in the entities table, the solution simplifies to:
select distinct entityid,targetid
from entities ent
, targets tar
where not exists
( select attributename, AttributeValue
from entities ent2
where ent2.entityid = ent.entityid
minus
select attributename, AttributeValue
from targets tar2
where tar.targetid = tar2.targetid)
order by entityid,targetid
/
edit 2:
It is not easy to understand the exact requirements of the OP.
Here is a new select statement. I hope he will test all my select statements to understand the differences. I hope he has good test cases and knows what he wants.
select distinct entityid,targetid
from entities ent
, targets tar
where not exists
( select attributename, AttributeValue
from targets tar2
where tar.targetid = tar2.targetid
minus
select attributename, AttributeValue
from entities ent2
where ent2.entityid = ent.entityid)
order by entityid,targetid
/