select N items for every group? - sql

I have tables:
Category: Id, Name...
News: Id, Title
News_Category_Mapping: Id, NewsId, CategoryId
Where newsid, categoryid are foreign keys to these 2 tables.
News_category_mapping:
Id NewsID CategoryId
1 1 1
2 2 1
3 3 1
4 4 3
5 5 5
6 6 3
so i may want to get maximum 2 news items from every categoryid, say like
Id NewsID CategoryId
1 1 1
2 2 1
4 5 3
6 6 3
5 5 5
Sorry for my english.

Let say you need 2 items each
Select *
From Category C
CROSS APPLY (Select top 2 Id,CatId,NewsName
From News Nw where Nw.CatId=C.Id) As N
Here is the fiddle sample

Try this:
WITH CTE AS
(SELECT C.Id,N.Id,N.Title,RN=ROW_NUMBER() OVER (PARTITION BY NC.CategoryID ORDER BY NC.NewsId)
FROM News_Category_Mapping NC JOIN
News N ON NC.NewsId=N.Id JOIN
Category C ON NC.CategoryId=C.Id)
SELECT * FROM CTE WHERE RN<3
Explanation:
Here, the inner query selects the records along a row number RN. To know how the query works, please execute the inner query first.

You can use CROSS APPLY, like so:
Select c.*, Sub.*
from
Categories c cross apply
(
select top 2
*
from
News n
where
exists
(
select 1
from NewsCategories nc
where nc.CatId = c.id and n.id = nc.NewsId
)
) Sub
Here is an SQLFiddle for this

Related

Recursive query with CTE

I need some help with one query.
So, I already have CTE with the next data:
ApplicationID
CandidateId
JobId
Row
1
1
1
1
2
1
2
2
3
1
3
3
4
2
1
1
5
2
2
2
6
2
5
3
7
3
2
1
8
3
6
2
9
3
3
3
I need to find one job per candidate in a way, that this job was distinct for table.
I expect that next data from query (for each candidate select the first available jobid that's not taken by the previous candidate):
ApplicationID
CandidateId
JobId
Row
1
1
1
1
5
2
2
2
8
3
6
2
I have never worked with recursive queries in CTE, having read about them, to be honest, I don't fully understand how this can be applied in my case. I ask for help in this regard.
The following query returns the expected result.
WITH CTE AS
(
SELECT TOP 1 *,ROW_NUMBER() OVER(ORDER BY ApplicationID) N,
CONVERT(varchar(max), CONCAT(',',JobId,',')) Jobs
FROM ApplicationCandidateCTE
ORDER BY ApplicationID
UNION ALL
SELECT a.*,ROW_NUMBER() OVER(ORDER BY a.ApplicationID),
CONCAT(Jobs,a.JobId,',') Jobs
FROM ApplicationCandidateCTE a JOIN CTE b
ON a.ApplicationID > b.ApplicationID AND
a.CandidateId > b.CandidateId AND
CHARINDEX(CONCAT(',',a.JobId,','), b.Jobs)=0 AND
b.N = 1
)
SELECT * FROM CTE WHERE N = 1;
However, I have the following concerns:
The recursive CTE may extract too many rows.
The concatenated JobId may exceed varchar(max).
See dbfiddle.

SQL - Choose 4 movies (one which stars one actor and the other 3 where he doesn't participate)

I have 3 tables:
FILMS
id film
1 Gladiator
2 Pulp Fiction
3 Taxi Driver
4 ...
ACTORS
id actor
1 Russell Crowe
2 Robert DeNiro
3 John Travolta
4 Samuel L. Jackson
RELATIONSHIPS
id_film id_actor
1 1
2 3
2 4
3 2
Now I'm trying to make a query where by passing an actor's id I would get 4 random movies - One where he participates and three others where he doesn't.
I'm finding it hard to find a solution. Any idea of what would be the better approach?
The canonical way would use union all. The following breaks this out into separate CTEs, just to make the logic very clear:
with a_1 as (
select top 1 r.id_file
from relationships r
where r.id_actor = #id_actor
order by newid()
),
nota_3 as (
select top 3 r.id_film
from relationships r
group by r.id_film
having sum(case when r.id_actor = #id_actor then 1 else 0 end) = 0
order by newid()
)
select * from a_1 union all
select * from nota_3;
To my mind the clearest way of expressing the query for non-participation films is with EXCEPT, like so:
;WITH ParticipationFilms AS (
SELECT F.id, F.film
FROM FILMS F INNER JOIN RELATIONSHIPS R ON F.id = R.id_film
WHERE R.id_actor = #id_actor
)
, NonParticipationFilms AS (
SELECT id, film
FROM FILMS
EXCEPT
SELECT id, film
FROM ParticipationFilms
)
SELECT TOP (1) * FROM ParticipationFilms ORDER BY NEWID()
UNION ALL
SELECT TOP (3) * FROM NonParticipationFilms ORDER BY NEWID()
;

Select unique subsets

I have a table like in example below.
SQL> select * from test;
ID PARENT_ID NAME
1 1 A
2 1 B
3 2 A
4 2 B
5 3 A
6 3 B
7 3 C
8 4 A
What I need is to get all unique subsets of names ((A,B), (A,B,C), (A)) or exclude duplicate subsets. You can see that (A,B) is twice there, one for PARENT_ID=1 and one for 2.
I want to exclude such duplicates:
ID PARENT_ID NAME
1 1 A
2 1 B
5 3 A
6 3 B
7 3 C
8 4 A
You can use DISTINCT to only return different values.
e.g.
SELECT DISTINCT GROUP_CONCAT(NAME SEPARATOR ',') as subsets
FROM TABLE_1
GROUP BY PARENT_ID;
SQL Fiddle
I have used 'group_concat' assuming you are using 'Mysql'. The equivalent function in Oracle is 'listagg()'. you can see it in action here in SQL fiddle
Here is the solution:-
Select a.* from
test a
inner join
(
Select nm, min(parent_id) as p_id
from
(
Select Parent_id, group_concat(NAME) as nm
from test
group by Parent_ID
) a
group by nm
)b
on a.Parent_id=b.p_id
order by parent_id, name

Formulating Query

I have a table 'TempC3'
Itemset itemset2
1 3
2 3
2 5
3 5
I want combination of elements in these columns without repetition. So the output table shall be
Itemset itemset2 Itemset3
1 3 5
2 3 5
1 2 3
I designed a query but it wont return the last row of the desired output table -
Select distinct a.Itemset,
a. Itemset2,
c.itemset2
from TempC3 a
Join TempC3 c
ON c.Itemset2 > a.Itemset2
This query only results this:
Itemset itemset2 Itemset3
1 3 5
2 3 5
Since you want all combinations of itemsets, you have to concatenate the two columns in your input table into a single column first. You could do this, for example, using a CTE:
Fiddle Here
WITH CTE AS (
SELECT Itemset FROM TempC3
UNION
SELECT Itemset2 FROM TempC3
)
SELECT I1.Itemset, I2.Itemset, I3.Itemset FROM CTE AS I1
INNER JOIN CTE AS I2 ON I2.Itemset > I1.Itemset
INNER JOIN CTE AS I3 ON I3.Itemset > I2.Itemset

Copying existing rows in a table

How do I go about doing the following?
I am using the following query to get a specific users tab ids:
select id
from intranet.dbo.tabs
where cms_initials = #user
order by id asc
which might return the following ids
4
5
6
7
I now want to insert the rows from the following query:
select tabs_id, widgets_id, sort_column, sort_row
from intranet.dbo.columns c
inner join intranet.dbo.tabs t on c.tabs_id = t.id
where t.is_default = 1
But use the ids from the first query to replace the tab ids
so if the second query originally returns tabs_id's as
0
0
0
0
1
1
1
2
2
2
3
3
I should end up with
0
0
0
0
1
1
1
2
2
2
3
3
4
4
4
4
5
5
5
6
6
6
7
7
Is this possible with sql server 2005 without using stored procedures?
So far I have
insert into intranet.dbo.columns ( tabs_id, widgets_id, sort_column, sort_row )
select tabs_id, widgets_id, sort_column, sort_row
from intranet.dbo.columns c
inner join intranet.dbo.tabs t on c.tabs_id = t.id
where t.is_default = 1
But this just copies everything as is, I need to do that, but replace the ids in the copied rows.
This solution uses common table expressions and ranking functions. A and B are your original queries ranked by tab order. A and B are then joined by tab ranking and inserted.
USE intranet
;WITH A AS
(
SELECT ROW_NUMBER() OVER (ORDER BY id) AS tab_ranking
, id
FROM dbo.tabs
WHERE cms_initials = #user
),
B AS
(
SELECT DENSE_RANK() OVER (ORDER BY tabs_id) AS tab_sequence
, tabs_id, widgets_id, sort_column, sort_row
FROM dbo.columns
WHERE tabs_id IN (SELECT t.id FROM dbo.tabs t WHERE t.is_default = 1)
)
INSERT INTO dbo.columns (tabs_id, widgets_id, sort_column, sort_row)
SELECT a.id, b.widgets_id, b.sort_column, b.sort_row
FROM A
INNER JOIN B ON B.tab_ranking = A.tab_ranking