Find parent based on child table keywords - sql

I'm trying to get the parents based on matching keywords using a child table:
AssetKeyword
============
AssetID (int)
KeywordID (int)
I'm trying to find the Assets having entries in the table, for example keywords 3 and 4 and 5.
I've tried subqueries and aggregates but can't get my head around it. Thankful for any help. Those fridays...

This isn't very dynamic i guess..
select
AssetID
from (
select distinct
AssetID,
KeywordID
from AssetKeyword
where
KeywordID in (3,4,5)
) t
group by
AssetID
having
COUNT(*) = 3

You can use EXISTS:
SELECT a.AssetID, a.Col2, ...
FROM dbo.Asset a
WHERE EXISTS
(
SELECT 1 FROM AssetKeyword ak -- it doesn't matter what you "select" here
WHERE ak.AssetID = a.AssetID
AND ak.KeywordID IN (3, 4, 5)
)
This selects all parent records where there is at least one child with at least one of those keywords.

Related

SQL Server query with intersect except or union relational Algebra

I am trying to solve a problem. It seems that of a brain teaser if you ask me.
Given two tables, return only values from the first table when there is a match for EVERY record in a second table. So a record in table 1 must have a match to every record in table 2. If table 2 has fewer than every row I want to exclude it from the final result.
This must be done without using count, having, group by. I must solve it with union, intersect, except, exists.
I am using SQL Server BTW.
CREATE TABLE table1 (id int, lid int)
INSERT INTO table1 VALUES (1, 1),(1, 2),(1,3),(1,4),(2,1),(3,3),(4,4)
CREATE TABLE table2 (lid int)
INSERT INTO table2 VALUES (1),(2),(3),(4)
Table 1:
id lid
--------
1 1
1 2
1 3
1 4
2 1
3 3
4 4
Table2:
lid
-----
1
2
3
4
This method here is "not the way I am supposed to solve it". Frustrating because this solution is so simple and does exactly what it should do. I can't use count, group by, and having.
SELECT id
FROM dbo.table1, dbo.table2
WHERE table1.lid = table2.lid
GROUP BY id
HAVING COUNT(*) = (SELECT COUNT(*) FROM dbo.table2)
So basically I need to find a way to exclude the results from the first table when there is not a full set of matches in table 2. In this example the only value in table 1 with a match to every record in table 2 is 1. 2,3,4 would need to be excluded.
What you're looking for has a name. It's called relational division. It has no equivalent in SQL, although it can be emulated in a variety of ways. Joe Celko has written one of the most complete blog posts about the topic.
Since you must use some of the more basic relational operators in SQL, this could be one solution for you:
SELECT DISTINCT id
FROM table1 t1a
WHERE NOT EXISTS (
SELECT *
FROM table2
WHERE NOT EXISTS (
SELECT *
FROM table1 t1b
WHERE t1a.id = t1b.id
AND t2.lid = t1b.lid
)
)
It reads in English, informally:
Get me all the elements in table1 for which there is no element in table2, which doesn't match such an element from table1
Or also:
Get me the elements from table1, which match all the elements in table2
That's one of the solutions:
select distinct id from table1 AS T1
where not exists(
select lid from table2
except
select lid from table1 where id = T1.id
)

Linking together different columns in a SQL table?

I'm not very experienced with advance SQL queries, I'm familiar with basic statements and basic joins, currently trying to figure out how to write a query that seems to be out of my depth and I haven't been able to find a solution from google so far and I'm hoping somebody might be able to point me in the right direction.
The table I'm working with has an ID column, and a 'parent id' column.
I'm looking for all descendants of ID '1' - rows with a parent ID of '1', rows with a parent ID equal to any row's ID with a parent ID of '1' etc. Currently I've been doing this manually but there are hundreds of descendants so far and I feel like there's a way to put this into one query.
Any help would be appreciated, if this is unclear I can also try to clarify.
EDIT - I got it working with the following query:
with cteMappings as (
select map_id, parent_map_id, map_name
from admin_map
where map_id = '1'
union all
select a.map_id, a.parent_map_id, a.map_name
from admin_map a
inner join cteMappings m
on a.parent_map_id = m.map_id
)
select map_id, parent_map_id, map_name
from cteMappings
Sounds like it can be achieved by Common Table Expression:
DECLARE #temp TABLE (id INT IDENTITY(1, 1), parent_id INT);
INSERT #temp
SELECT NULL
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT NULL
SELECT * FROM #temp
; WITH HierarchyTemp (id, parent_id, depth) AS (
SELECT id, parent_id, 0
FROM #temp
WHERE id = 1
UNION ALL
SELECT t.id, t.parent_id, ht.depth + 1
FROM #temp t
INNER JOIN HierarchyTemp ht ON ht.id = t.parent_id
)
SELECT *
FROM HierarchyTemp
So the above example is creating a table variable with 4 rows:
id parent_id
1 NULL
2 1
3 2
4 NULL
Result of descendent of id '1' (also including itself, but can be excluded with an additional WHERE clause):
id parent_id depth
1 NULL 0
2 1 1
3 2 2

Is there a better way to get the last level of groups from a table?

I have a Group table and within that table, there is a ParentId column that denotes a groups parent within the Group table. The purpose is to build a dynamic menu from these groups. I know I can loop and grab the last child and construct a result set, but I'm curious if there's a more SQL-y way of accomplishing this.
The table has Id, ParentId and Title fields of int, int and varchar.
Basically, a hierarchy may be constructed this way (People is the base group):
People -> Male -> Boy
-> Man
-> Female
I want to grab the last child(ren) of each branch. So, {Boy, Man, Female} in this case.
As I mentioned, getting that info isn't a problem. I'm just looking for a better way of getting it without having to write a bunch of unions and loops where I can basically change the base group and traverse the entire hierarchy outward, dynamically. I'm not really a Db guy, so I don't know if there's a slick way of doing this or not.
To get the leaf levels for one of many hierarchies, you can use a Recursive Common Table Expressions (CTEs) to enumerate the hierarchy, and then check which members are not the parent of another group to filter to the leaves:
Declare #RootID int = 1
;with cte as (
select
Id,
ParentId,
Title
From
Groups
Where
Id = #RootID
Union All
Select
g.Id,
g.ParentId,
g.Title
From
cte c
Inner Join
Groups g
On c.Id = g.ParentID
)
Select
*
From
cte g
Where
Not Exists (
Select
'x'
From
Groups g2
Where
g2.ParentID = g.Id
);
You can also do this with a left join rather than a not exists
http://sqlfiddle.com/#!6/8f1aa/9
Since you are using SQL Server 2012 you could take advantage of hierarchyid; here is an example following Laurence's schema:
CREATE TABLE Groups
(
Id INT NOT NULL
PRIMARY KEY
, Title VARCHAR(20)
, HID HIERARCHYID
)
INSERT INTO Groups
VALUES ( 1, 'People', '/' ),
( 2, 'Male', '/1/' ),
( 3, 'Female', '/2/' ),
( 4, 'Boy', '/1/1/' ),
( 5, 'Man', '/1/2/' );
SELECT Id
, Title
FROM Groups
WHERE HID NOT IN ( SELECT HID.GetAncestor(1)
FROM Groups
WHERE HID.GetAncestor(1) IS NOT NULL )
http://sqlfiddle.com/#!6/00330/1/0
Results:
ID TITLE
3 Female
4 Boy
5 Man

How do I select rows in table (A) sharing the same foreign key (itemId) where multiple rows in table have the values in table B

Sorry about the title, not sure how to describe without example. I trying to implement faceting of attributes in SQL Server 2008.
I have 2 tables. itemAttributes and facetParameters
Assume the following values in itemAttributes
id, itemId, name, value
---------------------------------------
1 1 keywords example1
2 1 keywords example2
3 2 color red
4 2 keywords example1
5 2 keywords example2
6 3 keywords example2
7 3 color red
8 3 color blue
Assume the following values in facetParameters
name value
----------------------
keywords example1
color red
I need to retrieve the (optional: distinct) itemIds where a given itemId has rows that contain all the values in facetParameters.
e.g. given the rows in facetParameters the query should return itemId 2. At the moment I would be using this in a CTE however given that they do not support a number of features I can work around this if there is no solution that works inside a CTE.
I have done a fair bit of sql over the years but this one has really stumped me and the shame is I keep thinking the answer must be simple.
You could join both tables, and use a having clause to ensure that all items match:
select ia.itemid
from #itemAttributes ia
inner join #facetParameters fp
on ia.name = fp.name
and ia.value = fp.value
group by ia.itemid
having count(distinct fp.name) =
(
select count(*) from #facetParameters
)
The count in the having clause assumes that the name uniquely identifies a row in the facetParameters table. If it doesn't, add an identity column to facetParameters, and use count(distinct id_column) instead of count(distinct fp.name).
Here's code to create the data set in the question:
declare #itemAttributes table (id int, itemId int,
name varchar(max), value varchar(max))
insert into #itemAttributes
select 1,1,'keywords','example1'
union all select 2,1,'keywords','example2'
union all select 3,2,'color','red'
union all select 4,2,'keywords','example1'
union all select 5,2,'keywords','example2'
union all select 6,3,'keywords','example2'
union all select 7,3,'color','red'
union all select 8,3,'color','blue'
declare #facetParameters table (name varchar(max), value varchar(max))
insert into #facetParameters
select 'keywords','example1'
union all select 'color','red'

Get the last children from database

My situation:
Table A
(
ID
Parent_Id
TimeStamp
)
The root has Parent_Id null and children has Id of its father.
I simple want to get all LAST children of every Table A.
Father and Children I don't want. (except last one).
Is it possible to build a SQL to get this?
PS: I'm on sql anywhere 11. Maybe an ansi sql can solve this, i'm not sure.
EDIT: (edited to give additional details)
I don't want the last children from an element.
Example:
Id 1
Parent NULL
Id 2
Parent 1
Id 3 (the last child)
Parent 1
Id 4
Parent NULL
Id 5 (the last child)
parent 4
I want to get:
Id 3
Id 5
Using stored function
create function LastChild(in parent integer)
returns integer
begin
declare res integer;
select top 1 id into res from TableA where parent_id = parent order by timeCol desc;
return res;
end
select
select Id, lastchild(id) from TAbleA where parent_id is null
I'll work on another solution without stored function.
EDIT: without stored function:
select Id, (select top 1 id from TableA childs where parent_id = TableA.id order by timeCol desc) from TableA where parent_id = 0
If by "last children" you mean items that themselves have no children (and often referred to as leaf-level items), something like this should do:
SELECT ID
from A
where ID not in (select Parent_Id from A)
The correlated subquery version is a bit tricker to understand, but would work faster on large tables:
SELECT ID
from A OuterReference
where not exists (select 1 from A where Parenti_ID = OuterReference.ID)
("OuterReference" is an alias for table A)
I use SQL Server, but this is pretty basic syntax and should work for you with minimal modification.
select * from a where id not in (select parent_id from table a)
In other words, select everything from table a where the ID of the item is not the parent ID of any other item. This will give you all the leaf nodes of the graph.
EDIT:
Your edit is a bit confusing, and ID's aren't typically used as ordering mechanisms, but regardless, the example you give can be accomplished by this query
SELECT MAX( id )
FROM a
WHERE id NOT IN
(SELECT parent_id
FROM a
WHERE parent_id IS NOT NULL
)
GROUP BY parent_id
I had to update the query a little to get only child categories, for Postgres 9.4
select count(id) from A as outer_ref where not exists(
select 1 from A where parent_id=outer_ref.id) and parent_id is not null;