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

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

Related

SQL Server: Query for products with matching tags

I have been pondering over this for the past few hours but I cannot find a solution.
I have a products in a table, tags in another table and a product/tag link table.
Now I want to retrieve all products which have the same tags as a certain product.
Here are the tables (simplified):
PRODUCT:
id varchar(36) (primary key)
Name varchar(50)
TAG:
id varchar(36) (primary key)
Name varchar(50)
PRODUCTTAG:
id varchar(36) (primary key)
ProductID varchar(36)
TagID varchar(36)
I find quite a few answers here on Stackoverflow talking about returning full and partial matches. However I am looking for a query which only gives full matches.
Example:
Product A has tags 1, 2, 3
Product B has tags 1, 2
Product C has tags 1, 2, 3
Product D has tags 1, 2, 3, 4
If I query for product A, only product C should be found - as it is the only one having exactly the same tags.
Is this even possible?
Yes, yes, try this way:
with aa as (
select count(*) count
from [PRODUCTTAG]
where ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
), bb as
(
select ProductID, count(*) count
from [PRODUCTTAG]
group by ProductID
)
select distinct b.ProductID
from [dbo].[PRODUCTTAG] a join
[dbo].[PRODUCTTAG] b on a.TagID = b.TagID cross join
aa join
bb on aa.count = bb.count and b.ProductID = bb.ProductID
where a.ProductID = '19A947C0-6A0F-4A6F-9675-48FBE30A877D'
declare #PRODUCTTAG table(id int identity(1,1),ProductID int,TagID int)
insert into #PRODUCTTAG VALUES
(1,1),(1,2),(1,3)
,(2,1),(2,2)
,(3,1),(3,2),(3,3)
,(4,1),(4,2),(4,3),(4,4)
;With CTE as
(
select ProductID,count(*)smallCount
FROM #PRODUCTTAG
group by ProductID
)
,CTE1 as
(
select smallCount, count(smallCount)BigCount
from cte
group by smallCount
)
,CTE2 as
(
select * from cTE c
where exists(
select smallCount from cte1 c1
where BigCount>1 and c1.smallCount=c.smallCount
)
)
select * from cte2
--depending upon the output expected join this with #PRODUCTTAG,#Product,#Tag
--like this
--select * from #PRODUCTTAG PT
--where exists(
--select * from cte2 c2 where pt.productid=c2.productid
--)
Or Tell what is final output look like ?
This is a case where I find it simpler to combine all the tags into a single string and compare the strings. But, that is painful in SQL Server until 2016.
So, there is a set based solution:
with pt as (
select pt.*, count(*) over (partition by productid) as cnt
from producttag pt
)
select pt.productid
from pt join
pt pt2
on pt.cnt = pt2.cnt and
pt.productid <> pt2.productid and
pt.tagid = pt2.tagid
where pt2.productid = #x
group by pt.productid, pt.cnt
having count(*) = pt.cnt;
This matches every product to your given product based on the tags. The having clause then ensures that the number of matching tags is the same for the two products. Because the join only considers matching tags, all the tags are the same.

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%'

Combine rows from Mulitple tables into single table

I have one parent table Products with multiple child tables -Hoses,Steeltubes,ElectricCables,FiberOptics.
ProductId -Primary key field in Product table
ProductId- ForeignKey field in Hoses,Steeltubes,ElectricCables,FiberOptics.
Product table has 1 to many relationship with Child tables
I want to combine result of all tables .
For eg - Product P1 has PK field ProductId which is used in all child tables as FK.
If Hoses table has 4 record with ProductId 50 and Steeltubes table has 2 records with ProductId 50 when I perform left join then left join is doing cartesian product of records showing 8 record as result But it should be 4 records .
;with HOSESTEELCTE
as
(
select '' as ModeType, '' as FiberOpticQty , '' as NumberFibers, '' as FiberLength, '' as CableType , '' as Conductorsize , '' as Voltage,'' as ElecticCableLength , s.TubeMaterial , s.TubeQty, s.TubeID , s.WallThickness , s.DWP ,s.Length as SteelLength , h.HoseSeries, h.HoseLength ,h.ProductId
from Hoses h
left join
(
--'' as HoseSeries,'' as HoseLength ,
select TubeMaterial , TubeQty, TubeID , WallThickness , DWP , Length,ProductId from SteelTubes
) s on (s.ProductId = h.ProductId)
) select * from HOSESTEELCTE
Assuming there are no relationships between child tables and you simply want a list of all child entities which make up a product you could generate a cte which has a number of rows which are equal to the largest number of entries across all the child tables for a product. In the example below I have used a dates table to simplify the example.
so for this data
create table products(pid int);
insert into products values
(1),(2);
create table hoses (pid int,descr varchar(2));
insert into hoses values (1,'h1'),(1,'h2'),(1,'h3'),(1,'h4');
create table steeltubes (pid int,descr varchar(2));
insert into steeltubes values (1,'t1'),(1,'t2');
create table electriccables(pid int,descr varchar(2));
truncate table electriccables
insert into electriccables values (1,'e1'),(1,'e2'),(1,'e3'),(2,'e1');
this cte
;with cte as
(select row_number() over(partition by p.pid order by datekey) rn, p.pid
from dimdate, products p
where datekey < 20050105)
select * from cte
create a cartesian join (one of the rare ocassions where an implicit join helps) pid to rn
result
rn pid
-------------------- -----------
1 1
2 1
3 1
4 1
1 2
2 2
3 2
4 2
And if we add the child tables
;with cte as
(select row_number() over(partition by p.pid order by datekey) rn, p.pid
from dimdate, products p
where datekey < 20050106)
select c.pid,h.descr hoses,s.descr steeltubes,e.descr electriccables from cte c
left join (select h.*, row_number() over(order by h.pid) rn from hoses h) h on h.rn = c.rn and h.pid = c.pid
left join (select s.*, row_number() over(order by s.pid) rn from steeltubes s) s on s.rn = c.rn and s.pid = c.pid
left join (select e.*, row_number() over(order by e.pid) rn from electriccables e) e on e.rn = c.rn and e.pid = c.pid
where h.rn is not null or s.rn is not null or e.rn is not null
order by c.pid,c.rn
we get this
pid hoses steeltubes electriccables
----------- ----- ---------- --------------
1 h1 t1 e1
1 h2 t2 e2
1 h3 NULL e3
1 h4 NULL NULL
2 NULL NULL e1
In fact, the result having 8 rows can be expected to be the result, since your four records are joined with the first record in the other table and then your four records are joined with the second record of the other table, making it 4 + 4 = 8.
The very fact that you expect 4 records to be in the result instead of 8 shows that you want to use some kind of grouping. You can group your inner query issued for SteelTubes by ProductId, but then you will need to use aggregate functions for the other columns. Since you have only explained the structure of the desired output, but not the semantics, I am not able with my current knowledge about your problem to determine what aggregations you need.
Once you find out the answer for the first table, you will be able to easily add the other tables into the selection as well, but in case of large data you might get some scaling problems, so you might want to have a table where you store these groups, maintain it when something changes and use it for these selections.

how can select unique row using where/having cluse and compare with another table

i cant understand how can take unique column (remove duplication) from a table
which compare with another table data.
in my case
i have two table
i want to get unique rows from tblproduct after compireing with tblviewer as
[in table viewer first taking viewerid after that taking productid in viewer table afterthat compire with tblproduct.
actualy like that
if i take vieweris=123 two row productid select 12001&11001 after that this tblproduct productid and finaly taking the row from tblproduct which maching.
select *
from tblproduct
where productid =
(
select distinct(productid)
from tblviewer
where viewerid = 123
)
There are a few ways to do this. You can do a standard INNER JOIN to the table to filter the results:
Select Distinct P.*
From tblProduct P
Join tblViewer V On V.ProductId = P.ProductId
Where V.ViewerId = 123
Alternatively, you could use EXISTS as well - this eliminates the need to use a DISTINCT altogether:
Select *
From tblProduct P
Where Exists
(
Select *
From tblViewer V
Where V.ProductId = P.ProductId
And V.ViewerId = 123
)
Or, you could also use an IN, as suggested by the other answers:
Select *
From tblProduct
Where ProductId In
(
Select ProductId
From tblViewer
Where ViewerId = 123
)
I think you just want to use an IN clause, you will not need to use distinct
select *
from tblproduct
where productid in
(
select productid
from tblviewer
where viewerid = 123
)
I'm not sure what you're asking, but I think it is,
select *
from tblproduct
where productid in
(
select distinct(productid)
from tblviewer
)

Create parent/child relationship from static rows

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