Sql Server Master/Detail search query - sql

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

Related

SQL joining columns of the same table

I need help on the following SQL query. Let's say we have table_1 with these columns:
number
Customer
list
321
4514
321
2
2
5321
2
5555
If there's a number in the list column, that indicates that is that there is a list of numbers that should refer to that list. Below is a snapshot of how the final table should look. When there's a null value in the customer field it indicates that there is a list, that list number you can find the customers on that list when the number = the list. I need to change the number to make reference to the number the list belongs to.
number
Customer
list
321
4514
321
5321
2
321
5555
2
I've tried with different joins but unsuccessful:
SELECT *
FROM table_1
OUTER JOIN
(SELECT *
FROM TABLE_1
WHERE list IS NOT NULL) AS table_2 ON TABLE_1.list = table_2.list
You say that this is guaranteed to be one level only. So you can have 321->2, but not, say, 321->2->1.
Then, well, let's join and show the joined result:
select
coalesce(ref.number, t1.number) as num,
t1.customer,
ref.list
from table_1 t1
left outer join table_1 ref on ref.list = t1.number
where t1.list is null;
I guess you need to change the data (DML). Here us an example:
DROP TABLE IF EXISTS customer_list
CREATE TABLE customer_list (
number INT,
Customer INT,
list INT
);
INSERT INTO customer_list (number, Customer, list)
VALUES
(321, 4514, NULL),
(321, NULL, 2),
(2, 5321, NULL),
(2, 5555, NULL);
UPDATE A
SET [number] = B.number
,[list] = b.list
FROM customer_list A
INNER JOIN customer_list B
ON A.number = B.list
WHERE B.Customer IS NULL
DELETE FROM customer_list
WHERE Customer IS NULL;
SELECT *
FROM customer_list
If you need only to get the records:
SELECT B.number
,A.customer
,B.List
FROM customer_list A
INNER JOIN customer_list B
ON A.number = B.list
WHERE B.Customer IS NULL

How to use array in a sql join and check if all of the array elements satisfy a condition?

I have two tables, activity and contacts in postgresql.
An activity can have multiple contacts in array form, like this
contact Id = {23,54,34}.
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
Deleted At is column in contacts table to check for deleted contacts. I don't want to use NOT IN.
Activity table
id contact Id
-------------------
16 {60,61,23}
15 {}
5 {59}
6 {}
You can use simple EXISTS predicate testing contacts table with activity.contacts array:
create table activity (
id int primary key,
contacts int[]
)
create table contacts (
id int primary key,
name varchar(10),
deleted boolean
)
insert into activity
select 16 as id, '{1,2,3}'::int[] as contacts union all
select 15, null union all
select 5, '{4}' union all
select 6, '{6, 5}'
insert into contacts
select 1 as id, 'q' as name, false as deleted union all
select 2, 'w', false union all
select 3, 'e', true union all
select 4, 'r', false union all
select 5, 't', true union all
select 6, 'y', true
delete
from activity a
where not exists (
select null
from contacts c
where not(c.deleted)
and c.id = any(a.contacts)
)
2 rows affected
db<>fiddle here
Use UNNEST to convert the nested-array to rows, then do an anti-join to look for broken references:
(An anti-join is when you perform a x LEFT OUTER JOIN y with a WHERE y.KeyColumn IS NULL - this gives you the opposite of an INNER JOIN with the same join criteria).
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFromArray
FROM
crmActivity
)
SELECT
u.ActivityId,
u.ContactIdFromArray AS Missing_ContactId_in_Activity
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
WHERE
c.ContactId IS NULL
ORDER BY
u.ActivityId
I want to delete a activity only if all the contact Ids of that activity are deleted in contacts table and keep the activity if at least one one contact id is still not deleted.
This can be done with a DELETE FROM using WHERE crmActivity.Id IN with a CTE that generates the correct set of bad crmActivity.Id values, via a GROUP BY with the above query:
WITH unnested AS (
SELECT
Id AS ActivityId,
UNNEST( ContactId ) AS ContactIdFK
FROM
crmActivity
)
WITH brokenContacts AS (
SELECT
u.ActivityId,
u.ContactIdFK,
c.ContactId AS ContactIdPK
FROM
unnested AS u
LEFT OUTER JOIN contacts AS c ON
c.ContactId = u.ContactIdFromArray
)
WITH counts AS (
SELECT
ActivityId,
COUNT(*) AS ContactIdCount,
COUNT( CASE WHEN ContactIdPK IS NULL THEN 1 END ) AS BadContactIdCount
FROM
brokenContacts
GROUP BY
ActivityId
)
WITH badActivityIds AS (
SELECT
ActivityId
FROM
counts
WHERE
BadContactIdCount = ContactIdCount
AND
ContactIdCount > 0
)
DELETE FROM
crmActivity
WHERE
ActivityId IN ( SELECT ActivityId FROM badActivityIds );

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.

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

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.