How to merge list from SQL Server stored procedure - sql-server

ALTER PROCEDURE [dbo].[spGetItemCategories]
AS
BEGIN
SET NOCOUNT ON;
--SELECT * FROM ItemCategories
SELECT
IC.Id, IC.Name ,C.Id AS CompanyId, C.Name AS CompanName
FROM
ItemCategories IC
JOIN
CompanyItems CI ON IC.Id = CI.ItemCategoryId
JOIN
Companies C ON CI.CompanyId = C.Id
--WHERE CI.CompanyId IN (SELECT TOP(100)* FROM Companies C)
END
This displays data like:
4 sdfs 14 Nestle
4 sdfs 15 Unilever
but I want to get like this:
4 sdfs 14 Nestle
15 Unilever

You can check this method but the same data.
declare #mytable table (compid int,compname varchar(20),itemid int, itemdesc varchar(20))
insert into #mytable
values
(1,'Company A',100,'Nestle'),
(1,'Company A',200,'UniLever'),
(2,'Company B',300,'Citrix'),
(2,'Company B',400,'SQL'),
(2,'Company B',500,'Oracle'),
(1,'Company B',600,'Microsoft')
select
iif(left(m1.ord_id,1)>1,NULL,m.compid) [CompID],
iif(left(m1.ord_id,1)>1,NULL,m.compname) [CompName],
m.itemid,
m.itemdesc
from #mytable m
inner join (
select distinct compid,row_number() over (partition by compid order by itemid) [ord_id], itemid
from #mytable) m1
on m.compid = m1.compid and m.itemid = m1.itemid
or CTE
;with cte as
(
select distinct compid,row_number() over (partition by compid order by itemid) [ord_id], itemid
from #mytable
)
select
iif(left(m1.ord_id,1)>1,NULL,m.compid) [CompID],
iif(left(m1.ord_id,1)>1,NULL,m.compname) [CompName],
m.itemid,
m.itemdesc
from #mytable m
inner join cte m1
on m.compid = m1.compid and m.itemid = m1.itemid
if you are not happy with nulls replace the fields
iif(left(m1.ord_id,1)>1,'',cast(m.compid as varchar)) [CompID],
iif(left(m1.ord_id,1)>1,'',m.compname) [CompName],
Result
CompID CompName itemid itemdesc
1 Company A 100 Nestle
200 UniLever
600 Microsoft
2 Company B 300 Citrix
400 SQL
500 Oracle

Related

T-SQL Group by multiple batches of 2

So the wonderful people here on stackoverflow helped me with a "find consecutive failures" type query. (Status =4 is a failure). I thought I had cracked the second part of my problem because my test case seems to work fine but whenever I run it on our test environment I get dodgy results, so I must be doing something wrong. The goal is to find X number of consecutive failures. So the below is set to find 2 consecutive failures. I'm using SQL Server 2008 R2
DECLARE #t TABLE (
[InstructionId] INT,
[InstructionDetailId] INT,
[Sequence] INT,
[Status] INT
)
INSERT INTO #t SELECT 222,111,1, 2
INSERT INTO #t SELECT 222,112,2,2
INSERT INTO #t SELECT 222,113,3,4
INSERT INTO #t SELECT 222,114,4,4
INSERT INTO #t SELECT 222,115,5,2
INSERT INTO #t SELECT 222,116,6,4
INSERT INTO #t SELECT 222,117,7,2
INSERT INTO #t SELECT 222,118,8,4
INSERT INTO #t SELECT 222,119,9,4
INSERT INTO #t SELECT 222,120,10,2
INSERT INTO #t SELECT 222,121,11,2
INSERT INTO #t SELECT 222,124,12,4
INSERT INTO #t SELECT 222,126,13,4
INSERT INTO #t SELECT 222,128,14,4
INSERT INTO #t SELECT 223,126,13,4
INSERT INTO #t SELECT 223,128,14,4
INSERT INTO #t SELECT 223,129,15,2
INSERT INTO #t SELECT 223,130,16,4
INSERT INTO #t SELECT 224,111,17,4
INSERT INTO #t SELECT 224,112,18,4
INSERT INTO #t SELECT 223,160,33,4
INSERT INTO #t SELECT 223,161,34,4
INSERT INTO #t SELECT 223,162,35,4
INSERT INTO #t SELECT 223,163,40,4
;with HardcoreCTE AS
(
select t.*,
t.[Sequence] - ROW_NUMBER() OVER(PARTITION BY t.instructionId ORDER BY
t.InstructionDetailId) AS ItemCount
from #t t outer apply
( select top (1) t1.*
from #t t1
where t1.InstructionId = t.InstructionId and
t1.Sequence < t.Sequence
order by t1.Sequence desc
) t1 outer apply
( select top (1) t2.*
from #t t2
where t2.InstructionId = t.InstructionId and
t2.Sequence > t.Sequence
order by t2.Sequence
) t2
where t.status = 4 and (t.status = t1.status or t.status = t2.status)
)
,
HardCoreCTE2
AS
(
select *, Count(1) OVER(PARTITION BY ItemCount) AS ItemCount2 from
HardcoreCTE
)
select * from HardCoreCTE2
where ItemCount2 =2
So the above works brilliants to find results where there are specifically only 2 consecutive failures with these
results:
Now from the above results the only ones it finds are the records where there are 2 consecutive failures but whenever I convert the above to the actual test environment tables it doesn't seem to work.
Test Env Results: As you can see for the "InstructionId" of 2518380 it brought back one record and the for "InstructionId" 2614351. It's meant to bring back sets of 2 records.
Test Env Query: (Pretty much identical)
;with InitialDataCTE
AS
(
SELECT Instruction.InstructionID,InstructionDetail.InstructionDetailID,
InstructionDetail.InstructionDetailStatusID AS [Status],
InstructionDetail.Sequence
FROM Instruction INNER JOIN
InstructionDetail ON Instruction.InstructionID =
InstructionDetail.InstructionID
where InstructionDetailStatusID =4
and InstructionDetail.PaymentDateOriginal between '2015-01-05'
AND '2018-09-08'
),
HardCoreCTE
AS
(
select t.*,
t.Sequence - ROW_NUMBER() OVER(PARTITION BY t.instructionId ORDER BY
t.InstructionDetailId) AS ItemCount
from InitialDataCTE t outer apply
( select top (1) t1.*
from InitialDataCTE t1
where t1.InstructionId = t.InstructionID and
t1.Sequence < t.Sequence
order by t1.Sequence desc
) t1 outer apply
( select top (1) t2.*
from InitialDataCTE t2
where t2.InstructionId = t.InstructionId and
t2.Sequence > t.Sequence
order by t2.Sequence
) t2
where t.Status = 4 and (t.Status = t1.Status or t.Status = t2.Status)
)
,
HardCoreCTE2
AS
(
select *, Count(1) OVER(PARTITION BY ItemCount) AS ItemCount2 from
HardCoreCTE
)
select * from HardCoreCTE2
where ItemCount2 =2
order by InstructionID, Sequence
Really appreciate if someone can tell me where I am going wrong, I've been messing around with variations of the Count(*) but nothing successful yet. Thanx alot
I came to the next query:
with
a as (
select *,
row_number() over(partition by InstructionId order by Sequence)-
row_number() over(partition by InstructionId, [Status] order by Sequence) g
from #t
),
b as (
select *,
count(*) over(partition by InstructionId, [Status], g) c
from a
where [Status] = 4
)
select *
from b
where c > 2
order by 1, 3;
For your test data, I got the following result:
InstructionId InstructionDetailId Sequence Status g c
222 224 312 4 6 3
222 226 413 4 6 3
222 228 514 4 6 3
223 161 84 4 2 3
223 162 95 4 2 3
223 163 140 4 2 3
You can test this query here.

Select from table only if all dynamic parameter values match

I have the following animal table:
id | action
------------------
duck | cuack
duck | fly
duck | swim
pelican | fly
pelican | swim
I want to create a stored procedure and pass a group of values into a single parameter:
EXEC GuessAnimalName 'cuack,fly,swim'
Result:
duck
So, if it cuacks, it flyes and it swims then it's a duck. But also:
EXEC GuessAnimalName 'fly,swim'
Result:
pelican
---
EXEC GuessAnimalName 'fly'
Result:
No results
Parameter's number is dynamic.
In order to guess the animal's name all actions provided must match or be found in animal table.
This is what I have so far:
DECLARE #animal AS TABLE
(
[id] nvarchar(8),
[action] nvarchar(16)
)
INSERT INTO #animal VALUES('duck','cuack')
INSERT INTO #animal VALUES('duck','fly')
INSERT INTO #animal VALUES('duck','swim')
INSERT INTO #animal VALUES('pelican','fly')
INSERT INTO #animal VALUES('pelican','swim')
-- Parameter simulation
DECLARE #params AS TABLE
(
[action] nvarchar(16)
)
INSERT INTO #params VALUES('cuack')
INSERT INTO #params VALUES('fly')
INSERT INTO #params VALUES('swim')
SELECT
a.[id]
FROM
#animal a
INNER JOIN
#params p
ON
a.[action] = p.[action]
GROUP BY
a.[id]
HAVING COUNT(a.[action]) IN (SELECT COUNT([action]) FROM #animal GROUP BY [id])
Which gives the result:
Result:
--------
duck
--------
pelican
It should return just duck.
convert this to a stored proc using RANK
declare #lookfor varchar(100) = 'swim,fly'
select id from
(select
id, rank() over (order by cnt desc) rank_ -- get rank based on the number of match where should be the same number of rows
from
(
SELECT
a.id, count(1) cnt -- identify how many matches
FROM
#animal a
INNER JOIN
#params p
ON
a.[action] = p.[action]
where charindex(p.action,#lookfor) > 0
group by a.id
having count(1) = (select count(1) from #animal x where a.id = x.id)) -- only get the animal with the same number of matches and rows
y)
z where rank_ = 1 -- display only with the most matches which should be the same number of rows matched
you don't need #params here
select id from
(select
id, rank() over (order by cnt desc) rank_
from
(
SELECT
a.id, count(1) cnt
FROM
#animal a
where charindex(a.action,#lookfor) > 0
group by a.id
having count(1) = (select count(1) from #animal x where a.id = x.id))
y)
z where rank_ = 1

Unexpected result using CTE to perform a random join on two tables for all rows one-to-many

I am attempting to randomly join the rows of two tables (TableA and TableB) such that each row in TableA is joined to only one row in TableB and every row in TableB is joined to at least one row in TableA.
For example, a random join of TableA with 5 distinct rows and TableB with 3 distinct rows should result in something like this:
TableA TableB
1 3
2 1
3 1
4 2
5 1
However, sometimes not all the rows from TableB are included in the final result; so in the example above might have row 2 from TableB missing because in its place is either row 1 or 3 joined to row 4 on TableA. You can see this occur by executing the script a number of times and checking the result. It seems that it is necessary for some reason to use an interim table (#Q) to be able to ensure that a correct result is returned which has all rows from both TableA and TableB.
Can someone please explain why this is happening?
Also, can someone please advise on what would be a better way to get the desired result?
I understand that sometimes no result is returned due to a failure of some kind in the cross apply and ordering which i have yet to identify and goes to the point that I am sure there is a better way to perform this operation. I hope that makes sense. Thanks in advance!
declare #TableA table (
ID int
);
declare #TableB table (
ID int
);
declare #Q table (
RN int,
TableAID int,
TableBID int
);
with cte as (
select
1 as ID
union all
select
ID + 1
from cte
where ID < 5
)
insert #TableA (ID)
select ID from cte;
with cte as (
select
1 as ID
union all
select
ID + 1
from cte
where ID < 3
)
insert #TableB (ID)
select ID from cte;
select * from #TableA;
select * from #TableB;
with cte as (
select
row_number() over (partition by TableAID order by newid()) as RN,
TableAID,
TableBID
from (
select
a.ID as TableAID,
b.ID as TableBID
from #TableA as a
cross apply #TableB as b
) as M
)
select --All rows from TableB not always included
TableAID,
TableBID
from cte
where RN in (
select
top 1
iCTE.RN
from cte as iCTE
group by iCTE.RN
having count(distinct iCTE.TableBID) = (
select count(1) from #TableB
)
)
order by TableAID;
with cte as (
select
row_number() over (partition by TableAID order by newid()) as RN,
TableAID,
TableBID
from (
select
a.ID as TableAID,
b.ID as TableBID
from #TableA as a
cross apply #TableB as b
) as M
)
insert #Q
select
RN,
TableAID,
TableBID
from cte;
select * from #Q;
select --All rows from both TableA and TableB included
TableAID,
TableBID
from #Q
where RN in (
select
top 1
iQ.RN
from #Q as iQ
group by iQ.RN
having count(distinct iQ.TableBID) = (
select count(1) from #TableB
)
)
order by TableAID;
See if this gives you what you're looking for...
DECLARE
#CountA INT = (SELECT COUNT(*) FROM #TableA ta),
#CountB INT = (SELECT COUNT(*) FROM #TableB tb),
#MinCount INT;
SELECT #MinCount = CASE WHEN #CountA < #CountB THEN #CountA ELSE #CountB END;
WITH
cte_A1 AS (
SELECT
*,
rn = ROW_NUMBER() OVER (ORDER BY NEWID())
FROM
#TableA ta
),
cte_B1 AS (
SELECT
*,
rn = ROW_NUMBER() OVER (ORDER BY NEWID())
FROM
#TableB tb
),
cte_A2 AS (
SELECT
a1.ID,
rn = CASE WHEN a1.rn > #MinCount THEN a1.rn - #MinCount ELSE a1.rn end
FROM
cte_A1 a1
),
cte_B2 AS (
SELECT
b1.ID,
rn = CASE WHEN b1.rn > #MinCount THEN b1.rn - #MinCount ELSE b1.rn end
FROM
cte_B1 b1
)
SELECT
A = a.ID,
B = b.ID
FROM
cte_A2 a
JOIN cte_B2 b
ON a.rn = b.rn;

Left join with Sum Clause with more than 1 table gives incorrect Sum

I am trying to get the Sum of rows while applying a left join with more than 1 table. It seems it is creating a matrix of result which results in wrong sum function.
Example:
First Table: Customer
Second Table: TotalAssets
Third Table: TotalLiability
Table Structure:
Customer
CustID(int) CustomerName(varchar)
1 Abc
2 Def
3 Ghi
TotalAssets
CustID Amount
1 2000
1 1000
2 600
TotalLiability
CustID Amount
1 1000
1 1000
2 800
Output Expected
CustID TotalAssets TotalLiability
1 3000 2000
2 600 800
Current Query
Select c.CustID , Sum(a.Amount) , Sum(l.Amount) From Customer c
left join TotalAssests a on a.CustID = c.CustID
left join TotalLiability l on l.CustID = c.CustID
Group by c.CustID
The problem with this current query is the sum is not correct as i think the first left join create a first set with multiple records and then second one is applied.
Any help is appreciated
UPDATE:
I find some luck by following method but it seems a bad/hacky option as in my case i have over 7-8 elements in group by and adding more left clauses results in query difficult to manage.
New Query which is resulting correct result but looks very bad to maintains
Select Set1.CustID , Set1.TotalAssets, Sum(l.Amount) from (Select c.CustID , Sum(a.Amount) as TotalAssets From Customer c
left join TotalAssests a on a.CustID = c.CustID
Group by c.CustID)Set1
left join TotalLiability l on l.CustID = Set1.CustID.
Group by Set1.CustID , Set1.TotalAssets
I think this gets you what you want with minimum complexity:
select c.CustId, isnull(a.Amount, 0) as TotalAssets, isnull(l.Amount, 0) as TotalLiability
from Customers c
left join (
select CustId, sum(Amount) as Amount from TotalAssets group by CustId
) a on a.CustId = c.CustId
left join (
select CustId, sum(Amount) as Amount from TotalLiability group by CustId
) l on l.CustId = c.CustId
You need to group/sum the two tables separately, since the data in them is independent. Left-joining both to the customers table ensures that customers with no entries in either/both tables are still reported.
This should work:
Select c.CustID
, (select sum(a.amount) from TotalAssests a where a.CustId = c.CustID) as SumAsset
, (select Sum(l.Amount) TotalLiability l where l.CustID = c.CustID) as SumLiability
From Customer c
Hope the below works with less maintenance,
DECLARE #Customer TABLE (CustID int, CustomerName varchar(50)) DECLARE #TotalAssets TABLE (CustID int, Amount INT) DECLARE #TotalLiability TABLE (CustID int, Amount INT)
INSERT INTO #Customer
SELECT 1,
'ABC'
UNION
SELECT 2,
'DEF'
UNION
SELECT 3,
'GHI'
--Select * From #Customer
INSERT INTO #TotalAssets
SELECT 1,
2000
UNION
SELECT 1,
1000
UNION
SELECT 2,
600
--Select * From #TotalAssets
INSERT INTO #TotalLiability
SELECT 1,
1000
UNION
SELECT 1,
1000
UNION
SELECT 2,
800
--Select * From #TotalLiability
SELECT *
FROM #Customer
SELECT C.CustID,
C.CustomerName,
Sum(A.Amount) TotalAssets,
Sum(L.Amount) TotalLiability
FROM #Customer C
JOIN #TotalAssets A ON C.CustID = A.CustID
JOIN #TotalLiability L ON C.CustId = L.CustID
GROUP BY C.CustID,
C.CustomerName

Trouble deleting duplicate records using partition and rank() in sql server

I am trying to identify duplicate records and then delete one of the duplicate record using PARTITION and RANK() n SQL Server 2008. I condition to delete duplicate record is that it should not be referenced in another table.
I have a Language table that has some duplicate languages. Employee table has employees and mapping to language. I have to delete one of the duplicate records if that Language id is not being mapped in Employee table.
CREATE TABLE MY_LANGUAGE (LANGUAGEID INT, LANGUAGENAME VARCHAR(20))
CREATE TABLE MY_EMPLOYEE (EMPID INT, NAME VARCHAR(20), LANGUAGEID INT)
INSERT INTO MY_LANGUAGE VALUES(1, 'ENGLISH')
INSERT INTO MY_LANGUAGE VALUES(2, 'FRENCH')
INSERT INTO MY_LANGUAGE VALUES(3, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(4, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(5, 'ITALIAN')
INSERT INTO MY_LANGUAGE VALUES(6, 'GERMAN')
INSERT INTO MY_LANGUAGE VALUES(7, 'SPANISH')
INSERT INTO MY_EMPLOYEE VALUES (10, 'GLEN', 1)
INSERT INTO MY_EMPLOYEE VALUES (20, 'PETER', 2)
INSERT INTO MY_EMPLOYEE VALUES (30, 'MARIA', 3)
If you see, I have two languages that are duplicate and one of them is being used by an employee. I want to delete language ids 4 and 5.
LANGUAGENAME LANGUAGEID EMPNAME
GERMAN 4
GERMAN 6
ITALIAN 3 MARIA
ITALIAN 5
I have tried to create a select statement to return what I want to delete:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * FROM CTE WHERE RANKING = 1
This return the following
LANGUAGENAME LANGUAGEID RANKING
GERMAN 4 1
ITALIAN 5 1
When I try to delete I get an error:
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
DELETE FROM CTE WHERE RANKING = 1
Error that I get is:
Msg 4405, Level 16, State 1, Line 1
View or function 'CTE' is not updatable because the modification affects multiple base tables.
Any ideas how to fix this or may be it can be simplified. Thanks to #Szymon for showing a temp table solution but I am hoping to get a solution without temp tables (if possible).
Query:
DELETE ll
FROM MY_LANGUAGE ll
JOIN (SELECT L.LANGUAGENAME,
L.LANGUAGEID,
ROW_NUMBER()OVER(PARTITION BY L.LANGUAGENAME, e.EMPID
ORDER BY L.LANGUAGEID ASC) rnk,
COUNT(*)OVER(PARTITION BY L.LANGUAGENAME) cnt,
e.EMPID
FROM MY_LANGUAGE l
LEFT JOIN MY_EMPLOYEE e ON e.LANGUAGEID = l.LANGUAGEID) a
ON ll.LANGUAGEID = a.LANGUAGEID
AND a.rnk = 1
AND a.cnt > 1
and a.EMPID IS NULL
Result:
| LANGUAGEID | LANGUAGENAME |
|------------|--------------|
| 1 | ENGLISH |
| 2 | FRENCH |
| 3 | ITALIAN |
| 6 | GERMAN |
| 7 | SPANISH |
You can get the records from your CTE query into a temporary table and then delete based on that table.
WITH CTE AS (
SELECT L.LANGUAGENAME, L.LANGUAGEID, RANK() OVER(PARTITION BY L.LANGUAGENAME ORDER BY L.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE L
INNER JOIN (
SELECT LANGUAGENAME, COUNT(*) AS DUPECOUNT
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(*) > 1
) LC ON L.LANGUAGENAME = LC.LANGUAGENAME
WHERE NOT EXISTS (SELECT 1 FROM MY_EMPLOYEE WHERE MY_EMPLOYEE.LANGUAGEID = L.LANGUAGEID))
SELECT * INTO #temp FROM CTE WHERE RANKING = 1
And then use records from #temp to delete from the original table.
Try this..
;WITH CTE AS (
SELECT t.LANGUAGEID, LANGUAGENAME, RANK() OVER(PARTITION BY T.LANGUAGENAME ORDER BY T.LANGUAGEID) AS RANKING
FROM MY_LANGUAGE T
WHERE T.LANGUAGEID IN (
SELECT L.LANGUAGEID
FROM MY_LANGUAGE L LEFT JOIN MY_EMPLOYEE E
ON L.LANGUAGEID = E.LANGUAGEID
WHERE E.LANGUAGEID IS NULL)
)
DELETE FROM CTE
WHERE RANKING = 1 AND (CTE.LANGUAGENAME IN (SELECT LANGUAGENAME
FROM MY_LANGUAGE
GROUP BY LANGUAGENAME
HAVING COUNT(LANGUAGENAME) > 1))

Resources