Create parent/child relationship from static rows - sql

By connection to a webservice, I receive a list of data. Each record in the list contains three category fields, which I save in a product table with the following column markup:
CategoryName SubCategoryName SubSubCategoryName
-----------------------------------------------------
Men Clothing Jeans
Women Jewelry Bracelets
Women Clothing Hoodies
Men Clothing Hoodies
ProductTable: CategoryName | SubCategoryName | SubSubCategoryName
What I want to do, is to extract the categories from the product table and save them to a table with parent/child relationship.
Id ParentId CategoryName
-------------------------------
1 NULL Men
2 1 Clothing
3 2 Jeans
4 NULL Women
5 4 Jewelry
6 5 Bracelets
7 4 Clothing
8 7 Hoodies
9 2 Hoodies
What SQL query can I use to perform this action?

First, create a new table
create table NewCategories (
ID int IDENTITY(1,1) primary key,
ParentID int null,
Name nvarchar(max)
)
Now, Insert all rows into the new table (this will assign the ID's)
insert into NewCategories (Name)
select distinct CategoryName
from OldCategories
insert into NewCategories (Name)
select distinct SubCategoryName
from OldCategories
insert into NewCategories (Name)
select distinct SubSubCategoryName
from OldCategories
Update the NewCategories table, setting the ParentID column, once for the SubCategoryName, and once for the SubSubCategoryName:
update nc2
set ParentID = nc1.ID
from NewCategories nc1
inner join OldCategories oc on oc.CategoryName = nc1.Name
inner join NewCategories nc2 on oc.SubCategoryName = nc2.Name
update nc2
set ParentID = nc1.ID
from NewCategories nc1
inner join OldCategories oc on oc.SubCategoryName = nc1.Name
inner join NewCategories nc2 on oc.SubSubCategoryName = nc2.Name
This assumes that there are no *CategoryName duplicates in the original table.
SQL Fiddle
For duplicates, you can do (slightly more complex)
--insert all categories
insert into NewCategories (Name)
select distinct CategoryName
from OldCategories
--only categories in the "new" table now
insert into NewCategories (ParentID, Name)
select distinct n.ID, o.SubCategoryName
from OldCategories o
inner join NewCategories n on o.CategoryName = n.Name
--now subcategories are items with non-null parents,
-- so we need a double join
insert into NewCategories (ParentID, Name)
select distinct n1.ID, o.SubSubCategoryName
from OldCategories o
inner join NewCategories n1 on o.SubCategoryName = n1.Name
inner join NewCategories n2 on o.CategoryName = n2.Name and n2.ID=n1.ParentID
Here is a new fiddle, modified to handle duplicates

Related

SQL Server : select IF NOT EXISTS comparing date and time in 3 related tables

I have 3 tables, and I want to select all names, unless they have a block that covers a certain time frame of the game they have entered. For example, Jane has a block on game 2, which starts at 11:00, so she is not available for any game that starts at 11:00. She is available at 8:00, so she will be selected for game 1.
Officials tbl
RefId Name
---------------------
1 Jack
2 Sam
3 Jane
Games tbl Blocks tbl
GameId GameDate/Time BlockId RefId GameId
------------------------- ------------------------------
1 8/21/2021 8:00 1 2 1
2 8/21/2021 11:00 2 3 2
3 8/21/2021 11:00
Desired output
If Game 1 is selected: Jack Jane
If Game 2 is selected: Jack, Sam
If Game 3 is selected: Jack, Sam
I have tried similar SQL to the following and I am unable to get desired result:
Select a.GameId, a.GameDate o.Name
From Games a
Left Outer Join Blocks b On a.GameId = b.GameId
Left Outer Join Officials o On b.RefId = o.RefId
Where not exists ---the DateTime of the block = DateTime of the game
Schema:
create table Officials (
[Id] int identity not null,
[Name] nvarchar(255),
constraint PK_Officials primary key (Id)
)
create table Games (
Id int identity not null,
StartOn datetime,
constraint PK_Games primary key (Id)
)
create table Blocks (
Id int identity not null,
OfficialId int not null,
GameId int not null,
constraint PK_Block primary key (Id),
constraint AK_Block unique (OfficialId, GameId),
constraint FK_Block_Officials foreign key (OfficialId) references Officials (Id),
constraint FK_Block_Games foreign key (GameId) references Games (Id),
)
insert into Officials ([Name]) values
('Jack'),
('Sam'),
('Jane')
insert into Games (StartOn) values
('2021-08-21 08:00'),
('2021-08-21 11:00'),
('2021-08-21 11:00')
insert into Blocks (OfficialId, GameId) values
(2, 1),
(3, 2)
Blocks.Id is unnecessary, you can have composite primary key from foreign keys.
I removed unnecessary game prefixes from the game table
I renamed primary keys to be less confusing (for example at first look it is not clear if Officials.RefId is primary or foreign key so I renamed it to Officials.Id.
Query:
-- Game for which we want to display free officials.
-- 1: Jack, Jane
-- 2: Jack, Sam
-- 3: Jack, Sam
declare #gameId int = 3
-- Get datetime when the game starts.
declare #gameStartOn datetime =
(
select g.StartOn
from dbo.Games g
where g.Id = #gameId
)
-- Get all officicals not blocked for specified game start.
select * from dbo.Officials o
where o.Id not in (
-- Get all officials blocked for specified game start.
select o.Id
from dbo.Blocks b
join dbo.Officials o on b.OfficialId = o.Id
join dbo.Games g on b.GameId = g.Id
where g.StartOn = #gameStartOn
)
In common table expression part I have joined Games and Blocks table to get the list of games along with respective the blocked RefId.
select b.RefId,g2.GameId from Games g inner join Blocks b
on g.GameId=b.GameId
inner join Games g2
on g.GameDate_Time=g2.GameDate_Time
Then with not exists I have removed those RefId from Officials table for a particular game.
DB-Fiddle:
Schema and insert statements:
create table Officials( RefId int, Name varchar(50));
insert into Officials values(1, 'Jack');
insert into Officials values(2, 'Sam');
insert into Officials values(3, 'Jane');
create table Games(GameId int, GameDate_Time datetime);
insert into Games values(1, '8/21/2021 8:00');
insert into Games values(2, '8/21/2021 11:00');
insert into Games values(3, '8/21/2021 11:00');
create table Blocks(BlockId int, RefId int, GameId int);
insert into Blocks values(1, 2, 1);
insert into Blocks values(2, 3, 2);
Query for Game 1:
with BlockGames as
(
select b.RefId,g2.GameId from Games g inner join Blocks b
on g.GameId=b.GameId
inner join Games g2
on g.GameDate_Time=g2.GameDate_Time
)
Select * from Officials o
where not exists
(
select 1 from BlockGames bg
where o.RefId=bg.RefId and GameId=1
)
Output:
RefId
Name
1
Jack
3
Jane
Query for Game 2:
with BlockGames as
(
select b.RefId,g2.GameId from Games g inner join Blocks b
on g.GameId=b.GameId
inner join Games g2
on g.GameDate_Time=g2.GameDate_Time
)
Select * from Officials o
where not exists
(
select 1 from BlockGames bg
where o.RefId=bg.RefId and GameId=2
)
Output:
RefId
Name
1
Jack
2
Sam
Query for Game 3:
with BlockGames as
(
select b.RefId,g2.GameId from Games g inner join Blocks b
on g.GameId=b.GameId
inner join Games g2
on g.GameDate_Time=g2.GameDate_Time
)
Select * from Officials o
where not exists
(
select 1 from BlockGames bg
where o.RefId=bg.RefId and GameId=3
)
Output:
RefId
Name
1
Jack
2
Sam
db<>fiddle here
Just add your datetime check to the left join. If the joined table's fields are null, then no such pair exists.
Select a.GameId, a.GameDate o.Name From Games a
Left Outer Join Blocks b On a.GameId = b.GameId or a.DateTime = b.DateTime
Left Outer Join Officials o On b.RefId = o.RefId
where b.DateTime is null
and then there's this approach, to find which officials are available for which games, over all games and officials:
WITH cte1 AS (
SELECT g.GameId, g.GameDate, o.RefId, o.Name
FROM Games AS g
JOIN Officials AS o
ON NOT EXISTS (
SELECT 1 FROM Blocks AS b
JOIN Games AS g2
ON g2.GameDate = g.GameDate
AND b.RefId = o.RefId
AND b.GameId = g2.GameId
)
)
SELECT list.GameId, LEFT(names , LEN(names)-1) AS names
FROM cte1 AS list
CROSS APPLY (
SELECT Name + ','
FROM cte1 AS t
WHERE list.GameId = t.GameId
FOR XML PATH('')
) grp (names)
GROUP BY list.GameId, names
;
Just add WHERE GameId = <n> to the outer query expression to restrict the output to a specific game: WHERE GameId = 2 for just game 2.
Result (updated):
+--------+----------------+
| GameId | Available_Refs |
+--------+----------------+
| 1 | Jack,Jane |
| 2 | Jack,Sam |
| 3 | Jack,Sam |
+--------+----------------+
Working Example (updated)
You need to join Officials with LEFT joins to Blocks and 2 copies of Games and return the unmatched rows:
SELECT DISTINCT o.*
FROM Officials o
LEFT JOIN Blocks b ON b.RefId = o.RefId
LEFT JOIN Games g1 ON g1.GameId = b.GameId
LEFT JOIN Games g2 ON g2.GameDate = g1.GameDate AND g2.GameId = ?
WHERE g2.GameId IS NULL;
Or, with NOT EXISTS:
SELECT o.*
FROM Officials o
WHERE NOT EXISTS (
SELECT 1
FROM Blocks b
INNER JOIN Games g1 ON g1.GameId = b.GameId
INNER JOIN Games g2 ON g2.GameDate = g1.GameDate AND g2.GameId = ?
WHERE b.RefId = o.RefId
);
See the demo.

Sql Server Master/Detail search query

I have 2 tables:
Customers
AccountId Cdescr other customer cols...
1000 ABC
Branch
AccountId BranchId Bdescr other branch cols...
1000 1 AAA
1000 2 BBB
I cannot find a way to achieve this
AccountId BranchId Cdescr Bdescr branchCols... customerCols...
1000 0 ABC NULL NULL VALUES...
1000 1 NULL AAA VALUES... NULL
1000 2 NULL ABC VALUES... NULL
On the customer table missing branchId column should be set to 0 by default.
I need to be able to search for both Cdescr and Bdescr and every match on Customer table should pick up the related branches. If mathing only on the branch table anyway the related customer row should be picked up
Using a FULL OUTER JOIN joining on branchId is actually not working
SELECT *
FROM (
SELECT *, 0 as branchId
FROM Customers
WHERE CONCAT(an_descr1, an_descr2) LIKE '%SEARCH_STRING%'
) a
FULL OUTER JOIN Branch d ON d.branchId = a.branchId
In the current query im not able to search in the branch table
Try this:
DECLARE #tCust TABLE(
AccountId INT
,Cdescr NVARCHAR(10)
);
DECLARE #tBranch TABLE(
AccountId INT
,BranchId INT
,Bdescr NVARCHAR(10)
);
INSERT INTO #tCust VALUES(1000, 'ABC');
INSERT INTO #tBranch VALUES(1000, 1, 'AAA'), (1000, 2, 'BBB');
WITH cte AS(
SELECT ISNULL(b.AccountId, c.AccountId) AccountId, ISNULL(b.BranchId, 0) BranchId, bDescr, cDescr
FROM #tCust c
FULL OUTER JOIN #tBranch b ON b.AccountId = c.AccountId
UNION ALL
SELECT c.AccountId, 0 BranchId, NULL bDescr, cDescr
FROM #tCust c
)
SELECT *
FROM cte
WHERE CONCAT(Bdescr, Cdescr) LIKE '%ABC%'
You need use UNION.
First query you select Customers fields with null value for
Branch fields
Second Query you select Branch fields with null
value for Customers fields.
You need select explicity all the fields.
All the query need of UNION provide fields in same order and same type.
select
AccountId,
0 as BranchId,
Customers.Cdescr,
null as Bdescr ,
Customers.C1,
Customers.C2,
null as B1,
null as B2
from
Customers
union all
select
Branch.AccountId,
Branch.BranchId,
null Cdescr,
Branch.Bdescr ,
null as C1,
null as C2,
Branch.B1,
Branch.B2
from
Branch
Try this using coalesce to convert null to empty string:
SELECT
*
FROM
Customers C
FULL OUTER JOIN Branch B ON
C.AccountId = B.AccountId
where
CONCAT(
coalesce(C.an_descr1,''),
coalesce(C.an_descr2,''),
coalesce(B.another_descr,'')
) LIKE '%SEARCH_STRING%'

Count() where the item counted is a JOIN back to another table

I'm pretty sure I could have done a better job with the title of this post. I wasn't sure how to shrink this question down into a working title...
I have two tables. BillItems and SubItems.
SubItems:
SubItemId ItemId MasterItemId
-----------------------------------
1 50 10
2 50 11
3 60 10
4 60 12
5 70 10
BillItems:
BillItemId ItemId
---------------------
1 10
2 11
3 50
4 60
5 70
Ok, so now, I need to know if BillItems contains any items that are chilren to more than one MasterItem, where the MasterItem is also inside of the BillItems table. I know that sounds confusing, so I'll give an example:
Item 50 is a child item to both Item 10 and Item 11. Item 10 and Item 11 are both in the BillItems table. So, I need Item 50 to show up in the query.
Item 60 is a child to both Item 10 and Item 12. Item 10 is in the BillItems table, but Item 12 is not. So I don't want Item 60 to show up in the query.
Items 10 and 11 are not children in SubItems. So I don't want it to show up in the query.
EDIT:
The desired output, with the above data, would be simply:
ItemId
------
50
I believe this is what you're looking for:
SELECT si.ItemId
FROM SubItems si
WHERE EXISTS (SELECT 1 -- This EXISTS may be omitted if SubItems.ItemId has an enforced FOREIGN KEY reference to BillItems.ItemId
FROM BillItems bi
WHERE bi.ItemId = si.ItemId)
AND EXISTS (SELECT 1
FROM BillItems bi
WHERE bi.ItemId = si.MasterItemId)
GROUP BY si.ItemId
HAVING COUNT(DISTINCT si.MasterItemId) > 1;
You can use a correlated sub-query to check if a masteritemid is present in the billitems table. For id's that aren't present you get a count of 0. You can then group by the itemid and eliminate all such items where there is a missing item and when there are more than 1 items in the itemid group.
select itemid
from (select itemid,(select count(*) from billitems where s.masteritemid=itemid) as present_or_not
from subitems s
) x
group by itemid
having count(case when present_or_not=0 then 1 end)=0 and count(*) > 1
You could do something like this:
var result=from b in context.BillItems
let masters=context.SubItems.Where(s=>s.ItemId==b.ItemId).Select(s=>s.MasterItemId)
where masters.All(e=>context.BillItems.Any(x=>x.ItemId==e))
select b.ItemId;
I noticed later this is a sql question :), but #Casey wants to look how to this in Linq. Another solution (in case you use EF and nav properties) could be:
var result=context.BillItems.Where(b=>b.SubItems.All(s=>context.BillItems.Any(x=>x.ItemId==s.MasterItemId))
.Select(e=>e.ItemId);
Or also doing a group join:
var result=from b in context.BillItems
join s in context.SubItems on b.ItemId equals s.ItemId into g
where g.All(e=>context.BillItems.Any(x=>x.ItemId==e.MasterItemId))
select b.ItemId;
Hoping I understood your question correctly.
Please check below query
select a.ItemId ItemId_a
--, count(distinct a.MasterItemId) , count(distinct b.ItemId)
from SubItems a left join BillItems b
on a.MasterItemId = b.ItemId
group by a.ItemId
having count(distinct a.MasterItemId) = count(distinct b.ItemId)
and count(distinct a.MasterItemId)>1
;
My two cents:
/*
create table BillItems (BillItemId int, ItemId int)
create table Subitems (SubitemId int, ItemId int, MasterItemId int)
insert BillItems values (1,10)
insert BillItems values (2,11)
insert BillItems values (3,50)
insert BillItems values (4,60)
insert BillItems values (5,70)
insert Subitems values(1,50,10)
insert Subitems values(2,50,11)
insert Subitems values(3,60,10)
insert Subitems values(4,60,12)
insert Subitems values(5,70,10)
*/
;with x as (select itemId from subitems group by itemId having count(*) > 1)
, y as (select s.ItemId, b.BillItemId from x join subitems s on x.itemid = s.itemid left join Billitems b on s.MasterItemId = b.ItemID)
select distinct itemid from y
except
select itemid from y where billitemid is null
You need something like this:
SELECT * FROM
(SELECT DISTINCT ItemId FROM TABLE1 t WHERE t.MasterId IN (SELECT ItemId FROM TABLE2)) as MasterInTable2
EXCEPT
(SELECT DISTINCT ItemId FROM TABLE1 t WHERE t.MasterId NOT IN (SELECT ItemId FROM TABLE2)) as
MasterNotInTable2
CREATE TABLE #SubItems (
id INT IDENTITY(1,1),
subItemID INT,
ItemID INT,
MasterItemID INT
)
INSERT INTO #SubItems
VALUES(1,50,10)
INSERT INTO #SubItems
VALUES(2,50,11)
INSERT INTO #SubItems
VALUES(3,60,10)
INSERT INTO #SubItems
VALUES(4,60,12)
INSERT INTO #SubItems
VALUES(5,70,10)
CREATE TABLE #BillItems (
id INT IDENTITY(1,1),
BillItemID INT,
ItemID INT
)
INSERT INTO #BillITems
VALUES(1,10)
INSERT INTO #BillITems
VALUES(2,11)
INSERT INTO #BillITems
VALUES(3,50)
INSERT INTO #BillITems
VALUES(4,60)
INSERT INTO #BillITems
VALUES(5,70)
SELECT A.ItemID
FROM (
SELECT bi.ItemID, COUNT(*) AS CountBill
FROM #BillItems bi
JOIN #SubItems si ON
bi.ItemID = si.ItemID
GROUP BY bi.ItemID
) A
JOIN #SubItems si ON
A.ItemID = si.ItemID
WHERE si.MasterItemID IN (SELECT ItemID FROM #BillItems)
GROUP BY A.ItemID
HAVING COUNT(*) > 1
DROP TABLE #SubItems
DROP TABLE #BillItems

How to join three tables with a NOT IN CLAUSE

Scenario ... A STORE gets a LIST. The 'LIST' basically is a collection of SKUs that the store stocks that needs to be counted.
When a store starts counting the SKUs from a given List assigned to it, the information is saved in another table called 'StoreCycles' which records the ListId, StoreName and the Dates the counting started and completed.
These are my tables.
Table 1. Table with 'Lists' which have a primary key 'ListId'
ListId ListName
1 abc
2 def
3 ghi
Table 2. 'Stores' -- Each list from the above table ('Lists' Table) is assigned to one or more stores.
The ListId in the 'Stores' Table is the primary key of the 'Lists' tabel. The Listid and LineId together make up the foreign key.
ListId LineId StoreName
1 1 StoreA
1 2 StoreD
2 1 StoreB
2 2 StoreC
2 3 StoreA
3 1 StoreA
Table 3. 'StoreCycles' -- that saves when the list assigned to a store was started counting and when it was completed.
ListId StoreName StartDate CompleteDate
1 StoreA 2016-7-22 2016-7-22
2 StoreA 2016-7-22
2 StoreC 2016-7-22
At any time I want to pull up those list names that have not been completed , i.e they have a null complete date.
This is my query:
Select T0.ListId,
T0.ListNaame ,
T2.StartDate
From Lists T0
JOIN Stores T1 On T0.ListId = T1.ListId
LEFT JOIN StoreCycles T2 ON T0.ListId = T2.ListId
WHERE T1.StoreName = 'StoreA'
AND T0.ListId NOT IN (SELECT ListId FROM StoreCycles WHERE CompleteDate IS NOT NULL)
RESULT SHOULD BE >>
ListId ListName StartDate
2 def 2016-7-22
3 ghi NULL
BUT The result that I get is this
ListId ListName StartDate
2 def NULL
2 def NULL
3 ghi NULL
Just passing by and dumping SQL.
Go on, nothing to see.
There is no bug hunt going on here.
declare #Lists table (ListId int primary key, ListName varchar(20));
insert into #lists values (1,'abc'),(2,'def'),(3,'ghi');
declare #Stores table (ListId int, LineId int, StoreName varchar(20));
insert into #Stores values
(1,1,'StoreA'),
(1,2,'StoreD'),
(2,1,'StoreB'),
(2,2,'StoreC'),
(2,3,'StoreA'),
(3,1,'StoreA');
declare #StoreCycles table (ListId int, StoreName varchar(20), StartDate date, CompleteDate date);
insert into #StoreCycles values
(1,'StoreA','2016-7-22','2016-7-22'),
(2,'StoreA','2016-7-22',null),
(2,'StoreC','2016-7-22',null);
SELECT
L.ListId,
L.ListName,
SC.StartDate
FROM #Stores S
JOIN #Lists L On (S.ListId = L.ListId)
LEFT JOIN #StoreCycles SC ON (S.ListId = SC.ListId AND S.StoreName = SC.StoreName)
WHERE S.StoreName = 'StoreA'
AND SC.CompleteDate IS NULL;
Select
l.ListId
,l.ListNaame
,sc.StartDate
From
Lists l
JOIN Stores s
ON l.ListId = s.ListId
AND s.StoreName = 'StoreA'
LEFT JOIN StoreCycles sc
ON s.ListId = sc.ListId
AND s.LineId = sc.LineId
WHERE
sc.CompleteDate IS NULL
You are already doing joins if you build them correctly you will not need a select or not in in your where clause. Also your relationship for StoreCycles seems to be wrong because you are trying to go straight from Lists to StoreCycles but Stores is the intermediary table.
Also just a thought why not use table aliases that will help you know what table you are referring to rather than t0, t1, t2 such as l, s, sc....
Maybe another way to look at it is to return all rows where either the StartDate is NULL (no records) or the CompleteDate is NULL (incomplete records). Also, if the foreign key is using 2 columns you probably want to use both columns in the JOIN.
SELECT T0.ListId,
T0.ListName,
T2.StartDate
FROM Lists T0
JOIN Stores T1
ON T1.ListId = T0.ListId
LEFT JOIN StoreCycles T2
ON T2.ListId = T1.ListId
AND T2.LineId = T1.LineId
WHERE T1.StoreName = 'StoreA'
AND (T2.CompleteDate IS NULL OR T2.StartDate IS NULL)

Select rows that have a specific set of items associated with them through a junction table

Suppose we have the following schema:
CREATE TABLE customers(
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE items(
id INTEGER PRIMARY KEY,
name TEXT
);
CREATE TABLE customers_items(
customerid INTEGER,
itemid INTEGER,
FOREIGN KEY(customerid) REFERENCES customers(id),
FOREIGN KEY(itemid) REFERENCES items(id)
);
Now we insert some example data:
INSERT INTO customers(name) VALUES ('John');
INSERT INTO customers(name) VALUES ('Jane');
INSERT INTO items(name) VALUES ('duck');
INSERT INTO items(name) VALUES ('cake');
Let's assume that John and Jane have id's of 1 and 2 and duck and cake also have id's of 1 and 2.
Let's give a duck to John and both a duck and a cake to Jane.
INSERT INTO customers_items(customerid, itemid) VALUES (1, 1);
INSERT INTO customers_items(customerid, itemid) VALUES (2, 1);
INSERT INTO customers_items(customerid, itemid) VALUES (2, 2);
Now, what I want to do is to run two types of queries:
Select names of customers who have BOTH a duck and a cake (should return 'Jane' only).
Select names of customers that have a duck and DON'T have a cake (should return 'John' only).
For the two type of queries listed, you could use the EXISTS clause. Below is an example query using the exists clause:
SELECT cust.name
from customers AS cust
WHERE EXISTS (
SELECT 1
FROM items
INNER JOIN customers_items ON items.id = customers_items.itemid
INNER JOIN customers on customers_items.customerid = cust.id
WHERE items.name = 'duck')
AND NOT EXISTS (
SELECT 1
FROM items
INNER JOIN customers_items ON items.id = customers_items.itemid
INNER JOIN customers on customers_items.customerid = cust.id
WHERE items.name = 'cake')
Here is a working example: http://sqlfiddle.com/#!6/3d362/2