SQL N To No Releationship Table - sql

I have 3 table by this names
Supplier :For store supplier info
SupplierID
Name
1
Supplier 1
2
Supplier 2
3
Supplier 3
4
Supplier 4
Product : For store product info
ProductID
Name
1
Product 1
2
Product 2
3
Product 3
4
Product 4
5
Product 5
SupplierProduct : For store Product that supplier can supply
ProductID
SupplierID
2
1
3
1
4
1
2
2
3
2
4
2
3
3
4
3
1
4
2
4
4
4
I want to write a query that get a bunch of product ID and return the supplier ID that have all this product ID (N:N Relation) for example get product ID 2,3 and return just supplier ID 1,2

This is a question of Relational Division With Remainder, with multiple divisors.
Firstly, to be able to make good solutions for this, you need your input data in tabular form. You can use a table variable or a Table Valued Parameter for this.
There are many solutions. Here is one common one:
Join the input data to the SupplierProduct table. In your case, you only want the Supplier data, so do this in a subquery.
Group it up and check that the count matches the total count of inputs
DECLARE #ProductInput TABLE (ProductID int);
INSERT #ProductInput (ProductID) VALUES (2),(3);
SELECT *
FROM Supplier s
WHERE (SELECT COUNT(*)
FROM SupplierProduct sp
JOIN #ProductInput pi ON pi.ProductID = sp.ProductID
WHERE sp.SupplierID = s.SupplierID
) = (SELECT COUNT(*) FROM #ProductInput)
;
db<>fiddle
Another common solution is a double NOT EXISTS. This verifies that there are no inputs which do not have a match. It is generally considered to be less efficient.
DECLARE #ProductInput TABLE (ProductID int);
INSERT #ProductInput (ProductID) VALUES (2),(3);
SELECT *
FROM Supplier s
WHERE NOT EXISTS (SELECT 1
FROM #ProductInput pi
WHERE NOT EXISTS (SELECT 1
FROM SupplierProduct sp
WHERE pi.ProductID = sp.ProductID
AND sp.SupplierID = s.SupplierID
)
);

You can use intersect as follows:
select distinct SupplierID
from SupplierProduct
where ProductID = 2
intersect
select SupplierID
from SupplierProduct
where ProductID = 3
Fiddle

Try this:
DECLARE #Supplier TABLE (SupplierID int, Name varchar(50));
INSERT INTO #Supplier VALUES
(1, 'Supplier 1')
, (2, 'Supplier 2')
, (3, 'Supplier 3')
, (4, 'Supplier 4')
;
DECLARE #Product TABLE (ProductID int, Name varchar(50));
INSERT INTO #Product VALUES
(1, 'Product 1')
, (2, 'Product 2')
, (3, 'Product 3')
, (4, 'Product 4')
, (5, 'Product 5')
;
DECLARE #SupplierProduct TABLE (ProductID int, SupplierID int);
INSERT INTO #SupplierProduct VALUES
(2, 1)
, (3, 1)
, (4, 1)
, (2, 2)
, (3, 2)
, (4, 2)
, (3, 3)
, (4, 3)
, (1, 4)
, (2, 4)
, (4, 4)
;
DECLARE #ProductSelection TABLE (ProductID int)
INSERT INTO #ProductSelection
SELECT
ProductID
FROM #Product
WHERE 1=1
-- AND ProductID IN (2, 3) -- returns Suppliers 1, 2
-- AND ProductID IN (3, 4) -- returns Suppliers 1, 2, 3
AND ProductID IN (2, 4) -- returns Suppliers 1, 2, 4
;
WITH SupplierList AS
(
SELECT
RowNo = ROW_NUMBER() OVER (PARTITION BY SP.SupplierID ORDER BY SP.SupplierID)
, S.SupplierID
FROM #SupplierProduct SP
JOIN #ProductSelection P ON P.ProductID = SP.ProductID
JOIN #Supplier S ON S.SupplierID = SP.SupplierID
)
SELECT
SupplierID
FROM SupplierList
WHERE RowNo = (SELECT SUM(1) FROM #ProductSelection)

Related

Find data by multiple Lookup table clauses

declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
The Name Table has more than just 1,2,3 and the list to parse on is dynamic
NameTable
id | name
----------
1 foo
2 bar
3 steak
CharacterTable
id | name
---------
1 tom
2 jerry
3 dog
NameToCharacterTable
id | nameId | characterId
1 1 1
2 1 3
3 1 2
4 2 1
I am looking for a query that will return a character that has two names. For example
With the above data only "tom" will be returned.
SELECT *
FROM nameToCharacterTable
WHERE nameId in (1,2)
The in clause will return every row that has a 1 or a 3. I want to only return the rows that have both a 1 and a 3.
I am stumped I have tried everything I know and do not want to resort to dynamic SQL. Any help would be great
The 1,3 in this example will be a dynamic list of integers. for example it could be 1,3,4,5,.....
Filter out a count of how many times the Character appears in the CharacterToName table matching the list you are providing (which I have assumed you can convert into a table variable or temp table) e.g.
declare #Character table (id int, [name] varchar(12));
insert into #Character (id, [name])
values
(1, 'tom'),
(2, 'jerry'),
(3, 'dog');
declare #NameToCharacter table (id int, nameId int, characterId int);
insert into #NameToCharacter (id, nameId, characterId)
values
(1, 1, 1),
(2, 1, 3),
(3, 1, 2),
(4, 2, 1);
declare #RequiredNames table (nameId int);
insert into #RequiredNames (nameId)
values
(1),
(2);
select *
from #Character C
where (
select count(*)
from #NameToCharacter NC
where NC.characterId = c.id
and NC.nameId in (select nameId from #RequiredNames)
) = 2;
Returns:
id
name
1
tom
Note: Providing DDL+DML as shown here makes it much easier for people to assist you.
This is classic Relational Division With Remainder.
There are a number of different solutions. #DaleK has given you an excellent one: inner-join everything, then check that each set has the right amount. This is normally the fastest solution.
If you want to ensure it works with a dynamic amount of rows, just change the last line to
) = (SELECT COUNT(*) FROM #RequiredNames);
Two other common solutions exist.
Left-join and check that all rows were joined
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM #RequiredNames rn
LEFT JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = COUNT(nc.nameId) -- all rows are joined
);
Double anti-join, in other words: there are no "required" that are "not in the set"
SELECT *
FROM #Character c
WHERE NOT EXISTS (SELECT 1
FROM #RequiredNames rn
WHERE NOT EXISTS (SELECT 1
FROM #NameToCharacter nc
WHERE nc.nameId = rn.nameId AND nc.characterId = c.id
)
);
A variation on the one from the other answer uses a windowed aggregate instead of a subquery. I don't think this is performant, but it may have uses in certain cases.
SELECT *
FROM #Character c
WHERE EXISTS (SELECT 1
FROM (
SELECT *, COUNT(*) OVER () AS cnt
FROM #RequiredNames
) rn
JOIN #NameToCharacter nc ON nc.nameId = rn.nameId AND nc.characterId = c.id
HAVING COUNT(*) = MIN(rn.cnt)
);
db<>fiddle

Reduce amount of available stock of items in a good table based on the sales table

If I have a sales table and a goods table, how can I use T-SQL to reduce the quantity of stock in goods table based on the sales table? I would like to run this once a day.
Sales table
customer id, itemforsaleID, quantity, price total
1 1 5 1.0 5.0
2 3 10 2.0 20.0
Goods table
ItemforsaleID Product name quantity left
1 toy 10
3 fruit 20
Data Preparation
DECLARE #SALES AS TABLE (CustomerId INT, ItemForSaleId INT, Quantity INT, Price Decimal)
DECLARE #GOODS AS TABLE (ItemForSaleId INT, ProductName VARCHAR(100), QantityLeft INT)
INSERT INTO #SALES VALUES (1, 1, 5, 1)
INSERT INTO #SALES VALUES (2, 3, 10, 2)
INSERT INTO #GOODS VALUES (1, 'toy', 10)
INSERT INTO #GOODS VALUES (3, 'fruit', 20)
Data Update
UPDATE G
SET G.QantityLeft = G.QantityLeft - S.Quantity
FROM #GOODS AS G
INNER JOIN (SELECT ItemForSaleId, SUM(Quantity) AS Quantity FROM #SALES GROUP BY ItemForSaleId) AS S
ON G.ItemForSaleId = S.ItemForSaleId
Results
SELECT * FROM #GOODS

SQL Joining on Field with Nulls

I'm trying to match two tables where one of the tables stores multiple values as a string.
In the example below I need to classify each product ordered from the #Orders table with a #NewProduct.NewProductId.
The issue I'm having is sometimes we launch a new product like "Black Shirt",
then later we launch an adaption to that product like "Black Shirt Vneck".
I need to match both changes correctly to the #Orders table. So if the order has Black and Shirt, but not Vneck, it's considered a "Black Shirt", but if the order has Black and Shirt and Vneck, it's considered a "Black Vneck Shirt."
The code below is an example - the current logic I'm using returns duplicates with the Left Join.
Also, assume we can modify the format of #NewProducts but not #Orders.
IF OBJECT_ID('tempdb.dbo.#NewProducts') IS NOT NULL DROP TABLE #NewProducts
CREATE TABLE #NewProducts
(
ProductType VARCHAR(MAX)
, Attribute_1 VARCHAR(MAX)
, Attribute_2 VARCHAR(MAX)
, NewProductId INT
)
INSERT #NewProducts
VALUES
('shirt', 'black', 'NULL', 1),
('shirt', 'black', 'vneck', 2),
('shirt', 'white', 'NULL', 3)
IF OBJECT_ID('tempdb.dbo.#Orders') IS NOT NULL DROP TABLE #Orders
CREATE TABLE #Orders
(
OrderId INT
, ProductType VARCHAR(MAX)
, Attributes VARCHAR(MAX)
)
INSERT #Orders
VALUES
(1, 'shirt', 'black small circleneck'),
(2, 'shirt', 'black large circleneck'),
(3, 'shirt', 'black small vneck'),
(4, 'shirt', 'black small vneck'),
(5, 'shirt', 'white large circleneck'),
(6, 'shirt', 'white small vneck')
SELECT *
FROM #Orders o
LEFT JOIN #NewProducts np
ON o.ProductType = np.ProductType
AND CHARINDEX(np.Attribute_1, o.Attributes) > 0
AND (
CHARINDEX(np.Attribute_2, o.Attributes) > 0
OR np.Attribute_2 = 'NULL'
)
You seem to want the longest overlap:
SELECT *
FROM #Orders o OUTER APPLY
(SELECT Top (1) np.*
FROM #NewProducts np
WHERE o.ProductType = np.ProductType AND
CHARINDEX(np.Attribute_1, o.Attributes) > 0
ORDER BY ((CASE WHEN CHARINDEX(np.Attribute_1, o.Attributes) > 0 THEN 1 ELSE 0 END) +
(CASE WHEN CHARINDEX(np.Attribute_2, o.Attributes) > 0 THEN 1 ELSE 0 END)
) DESC
) np;
I can't say I'm thrilled with the need to do this. It seems like the Orders should contain numeric ids that reference the actual product. However, I can see how something like this is sometimes necessary.
I couldn't get Gordon's answer to work, and was part way through my own response when his came in. His idea of taking the biggest overlap helped. I've tweaked your NewProducts table, so that that side of things is "normalised" even if the Orders table cannot be. Code below or at rextester.com/ERIF13021
create table #NewProduct
(
NewProductID int primary key,
ProductType varchar(max),
ProductName varchar(max)
)
create table #Attribute
(
AttributeID int primary key,
AttributeName varchar(max)
)
create table #ProductAttribute
(
NewProductID int,
AttributeID int
)
insert into #NewProduct
values (1, 'shirt', 'black shirt'),
(2, 'shirt', 'black vneck shirt'),
(3, 'shirt', 'white shirt')
insert into #Attribute
values (1, 'black'),
(2, 'white'),
(3, 'vneck')
insert into #ProductAttribute
values (1,1),
(2,1),
(2,3),
(3,2)
select top 1 with ties
*
from
(
select
o.OrderId,
p.NewProductID,
p.ProductType,
p.ProductName,
o.Attributes,
sum(case when charindex(a.AttributeName,o.Attributes)>0 then 1 else 0 end) as Matches
from
#Orders o
JOIN #Attribute a ON
charindex(a.AttributeName,o.Attributes)>0
JOIN #ProductAttribute pa ON
a.AttributeID = pa.AttributeID
JOIN #NewProduct p ON
pa.NewProductID = p.NewProductID AND
o.ProductType = p.ProductType
group by
o.OrderId,
p.NewProductID,
p.ProductType,
p.ProductName,
o.Attributes
) o2
order by
row_number() over (partition by o2.OrderID order by o2.Matches desc)

Query to select and combine uppermost and lowermost value from tables

We have the following tables on our SQL Server 2012.
Table A (data):
ID, Description
---------------
1 , Bla 1
2 , Bla 2
3 , Bla 3
Table P (data):
ID, ParentID, Name
------------------
1 , NULL , AAA
2 , 3 , CCC
3 , 1 , XXX
Table X (foreign keys A_ID to A.ID and P_ID to P.ID):
ID, A_ID, P_ID
--------------
1 , 1 , 1
2 , 1 , 2
3 , 2 , 1
4 , 2 , 2
5 , 2 , 3
6 , 3 , 1
Question:
We need a query something like:
SELECT ...
WHERE A_ID = 1
which should return this result:
ID, Name, Subname
-----------------
2 , AAA , CCC
Name needs to contain the upper most Name from Table P, i.e. the one that has no ParentID.
Subname needs to contain the bottom most Name from the Table P for which the ID still exists in Table X.
ID needs to contain the ID from Table X where P_ID is the ID of the bottom most child.
Another example:
SELECT ...
WHERE A_ID = 2
should return this result:
ID, Name, Subname
-----------------
4 , AAA , CCC
And
SELECT ...
WHERE A_ID = 3
should return this result:
ID, Name, Subname
-----------------
6 , AAA , NULL
We've tried various queries, but some work only for 'where A_ID = 1' and not for 'where A_ID = 2'. In order to select the lowest level child from P, we've looked at the 'How to select lowest level in hierarchy form table post' which probably comes in handy for the query we're looking for.
A single query would be nice, but we will accept a stored procedure as well.
Thanks in advance!
Information
The ID columns in all tables are primary keys
The ID columns in any given table can be changed to any other value in the sample data, while taking into account the primary and foreign key constraints. (E.g. changing P.ID '2' to '4' also results in the change of X.P_ID's '2' to '4'.) This is to show that ID's are not necessarily in order.
Values in the column P.Name can be any non-null value.
Table P can have multiple rows with ParentId set to null.
Sample Data
Taken from #NEER
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
Try with the below query. I think Table A is not required for getting the desired result.
SELECT TOP 1 First_VALUE(x.ID) OVER(ORDER BY x.ID desc) ID
,First_VALUE(Name) OVER(ORDER BY p.ID) Name
,CASE WHEN First_VALUE(Name) OVER(ORDER BY p.ID) = First_VALUE(Name) OVER(ORDER BY p.ID desc) THEN NULL
ELSE First_VALUE(Name) OVER(ORDER BY p.ID desc) END SubName
FROM [table P] p
JOIN [table X] x
ON p.ID=x.[P_ID]
WHERE x.[A_ID]=3
Try following query
DECLARE #TableA AS TABLE (ID INT,Des NVARCHAR(MAX));
Insert Into #TableA VALUES(1,'Bal 1'); Insert Into #TableA VALUES(2,'Bal 2'); Insert Into #TableA VALUES(3,'Bal 3');
DECLARE #TableP AS TABLE (ID INT,ParentID INT,Name NVARCHAR(MAX));
Insert Into #TableP VALUES(1,Null,'AAA'); Insert Into #TableP VALUES(2,1,'BBB'); Insert Into #TableP VALUES(3,2,'CCC');
DECLARE #TableX AS TABLE (ID INT,A_ID INT,P_ID INT);
Insert Into #TableX Values(1,1,1); Insert Into #TableX Values(2,1,2); Insert Into #TableX Values(3,2,1); Insert Into #TableX Values(4,2,3); Insert Into #TableX Values(5,3,1);
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Inner Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=1
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=2
Order by X.ID Desc
Select Top 1 X.ID,(Select top 1 Name from #TableP Where ParentID is null) Name,P.Name as SubName
from #TableX as X
Left Join #TableP as P On P.ID=x.P_ID And P.ParentID IS Not Null
Where A_ID=3
Order by X.ID Desc
You can try the following
with report as (
select max(x.ID) as ID, min(x.P_ID) as MinP, max(x.P_ID) as MaxP
from X x
where x.A_ID = 1 -- <-- here you can change the value
)
select r.ID,
mn.Name as Name,
case when r.MinP = r.MaxP then null else mx.Name end as Subname
from report r
inner join P mn on mn.ID = r.MinP
inner join P mx on mx.ID = r.MaxP
Hope this will help you
Try it with a GROUP BY:
SELECT x.a_id, max(x.id) AS id, min(p.name) AS name,
CASE WHEN max(p.name) = min(p.name) THEN NULL
ELSE max(p.name) END AS subname
FROM p INNER JOIN x
ON p.id = x.p_id
GROUP BY x.a_id
HAVING x.a_id = 1
Still works with your updated sample data. Tested here: http://sqlfiddle.com/#!9/99597f/1
You can use Recursive CTE as the below:
DECLARE #A TABLE (ID INT, DESCRIPTION NVARCHAR(10))
INSERT INTO #A
VALUES
(1 , 'Bla 1'),
(2 , 'Bla 2'),
(3 , 'Bla 3')
DECLARE #P TABLE (ID INT, ParentID INT, Name NVARCHAR(10))
INSERT INTO #P
VALUES
(1 , NULL , 'AAA'),
(2 , 3 , 'CCC'),
(3 , 1 , 'XXX')
DECLARE #X TABLE (ID INT,A_ID INT,P_ID INT)
INSERT INTO #X
VALUES
(1 , 1 , 1),
(2 , 1 , 2),
(3 , 2 , 1),
(4 , 2 , 2),
(5 , 2 , 3),
(6 , 3 , 1)
DECLARE #A_ID INT = 2
;WITH Parents
AS
(
SELECT
P.ID, P.ParentID, P.Name
FROM #P P WHERE P.ParentID IS NULL
UNION ALL
SELECT
P.ID, Parent.ID, Parent.Name
FROM
#P P INNER JOIN
Parents Parent ON P.ParentID = Parent.ID
), Temp
AS
(
SELECT
X.ID,
Parent.Name Name,
IIF(P.ParentID IS NULL, NULL, P.Name) SubName
FROM
#A A INNER JOIN
#X X ON X.A_ID = A.ID INNER JOIN
#P P ON X.P_ID = P.ID LEFT JOIN
Parents Parent ON P.ParentID = Parent.ID OR (P.ParentID IS NULL AND P.ID = Parent.ID)
WHERE
A.ID = #A_ID
), MainTable
AS
(
SELECT
Temp.ID ,
Temp.Name ,
Temp.SubName,
COUNT(Temp.ID) OVER (PARTITION BY Temp.Name ORDER BY (SELECT NULL)) CountOfRowByParent
FROM
Temp
)
SELECT
MainTable.ID ,
MainTable.Name ,
MainTable.SubName
FROM
MainTable
WHERE
(
MainTable.CountOfRowByParent > 1 AND
MainTable.SubName IS NOT NULL
) OR
MainTable.CountOfRowByParent = 1
Result for 2:
ID Name SubName
4 AAA CCC
5 AAA XXX

SELECT item types not provided by an entity

So I've got some data. There are entities. Entities have an arbitrary number of items. Items can be one of a defined set of types. An entity can have more than one item of a given type. I can get a list of items that an entity has. What I want is to get a list of types that an entity doesn't have an item for.
Here's my schema:
entities
id name
1 Bob
2 Alice
item_types
id name
1 red
2 yellow
3 green
4 blue
5 orange
items
entity_id item_type_id name
1 1 apple
1 2 banana
1 3 lime
1 3 tree
2 3 money
2 5 traffic cone
I would like to query Bob's id (1) and get this list:
4 blue
5 orange
And query Alice's id (2) and get:
1 red
2 yellow
4 blue
It's probably starting me in the face. I'm gonna keep working on it but I bet you SO peeps beat me to it. Thank you kindly for your time.
select id, name
from item_types
where id not in
(select i.item_type_id
from items i
inner join entities e
on e.id = t.entity_id
where e.Name = 'Bob')
or (sometimes faster, but optimizers are getting better all the time):
select disctinct t.id, t.name
from item_types t
left outer join items i
on i.item_type_id = t.id
left outer join entities e
on e.id = i.entity_id
and e.Name = 'Bob'
where e.id is null
for Bob
SELECT
t.id, t.name
FROM
items i
INNER JOIN
entities e ON e.id = i.entity_id
INNER JOIN
item_types t ON t.id = i.item_type_id
WHERE
e.id <> 1
for Alice just swap e.id <> 1 to e.id <> 2
I think this is what you are looking for:
SELECT id, name
FROM item_types
WHERE id NOT IN
(
SELECT DISTINCT item_type_id
FROM items
WHERE entity_id = 1
)
The "entity_id = 1" represents Bob, change it as necessary.
I will rework this to make it better, but here is a working solution
set nocount on
go
drop table #entities
drop table #itemtype
drop table #items
create table #Entities
(
EntityId int,
EntityName varchar (250)
)
create table #ItemType
(
ItemTypeId int,
ItemTypeName varchar (250)
)
create table #Items
(
EntityId int,
ItemTypeId int,
ItemName varchar (250)
)
go
insert into #entities values (1, 'Bob')
insert into #entities values (2, 'Alice')
go
insert into #ItemType values (1, 'red')
insert into #ItemType values (2, 'yellow')
insert into #ItemType values (3, 'green')
insert into #ItemType values (4, 'blue')
insert into #ItemType values (5, 'orange')
go
insert into #Items values (1, 1, 'apple')
insert into #Items values (1, 2, 'banana')
insert into #Items values (1, 3, 'lime')
insert into #Items values (1, 3, 'tree')
insert into #Items values (2, 3, 'money')
insert into #Items values (2, 5, 'traffic cone')
go
;WITH ENTITY AS (
SELECT #Entities.EntityId, EntityName, ItemTypeId, ItemName
FROM #Entities, #Items
WHERE #Entities.EntityId = #Items.EntityId
AND #Entities.EntityName = 'Bob'
)
SELECT #ItemType.* FROM ENTITY
RIGHT JOIN #ItemType ON ENTITY.ItemTypeId = #ItemType.ItemTypeId
WHERE EntityId is NULL
;WITH ENTITY AS (
SELECT #Entities.EntityId, EntityName, ItemTypeId, ItemName
FROM #Entities, #Items
WHERE #Entities.EntityId = #Items.EntityId
AND #Entities.EntityName = 'Alice'
)
SELECT #ItemType.* FROM ENTITY
RIGHT JOIN #ItemType ON ENTITY.ItemTypeId = #ItemType.ItemTypeId
WHERE EntityId is NULL