How to get unique result with left join - sql

I have two tables 1) Users and 2) Images.
Users table
id name
1 xyz
2 abc
Images table
id path
1 ~/img/12.jpg
1 ~/img/34.jpg
2 ~/img/de.jpg
2 ~/img/rt.jpg
So now when I write the query:
select id, name, path
from users
left join images on images.id = users.id
where id =1
Then it will give me output like:
id name path
1 xyz ~/img/12.jpg
1 xyz ~/img/34.jpg
But I need the output like:
id name path1 path2
1 xyz ~/img/12.jpg ~/img/34.jpg
How can I get this?

for your output we can achieve using Cross Apply and Pivot
declare #users TABLE
(id INT, varchar(4))
;
INSERT INTO #users
(id, name)
VALUES
(1, 'xyz'),
(2, 'abc')
;
declare #images TABLE
(id int, path varchar(12))
;
INSERT INTO #images
(id, path)
VALUES
(1, '~/img/12.jpg'),
(1, '~/img/34.jpg'),
(2, '~/img/de.jpg'),
(2, '~/img/rt.jpg')
;
select * from (
select s.id,
s.name ,
images.path,
COL + CAST(row_number()over(PARTITION BY s.ID ORDER BY COL) AS VARCHAR) RN
from #users s
left join #images images
on images.id=s.id
CROSS APPLY (VALUES ('path',path))CS(Col,val)
where s.id =1)P
PIVOT (MAX(PATH) FOR RN IN ([path1],[path2]))P

It is easy when it is fixed to 2 paths
;WITH paths AS
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY path) AS Seq FROM Images
)
SELECT
*
(SELECT path FROM paths WHERE id = u.id AND Seq = 1) Path1,
(SELECT path FROM paths WHERE id = u.id AND Seq = 2) Path2
FROM Users u
WHERE id = 1

Do a GROUP BY for min and max path:
select u.id, u.name, min(i.path), max(i.path)
from users u
left join images i on i.id = u.id
group by u.id, u.name
Works well as long as just one or two paths for a user.

You can use Group by like
select id, name , path from users left join images on images.id=users.id
where id =1 group by id

Related

MSSQL Select a parents ID value

Having some issues with a larger MSSQL DB I am administrating.
Have some functionallity I am trying to implement in a single table.
Table looks contains such columns:
ID MailID
010-123456 12345678
010-123456/1 NULL
010-123456/2 NULL
010-123456/3 NULL
Now, what I would like to accomplish is to set each Childs MaildID to the same as its Parents MailID.
A ParentID being an ID with same value as the Child (Id ID row) before the "/" delimiter.
Also The IDs should not contins any chars, just digits (execpt for '-' and '/').
So currently I have this solution:
Declare #MyTable table(Child varchar(MAX),Parent varchar(MAX),ParentMaildId varchar(MAX))
insert into #MyTable
select ID as Child, left(ID, charindex('/', ID)-1) AS Parent, MailID as MailID
FROM [thisismy].[dbo].[table]
where ID not like '%[a-z]%' and ID like '%/%' and MailId is not NULL
select * from #MyTable
--update t1
--SET t1.MailId = t2.ParentMaildId
--FROM [thisismy].[dbo].[table] AS t1
--INNER JOIN #IDAS t2 ON t1.ID = t2.Child
--WHERE t1.ID = t2.Child and t1.MailId is NULL
When I use the select statement to get MyTable fileld up here, it only returns a low subset of all the cases that is in my DB table, why?
I would like to have stored procedure that automatically updates the Mailid of the Child to the Parent (all recuired info being in the very same table).
Is there a better way to do this?
I think you want:
select m.*, p.mailid as imputed_parent
from #mytable m cross apply
(select top (1) p.*
from #mytable p
where m.id like p.id + '/%'
order by p.id desc
) p;
You can put this in an update using a correlated subquery:
update m
set mailid = (select top (1) p.mailid
from #mytable p
where m.id like p.id + '/%'
order by p.id desc
)
from #mytable m
where m.mailid is null;
Here is a db<>fiddle.

remove Union from query

I have query to get category and subcategories of same category. I used below query:
Select Id from category where name like '%events%' and deleted = 0 and published = 1
UNION
SELECT Id from category where parentcategoryid = (Select id from category where name like '%events%' and deleted = 0 and published = 1)
I do not want to use UNION, want to use Join only. But not getting how i can achieve. Below is the table structure. Please help me. thanks in Advance
The following might solve your problem:
DECLARE #t TABLE (id int, name varchar(20), parentcatid varchar(200))
insert into #t values(23,'Christmas', 34)
,(29,'Birthday', 34)
,(31,'New year', 34)
,(34,'Events', 0)
,(35,'gfrhrt', 0)
;WITH cte AS(
Select Id from #t
where name like '%events%' --and deleted = 0 and published = 1
),
cte2 AS (
SELECT a.id AS tId, cpa.id, cId2.id AS idPa
from #t a
LEFT JOIN cte AS cId ON cId.Id = a.Id
FULL OUTER JOIN #t AS cPa ON cPa.parentcatid = cId.Id
LEFT JOIN cte cId2 ON cId2.id = cPa.id
WHERE cId.Id IS NOT NULL OR cPa.Id IS NOT NULL
)
SELECT id
FROM cte2
WHERE tId IS NOT NULL
OR id = idPa
The idea is to get all required IDs within the cte and then get all categories, where either ID oder ParentID match the IDs from the cte. However, depending on the size of your table, you might want to add further filters to the cte.

Combining a recursive CTE with another query

I have a table of locations each of which can have a parent location
LocationId | ParentLocationId
-----------------------------
1 null
2 1
3 2
4 2
I managed to create a recursive CTE which gives me parent location id (plus the original location id) for any given location id
WITH GetLocationParents AS
(
select [LocationId], [ParentLocationId] from Locations
where LocationId = 3
UNION ALL
select i.[LocationId], i.[ParentLocationId]
from Locations i
join GetLocationParents cte on cte.ParentLocationId = i.LocationId
)
SELECT [ParentLocationId] FROM GetLocationParents
WHERE [ParentLocationId] is not NULL;
e.g. where LocationId = 3 would return:
ParentLocationId
----------------
3
2
1
In another table I have a query which will return LocationId as one of the fields:
select exi.PersonId, exi.LocationId from Persons e
left join PersonHasLocations exi on e.PersonId = exi.PersonId
left join Locations i on exi.LocationId = i.LocationId
Which with a where clause would return something like:
PersonId | LocationId
---------------------
100 3
I'm trying to combine these queries to get the result:
PersonId | LocationId
---------------------
100 3
100 2
100 1
I'm trying the following but it's still only returning the first row:
WITH
GetLocationParents AS
(select [LocationId], [ParentLocationId] from Locations
--where LocationId = 3
UNION ALL
select i.[LocationId], i.[ParentLocationId]
from Locations i inner join GetLocationParents cte
on cte.ParentLocationId = i.LocationId),
GetPersons AS
(select exi.PersonId, exi.LocationID from Persons e
left join PersonHasLocations exi on e.PersonID = exi.PersonId
left join Locations i on exi.LocationId = i.LocationID)
SELECT * FROM GetLocationParents gip
INNER JOIN GetPersons ge on ge.LocationId = gip.LocationID
WHERE ge.PersonId = 100
Is it possible to merge a recursive query with a normal query like this?
I guess you have a small bug in your cte. I would suggest to change the query as follows:
DECLARE #t TABLE (
LocationId int,
ParentLocationId int
)
INSERT INTO #t VALUES
(1, NULL)
,(2, 1)
,(3, 2)
,(4, 2)
;WITH GetLocationParents AS
(
select [LocationId] AS k, [LocationId], [ParentLocationId] from #t
UNION ALL
select k, i.[LocationId], i.[ParentLocationId]
from GetLocationParents cte
join #t i on cte.ParentLocationId = i.LocationId
)
SELECT *
FROM GetLocationParents
WHERE k = 3
With this you receive a list with the value you filter on in the first column and all depending "levels" above this in the second column. This can then be used in order to join to your second table.
Keep in mind that - depending on your number of levels - you will have to take care of MAX RECUSRSION.

How to join three tables with distinct

I'm trying to join three tables to pull back a list of distinct blog posts with associated assets (images etc) but I keep coming up a cropper. The three tablets are tblBlog, tblAssetLink and tblAssets. The Blog tablet hold the blog, the asset table holds the assets and the Assetlink table links the two together.
tblBlog.BID is the PK in blog, tblAssets.AID is the PK in Assets.
This query works but pulls back multiple posts for the same record. I've tried to use select distinct and group by and even union but as my knowledge is pretty poor with SQL - they all error.
I'd like to also discount any assets that are marked as deleted (tblAssets.Deleted = true) but not hide the associated Blog post (if that's not marked as deleted). If anyone can help - it would be much appreciated! Thanks.
Here's my query so far....
SELECT dbo.tblBlog.BID,
dbo.tblBlog.DateAdded,
dbo.tblBlog.PMonthName,
dbo.tblBlog.PDay,
dbo.tblBlog.Header,
dbo.tblBlog.AddedBy,
dbo.tblBlog.PContent,
dbo.tblBlog.Category,
dbo.tblBlog.Deleted,
dbo.tblBlog.Intro,
dbo.tblBlog.Tags,
dbo.tblAssets.Name,
dbo.tblAssets.Description,
dbo.tblAssets.Location,
dbo.tblAssets.Deleted AS Expr1,
dbo.tblAssetLink.Priority
FROM dbo.tblBlog
LEFT OUTER JOIN dbo.tblAssetLink
ON dbo.tblBlog.BID = dbo.tblAssetLink.BID
LEFT OUTER JOIN dbo.tblAssets
ON dbo.tblAssetLink.AID = dbo.tblAssets.AID
WHERE ( dbo.tblBlog.Deleted = 'False' )
ORDER BY dbo.tblAssetLink.Priority, tblBlog.DateAdded DESC
EDIT
Changed the Where and the order by....
Expected output:
tblBlog.BID = 123
tblBlog.DateAdded = 12/04/2015
tblBlog.Header = This is a header
tblBlog.AddedBy = Persons name
tblBlog.PContent = *text*
tblBlog.Category = Category name
tblBlog.Deleted = False
tblBlog.Intro = *text*
tblBlog.Tags = Tag, Tag, Tag
tblAssets.Name = some.jpg
tblAssets.Description = Asset desc
tblAssets.Location = Location name
tblAssets.Priority = True
Use OUTER APPLY:
DECLARE #b TABLE ( BID INT )
DECLARE #a TABLE ( AID INT )
DECLARE #ba TABLE
(
BID INT ,
AID INT ,
Priority INT
)
INSERT INTO #b
VALUES ( 1 ),
( 2 )
INSERT INTO #a
VALUES ( 1 ),
( 2 ),
( 3 ),
( 4 )
INSERT INTO #ba
VALUES ( 1, 1, 1 ),
( 1, 2, 2 ),
( 2, 1, 1 ),
( 2, 2, 2 )
SELECT *
FROM #b b
OUTER APPLY ( SELECT TOP 1
a.*
FROM #ba ba
JOIN #a a ON a.AID = ba.AID
WHERE ba.BID = b.BID
ORDER BY Priority
) o
Output:
BID AID
1 1
2 1
Something like:
SELECT b.BID ,
b.DateAdded ,
b.PMonthName ,
b.PDay ,
b.Header ,
b.AddedBy ,
b.PContent ,
b.Category ,
b.Deleted ,
b.Intro ,
b.Tags ,
o.Name ,
o.Description ,
o.Location ,
o.Deleted AS Expr1 ,
o.Priority
FROM dbo.tblBlog b
OUTER APPLY ( SELECT TOP 1
a.* ,
al.Priority
FROM dbo.tblAssetLink al
JOIN dbo.tblAssets a ON al.AID = a.AID
WHERE b.BID = al.BID
ORDER BY al.Priority
) o
WHERE b.Deleted = 'False'
You cannot join three tables unless they all have the same attribute. It would work if all tables had BID, but the second join is trying to join AID. Which wont work. They all have to have BID.
Based on your comments
i would like to get is just one asset per blog post (top one ordered
by Priority)
You can change your query as following. I suggest changing the join with dbo.tblAssetLink to filtered one, which contains only one (highest priority) link for every blog.
SELECT dbo.tblBlog.BID,
dbo.tblBlog.DateAdded,
dbo.tblBlog.PMonthName,
dbo.tblBlog.PDay,
dbo.tblBlog.Header,
dbo.tblBlog.AddedBy,
dbo.tblBlog.PContent,
dbo.tblBlog.Category,
dbo.tblBlog.Deleted,
dbo.tblBlog.Intro,
dbo.tblBlog.Tags,
dbo.tblAssets.Name,
dbo.tblAssets.Description,
dbo.tblAssets.Location,
dbo.tblAssets.Deleted AS Expr1,
dbo.tblAssetLink.Priority
FROM dbo.tblBlog
LEFT OUTER JOIN
(SELECT BID, AID,
ROW_NUMBER() OVER (PARTITION BY BID ORDER BY [Priority] DESC) as N
FROM dbo.tblAssetLink) AS filteredAssetLink
ON dbo.tblBlog.BID = filteredAssetLink.BID
LEFT OUTER JOIN dbo.tblAssets
ON filteredAssetLink.AID = dbo.tblAssets.AID
WHERE dbo.tblBlog.Deleted = 'False' AND filteredAssetLink.N = 1
ORDER BY tblBlog.DateAdded DESC

SQL Many-to-Many Query Problem

I have three tables: videos, videos_categories, and categories.
The tables look like this:
videos: video_id, title, etc...
videos_categories: video_id, category_id
categories: category_id, name, etc...
In my app, I allow a user to multiselect categories. When they do so, I need to return all videos that are in every selected category.
I ended up with this:
SELECT * FROM videos WHERE video_id IN (
SELECT c1.video_id FROM videos_categories AS c1
JOIN c2.videos_categories AS c2
ON c1.video_id = c2.video_id
WHERE c1.category_id = 1 AND c2.category_id = 2
)
But for every category I add to the multiselect, I have to add a join to my inner select:
SELECT * FROM videos WHERE video_id IN (
SELECT c1.video_id FROM videos_categories AS c1
JOIN videos_categories AS c2
ON c1.video_id = c2.video_id
JOIN videos_categories AS c3
ON c2.video_id = c3.video_id
WHERE c1.category_id = 1 AND c2.category_id = 2 AND c3.category_id = 3
)
I can't help but feel this is the really wrong way to do this, but I'm blocked trying to see the proper way to go about it.
if this is a primary key:
videos_categories: video_id, category_id
then a GROUP BY and HAVING should work, try this:
SELECT
*
FROM videos
WHERE video_id IN (SELECT
video_id
FROM videos_categories
WHERE category_id IN (1,2,3)
GROUP BY video_id
HAVING COUNT(video_id)=3
)
Sounds similar to SQL searching for rows that contain multiple criteria
To avoid having to another join for each category (and hence changing the structure of the query), you can put the categories into a temp table and then join against that.
CREATE TEMPORARY TABLE query_categories(category_id int);
INSERT INTO query_categories(category_id) VALUES(1);
INSERT INTO query_categories(category_id) VALUES(2);
INSERT INTO query_categories(category_id) VALUES(3);
SELECT * FROM videos v WHERE video_id IN (
SELECT video_id FROM video_categories vc JOIN query_categories q ON vc.category_id = qc.category_id
GROUP BY video_id
HAVING COUNT(*) = 3
)
Although this is ugly in its own way, of course. You may want to skip the temp table and just say 'category_id IN (...)' in the subquery.
Here's a FOR XML PATH solution:
--Sample data
CREATE TABLE Video
(
VideoID int,
VideoName varchar(50)
)
CREATE TABLE Videos_Categories
(
VideoID int,
CategoryID int
)
INSERT Video(VideoID, VideoName)
SELECT 1, 'Indiana Jones'
UNION ALL
SELECT 2, 'Star Trek'
INSERT Videos_Categories(VideoID, CategoryID)
SELECT 1, 1
UNION ALL
SELECT 1, 2
UNION ALL
SELECT 1, 3
UNION ALL
SELECT 2, 1
GO
--The query
;WITH GroupedVideos
AS
(
SELECT v.*,
SUBSTRING(
(SELECT (', ') + CAST(vc.CategoryID AS varchar(20))
FROM Videos_Categories AS vc
WHERE vc.VideoID = v.VideoID
AND vc.CategoryID IN (1,2)
ORDER BY vc.CategoryID
FOR XML PATH('')), 3, 2000) AS CatList
FROM Video AS v
)
SELECT *
FROM GroupedVideos
WHERE CatList = '1, 2'
(Ignore everything below - I misread the question)
Try
WHERE c1.category_id IN (1,2,3)
or
...
FROM videos v
JOIN Vedeos_categories vc ON v.video_id = vc.video_id
WHERE vc.category_id IN (1,2,3)
Multiple joins aren't at all necessary.
Edit: to put the solutions in context (I realize it's not obvious):
SELECT *
FROM videos
WHERE video_id IN
( SELECT c1.video_id
FROM videos_categories AS c1
WHERE c1.category_id = IN (1,2,3))
or
SELECT *
FROM videos v
JOIN Vedeos_categories vc ON v.video_id = vc.video_id
WHERE vc.category_id IN (1,2,3)