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

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.

Related

nested select or join query?

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
;

SQL Server find unique combinations

I have a table
rate_id service_id
1 1
1 2
2 1
2 3
3 1
3 2
4 1
4 2
4 3
I need to find and insert in a table the unique combinations of sevice_ids by rate_id...but when the combination is repeated in another rate_id I do not want it to be inserted
In the above example there are 3 combinations
1,2 1,3 1,2,3
How can I query the first table to get the unique combinations?
Thanx!
Try doing something like this:
DECLARE #TempTable TABLE ([rate_id] INT, [service_id] INT)
INSERT INTO #TempTable
VALUES (1,1),(1,2),(2,1),(2,3),(3,1),(3,2),(4,1),(4,2),(4,3)
SELECT DISTINCT
--[rate_id], --include if required
(
SELECT
CAST(t2.[service_id] AS VARCHAR) + ' '
FROM
#TempTable t2
WHERE
t1.[rate_id] = t2.[rate_id]
ORDER BY
t2.[rate_id]
FOR XML PATH ('')
) AS 'Combinations'
FROM
#TempTable t1
I put the values in a table variable just for ease of testing the SELECT query.

How do I SELECT such that a group by column has every value from supplied list in another coumn

In MS SQL Server 2008, have a table like the following:
CREATE TABLE SomeTable
(
MajorID int NOT NULL REFERENCES ...,
MinorID int NOT NULL,
Value int NOT NULL REFERENCES ...,
PRIMARY KEY(MajorID, MinorID)
)
I also have a set of (Value0, Value1, ...). The goal is to find all such MajorID that have every Value from the set listed at least once. MinorID is not important in this task. The size of set is not predefined, and the set is generated in client application. There's a reasonable limit on its maximum size, say, 64.
What SQL should I use?
Example:
MajorID MinorID Value
1 0 4
1 1 1
1 2 3
1 3 4
1 4 4
1 5 5
1 6 5
2 0 1
3 0 1
3 1 4
For value list (1, 4) the answer is (1, 3), because MajorID 1 and 3 have each value listed at least once.
You can do this with aggregation, as in this query:
select majorid
from t
group by majorid
having COUNT(distinct value) = (select COUNT(distinct value) from t)
The having clause checks that all values are there for a majorid.
This is for all values. If you have a value list, then try this:
with valuelist as (
select 1 as vslue union all
select 4
)
select majorid
from t join
valuelist vl
on t.value = vl.value
group by majorid
having count(distinct value) = (select count(*) from valuelist)
This is the simple way:
select majorid
from ReportStack
where value in (1, 3)
group by majorid
having count (distinct value) = 2
The only maintenance issue with this query is making sure the having clause value (2 in this query) is the same as size of the target value list.

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.

mysql: duplicate certain rows using SQL?

I have a table that looks like this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
I'd like to run some SQL that will take each one of those rows and create another based on it like so:
foreach row where vid=2, create a duplicate row where vid = 3
So I'll end up with this:
nid vid tid
1 2 3
2 2 4
3 2 4
4 2 3
1 3 3
2 3 4
3 3 4
4 3 3
The table is not that big (less than 500 rows, I think) and this is a one-time thing (so the code does not need to be amazingly optimized or anything). Thanks!
you’ll probably want this:
INSERT INTO `table`(`nid`, `vid`, `nid`)
SELECT `nid`, `vid`+1, `nid`
FROM `table`
WHERE `vid` = 2
it inserts all rows, with vid incremented by 1
If you want a general solution (i.e. all values of vid, but adding one to each of them), this is just adapting dandres' answer:
INSERT INTO TheTable(nid, vid_incremented, nid)
SELECT nid, vid + 1 as vid_incremented , nid
FROM TheTable
Edit: fixed to do arithmetic in select statement.
Could you do the following:
INSERT INTO TheTable(nid, vid, nid)
SELECT nid, 3, nid
FROM TheTable
WHERE vid = 2
Two columns appear to have the same name, but the above should work. It takes the set of Vid = 2 elements, inserts them again but using Vid = 3.
Create another table with 2 fixed records, e.g. TwoRec (vvid), with values 2 and 3
Do a cartesian join with TwoRec, i.e.
SELECT TheTable.nid, TwoRec.vvid, TheTable.nid
FROM TheTable, TwoRec
WHERE TheTable.vid = 2;
I'd create a temp table of all of the vids you want to use:
create temporary table Vids
(
vid int
)
declare #vid int
declare #maxvid int
set #vid = 1
set #maxvid = 500
while #vid < #maxvid
begin
insert into vids values(#vid)
set #vid = #vid + 1
end
Then, I'd use that temp table to build out your other table:
select
t2.id,
t1.vid,
t2.nid
into newtable
from
vids t1
cross join tbl t2
order by
t1.vid,
t2.id