nested select or join query? - sql

i am new to sql so this may have a very basic answer but the question to answer is as follows .....
which film has taken the least takings at a performance? include film name and cinema name in the result.?
film name and cinema name are in two different table film and cinema. takings are in the performance table. i cant figure out how to complete this query. this is what i have got so far but it comes with an error in line 3 column 7.
select cinema_no,film_no
from CINEMA, film
where takings ( select min(takings)
from performance);

Because of multiple tags with oracle, I ignored the tag mysql ( of one which you should get rid of. e.g. please decide which DBMS are you using, by the way I already removed the irrelevant one oracle-sqldeveloper ).
It seems you need such a select statement ( prefer using modern ANSI-92 JOIN syntax, easily maintained and understandable ) with ordering by descending sum and contribution of row_number function as :
SELECT Name, Sum_Takings
FROM
(
SELECT f.Name, sum(p.Takings) Sum_Takings,
row_number() over (ORDER BY sum(p.Takings)) as rn
FROM Film f
LEFT JOIN Cinema c ON f.Cinema_ID = c.ID
LEFT JOIN Performance p ON f.ID = p.id_film
GROUP BY f.Name
)
WHERE rn = 1;
with added DDL statement as in the following :
SQL> CREATE TABLE Cinema (
2 ID integer PRIMARY KEY NOT NULL,
3 Title varchar2(100) NOT NULL
4 );
Table created
SQL> CREATE TABLE Film (
2 ID integer PRIMARY KEY NOT NULL,
3 Name varchar2(100) NOT NULL,
4 Cinema_ID integer
5 CONSTRAINT fk_Cinema_ID REFERENCES Cinema(ID)
6 );
Table created
SQL> CREATE TABLE Performance (
2 ID integer PRIMARY KEY NOT NULL,
3 ID_Film integer
4 CONSTRAINT fk_Film_ID REFERENCES Film(ID),
5 Takings integer
6 );
Table created
SQL> INSERT ALL
2 INTO Cinema(ID,Title) VALUES(1,'NiteHawk')
3 INTO Cinema(ID,Title) VALUES(2,'Symphony Space')
4 INTO Cinema(ID,Title) VALUES(3,'The Ziegfeld')
5 INTO Cinema(ID,Title) VALUES(4,'Cinema Village')
6 SELECT * FROM dual;
4 rows inserted
SQL> INSERT ALL
2 INTO Film(ID,Name,Cinema_ID) VALUES(1,'Citizen Kane',1)
3 INTO Film(ID,Name,Cinema_ID) VALUES(2,'Titanic',2)
4 INTO Film(ID,Name,Cinema_ID) VALUES(3,'Brave Heart',4)
5 INTO Film(ID,Name,Cinema_ID) VALUES(4,'Dumb and Dummer',3)
6 INTO Film(ID,Name,Cinema_ID) VALUES(5,'How To Train Your Dragon',2)
7 INTO Film(ID,Name,Cinema_ID) VALUES(6,'Beetle Juice',3)
8 SELECT * FROM dual;
6 rows inserted
SQL> INSERT ALL
2 INTO Performance VALUES(1,1,15)
3 INTO Performance VALUES(2,1,4)
4 INTO Performance VALUES(3,2,10)
5 INTO Performance VALUES(4,3,1)
6 INTO Performance VALUES(5,4,5)
7 INTO Performance VALUES(6,3,3)
8 INTO Performance VALUES(7,2,7)
9 INTO Performance VALUES(8,5,7)
10 INTO Performance VALUES(9,6,6)
11 SELECT * FROM dual;
9 rows inserted
SQL> commit;
Commit complete
SQL> SELECT Name, Sum_Takings
2 FROM
3 (
4 SELECT f.Name, sum(p.Takings) Sum_Takings,
5 row_number() over (ORDER BY sum(p.Takings)) as rn
6 FROM Film f
7 LEFT JOIN Cinema c ON f.Cinema_ID = c.ID
8 LEFT JOIN Performance p ON f.ID = p.id_film
9 GROUP BY f.Name
10 )
11 WHERE rn = 1
12 ;
NAME SUM_TAKINGS
--------------------------------------------------------------------- -----------
Brave Heart 4
dbfiddle.uk demo

As it involves grouping of Performance table you can achieve it by using two queries one to get the cinemaID and filmID of minimum performance takings and the another one to get the cinema name and filmname using the collected ids
SELECT CinemaID,FilmID from Performance where
PerformanceTaking=(Select Min(PerformanceTaking) from Performance);
SELECT CinemaName,FilmName from Cinema,Film where CinemaID=1 and
FilmID=1;

this will work:
select * from (select c.cinema_name,f.film_name
from cinema c, film f,performance p,
rank() over (partition by c.cinema order by sum(p.takings)) as rank
where
c.ID=f.cinema_id and
f,id=p.id
group by f.film_name) where rank=1
;

Related

How to unescape % in LIKE clause

I have my search patterns stored in database in patterns table. For example my table column name_pattern contains string 'Basic%'. I'd like to create dynamic search where search patterns will be fetched from name_pattern column.
So my SQL query should look something like:
SELECT *
FROM products
WHERE product_name LIKE name_pattern <-- somehow joined from patterns table
Seems that Oracle escapes % in my string but I want to take it unescapped in order my query to work like:
SELECT *
FROM products
WHERE product_name LIKE 'Basic%'
I found that my problem is with stable set of rows:
CREATE TABLE patterns(code CHAR(1),name_pattern VARCHAR2(20));
INSERT INTO patterns(code,name_pattern) VALUES('B','Basic%');
INSERT INTO patterns(code,name_pattern) VALUES('T','%thing');
CREATE TABLE products (id NUMBER,name VARCHAR2(20),code CHAR(1));
INSERT INTO products(id,name,found) VALUES(1,'Basic instinct',NULL);
INSERT INTO products(id,name,found) VALUES(2,'Basic thing',NULL);
INSERT INTO products(id,name,found) VALUES(3,'Super thing',NULL);
INSERT INTO products(id,name,found) VALUES(4,'Hyper instinct',NULL);
MERGE INTO products p USING
(
SELECT code,name_pattern FROM patterns
) s
ON (p.name LIKE s.name_pattern)
WHEN MATCHED THEN UPDATE SET p.code=s.code;
SELECT * FROM products;
If my search patterns were Basic% and Super% in patterns table then this MERGE will work, but if my search patterns are Basic% and %thing, the second product should be marked with both codes 'B' and 'T' and that causes the error:
ORA-30926: unable to get a stable set of rows in the source tables
So my problem is not in (un)escaping :-(, sorry
You don't have to (un)escape anything, I'd say.
SQL> with
2 patterns (name_pattern) as
3 (select 'Basic%' from dual union all
4 select '%foot%' from dual
5 ),
6 products (id, name) as
7 (select 1, 'Basic instinct' from dual union all
8 select 2, 'Visual Basic' from dual union all
9 select 3, 'Littlefoot' from dual union all
10 select 4, 'Happy feet' from dual
11 )
12 select b.id, b.name, a.name_pattern
13 from products b join patterns a on b.name like a.name_pattern;
ID NAME NAME_P
---------- -------------- ------
1 Basic instinct Basic%
3 Littlefoot %foot%
SQL>
Based on test case you provided: don't merge, update!
SQL> update products p set
2 p.found = 1
3 where exists (select null
4 from patterns o
5 where p.name like o.name_pattern
6 );
3 rows updated.
SQL> select * from products;
ID NAME FOUND
---------- -------------------- ----------
1 Basic instinct 1
2 Basic thing 1
3 Super thing 1
4 Hyper instinct 0
SQL>
After you changed your mind (again), it is still update. Though, you didn't explain which code you want to take when there's multiple match (for example, product 2 matches both "Basic%" and "%thing") so I took any of them, using the min function.
SQL> update products p set
2 p.code = (select min(o.code)
3 from patterns o
4 where p.name like o.name_pattern
5 );
4 rows updated.
SQL> select * from products;
ID NAME CODE
---------- -------------------- ----------
1 Basic instinct B
2 Basic thing B
3 Super thing T
4 Hyper instinct NULL
SQL>

Select the row_number or fake identity

I have a table that did not have an identity column added to it. I don't really need one for any specific purpose.
I had 821 rows and I did a test of 500 more. Now I need to check those 500 files and I was looking for a simple way to
select * from table where row_number > 821
I've tried row_number() but I can't have them be ordered, I need all rows above 821 returned.
A table is an unordered bag of rows. You can't identify the first 820 rows that were inserted unless you have some column to identify the order of insertion (a trusted IDENTITY or datetime column, for example). Otherwise you are throwing a bunch of marbles on the floor and asking the first person who walks into the room to identify the first 820 marbles that fell.
Here is a very simple example that demonstrates that output order cannot be predicted, and certainly can't be relied upon to be FIFO (and also shows a case where HABO's "solution" breaks):
CREATE TABLE dbo.foo(id INT, x CHAR(1));
CREATE CLUSTERED INDEX x ON dbo.foo(x);
-- or CREATE INDEX x ON dbo.foo(x, id); -- doesn't require a clustered index to prove
INSERT dbo.foo VALUES(1,'z');
INSERT dbo.foo VALUES(2,'y');
INSERT dbo.foo VALUES(3,'x');
INSERT dbo.foo VALUES(4,'w');
INSERT dbo.foo VALUES(5,'v');
INSERT dbo.foo VALUES(6,'u');
INSERT dbo.foo VALUES(7,'t');
INSERT dbo.foo VALUES(8,'s');
SELECT TOP (5) id, x, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM dbo.foo;
SELECT * FROM dbo.foo;
Results:
---- ---- ----
8 s 1
7 t 2
6 u 3
5 v 4
4 w 5
---- ----
8 s
7 t
6 u
5 v
4 w
3 x
2 y
1 z
SQLfiddle demo
The following is a hack that may help, but is NOT a generally useful solution:
Row_Number() over (order by (select NULL)) as UntrustworthyRowNumber

How can I select a particular row from a nested table? [duplicate]

This question already has an answer here:
Oracle SQL: select from table with nested table
(1 answer)
Closed 10 years ago.
I have a table that looks like this:
CREATE
OR
REPLACE
TYPE subaccount_nt
IS TABLE OF VARCHAR2(30);
CREATE
TABLE my_table
( contract_id NUMBER(38,0)
, subaccount SUBACCOUNT_NT );
Here's some sample data:
100 [1000, 1, 2]
200 [2000, NULL, 999]
300 [3000]
How can I write a query to return the the third row from the nested table if the 3rd row exists? Here's the output I'm trying to get:
100 1
200 NULL
300 NULL
Haeving never worked with nested tables before, I'm finding it quite hard to forumate my query. Thanks.
You can use analytics with a lateral join (unnesting of collection):
SQL> SELECT contract_id, CASE WHEN rn = 2 THEN val END val
2 FROM (SELECT t.contract_id, column_value val,
3 row_number() over(PARTITION BY t.contract_id ORDER BY 1) rn,
4 COUNT(*) over (PARTITION BY t.contract_id) cnt
5 FROM my_table t,
6 TABLE(t.subaccount))
7 WHERE rn = 2 OR cnt = 1;
CONTRACT_ID VAL
-------------- ---
100 1
200
300
This will not list rows that have an empty subaccount.
By the way the order is not guaranteed since the nested tables are stored as unordered sets of rows.

SQL to move rows up or down in two-table arrangement

I have two tables that I designed this way with a possible reshuffling of elements in mind:
1. [dbo.test_db_002] with columns:
[id] = INT NOT NULL IDENTITY(1,1) PRIMARY KEY
[name] = NVARCHAR(255)
and
2. [dbo.test_db_003] with columns:
[ord] = INT
[itmid] = INT NOT NULL PRIMARY KEY
[itmid] column has a constraint linking it to [dbo.test_db_002].[id] like so:
ALTER TABLE [dbo.test_db_003]
ADD CONSTRAINT fk1 FOREIGN KEY ([itmid])
REFERENCES [dbo.test_db_002]([id])
ON DELETE CASCADE ON UPDATE CASCADE;
Say, [dbo.test_db_002] table has the following data:
[id] [name]
3 John
5 Mary
8 Michael
10 Steve
13 Jack
20 Pete
and [dbo.test_db_003] has the following ordering data:
[ord] [itmid]
1 5
4 8
5 13
8 3
10 10
13 20
So when I retrieve names from the database I use the following SQL:
SELECT [name]
FROM [dbo.test_db_002] t1
LEFT JOIN [dbo.test_db_003] t2 ON t1.[id]=t2.[itmid]
ORDER BY t2.[ord] ASC
It produces the list of names (ordered by the [dbo.test_db_003].[ord] column):
Mary
Michael
Jack
John
Steve
Pete
What I am looking for is an option to move each of the names up and down the list. For instance, if I want to move "John" one position up, what do I do?
So far I came up with this partial SQL:
WITH cte AS
(
SELECT [id], [ord], ROW_NUMBER() OVER (ORDER BY t2.[ord] ASC) AS rowNum
FROM [dbo.test_db_002] t1
LEFT JOIN [dbo.test_db_003] t2 ON t1.[id] = t2.[itmid]
)
That will select the following:
rowNum [id] [ord]
1 1 5
2 4 8
3 5 13
4 8 3
5 10 10
6 13 20
So I understand that I need to shift values in [ord] column up by one starting from the index 3 (since "John" index is 4) and then somehow make "John"'s [ord] to be set to 5, but how do you do that?
I prepared a complete demo for you how this can work on data.stackexchange.com.
The solution is tailored to your comment:
the move up or down can be only a single step - in other words, one
cannot move 2 or more positions
In the example I make John trade ordinal positions with Jack above him:
WITH x AS (
SELECT t2.itmid, t2.ord
FROM dbo.test_db_002 t1
LEFT JOIN dbo.test_db_003 t2 ON (t1.id = t2.itmid)
WHERE t1.name = 'John' -- must be unique, or query by id ...
)
, y AS (
SELECT TOP 1
t.itmid, t.ord
FROM dbo.test_db_003 t, x
WHERE t.ord < x.ord -- smaller ord = "above"
ORDER BY t.ord DESC
)
UPDATE dbo.test_db_003 SET ord = z.ord
FROM (
SELECT x.itmid, y.ord FROM x,y
UNION ALL
SELECT y.itmid, x.ord FROM x,y
) z
WHERE dbo.test_db_003.itmid = z.itmid
###Major points:
Use two CTE to structure the query:
Get John's id & ordinal position
Get the same for the person above him
Prepare two rows where these two switch ordinal numbers with the help of UNION ALL
Use these two rows in a now simple UPDATE
The ordinal position ord must allow passing duplicates for this to work.
If there is nobody 'above', the query will silently do nothing.

How do you find a missing number in a table field starting from a parameter and incrementing sequentially?

Let's say I have an sql server table:
NumberTaken CompanyName
2 Fred 3 Fred 4 Fred 6 Fred 7 Fred 8 Fred 11 Fred
I need an efficient way to pass in a parameter [StartingNumber] and to count from [StartingNumber] sequentially until I find a number that is missing.
For example notice that 1, 5, 9 and 10 are missing from the table.
If I supplied the parameter [StartingNumber] = 1, it would check to see if 1 exists, if it does it would check to see if 2 exists and so on and so forth so 1 would be returned here.
If [StartNumber] = 6 the function would return 9.
In c# pseudo code it would basically be:
int ctr = [StartingNumber]
while([SELECT NumberTaken FROM tblNumbers Where NumberTaken = ctr] != null)
ctr++;
return ctr;
The problem with that code is that is seems really inefficient if there are thousands of numbers in the table. Also, I can write it in c# code or in a stored procedure whichever is more efficient.
Thanks for the help
Fine, if this question isn't going to be closed, I may as well Copy and paste my answer from the other one:
I called my table Blank, and used the following:
declare #StartOffset int = 2
; With Missing as (
select #StartOffset as N where not exists(select * from Blank where ID = #StartOffset)
), Sequence as (
select #StartOffset as N from Blank where ID = #StartOffset
union all
select b.ID from Blank b inner join Sequence s on b.ID = s.N + 1
)
select COALESCE((select N from Missing),(select MAX(N)+1 from Sequence))
You basically have two cases - either your starting value is missing (so the Missing CTE will contain one row), or it's present, so you count forwards using a recursive CTE (Sequence), and take the max from that and add 1
Tables:
create table Blank (
ID int not null,
Name varchar(20) not null
)
insert into Blank(ID,Name)
select 2 ,'Fred' union all
select 3 ,'Fred' union all
select 4 ,'Fred' union all
select 6 ,'Fred' union all
select 7 ,'Fred' union all
select 8 ,'Fred' union all
select 11 ,'Fred'
go
I would create a temp table containing all numbers from StartingNumber to EndNumber and LEFT JOIN to it to receive the list of rows not contained in the temp table.
If NumberTaken is indexed you could do it with a join on the same table:
select T.NumberTaken -1 as MISSING_NUMBER
from myTable T
left outer join myTable T1
on T.NumberTaken= T1.NumberTaken+1
where T1.NumberTaken is null and t.NumberTaken >= STARTING_NUMBER
order by T.NumberTaken
EDIT
Edited to get 1 too
1> select 1+ID as ID from #b as b
where not exists (select 1 from #b where ID = 1+b.ID)
2> go
ID
-----------
5
9
12
Take max(1+ID) and/or add your starting value to the where clause, depending on what you actually want.