Recursive Query and INNER JOIN - sql

I am stuck with a query that is using recursive and wondering if you guys can help me out.
I have this query below and it is based on the ShipQuantity, then it lists the number of records. For example, mfgPN "ABC123" has a ShipQuantity of 4, it will list 4 records with a number 1,2,3, 4.
WITH feedInfo
AS (
SELECT df1.RecID, MfgPN, LinkID, ShipQuantity, 1 AS Number
FROM EXT_DistributorFeed df1
WHERE 1 = 1
AND df1.mfgPN IN ('ABC1', 'ABC2')
UNION ALL
SELECT df2.RecID, df2.MfgPN, df2.LinkID, df2.ShipQuantity, feedInfo.number + 1 AS Number
FROM EXT_DistributorFeed df2
INNER JOIN feedInfo ON df2.RecID = feedInfo.RecID
WHERE 1 = 1
AND number < feedInfo.ShipQuantity
AND df2.mfgPN IN ('ABC1', 'ABC2')
)
Select feedInfo.*
From feedInfo
OPTION (maxrecursion 20000);
Let's say the result is
RecID MfgPN LinkID ShipQuantity Number
101 ABC1 L11111 4 1
102 ABC1 L11111 4 2
103 ABC1 L11111 4 3
104 ABC1 L11111 4 4
105 ABC2 L22222 2 1
106 ABC2 L22222 2 2
Now, I have another table "EXT_DistributorFeedDetail" where it may contain serial# (some part# have serial# and some part# don't have). This table has only two columns: (1) LinkID and (2)SerialNo. Like this:
EXT_DistributorFeedDetail
LinkID SerialNo
L22222 S999999
L22222 S888888
I would like to join the feedInfo with EXT_DistributorFeedDetail table to get the result like this:
RecID MfgPN LinkID ShipQuantity Number Serial
101 ABC1 L11111 4 1 NULL
102 ABC1 L11111 4 2 NULL
103 ABC1 L11111 4 3 NULL
104 ABC1 L11111 4 4 NULL
105 ABC2 L22222 2 1 S99999
106 ABC2 L22222 2 2 S88888
Any expert out there can help would be greatly appreciated.
Thank you,

Looks like you're trying to match up the LinkID and Number from the recursive query to the LinkID and a Row Number in the EXT_DistributorFeedDetail table
WITH feedInfo
AS (
SELECT df1.RecID, MfgPN, LinkID, ShipQuantity, 1 AS Number
FROM EXT_DistributorFeed df1
WHERE 1 = 1
AND df1.mfgPN IN ('ABC1', 'ABC2')
UNION ALL
SELECT df2.RecID, df2.MfgPN, df2.LinkID, df2.ShipQuantity, feedInfo.number + 1 AS Number
FROM EXT_DistributorFeed df2
INNER JOIN feedInfo ON df2.RecID = feedInfo.RecID
WHERE 1 = 1
AND number < feedInfo.ShipQuantity
AND df2.mfgPN IN ('ABC1', 'ABC2')
)
Select fi.*,
dfd.SerialNo [Serial]
From feedInfo fi
LEFT JOIN (SELECT *, ROW_NUMBER() OVER (PARTITION BY LinkID ORDER BY SerialNo) rn,
FROM EXT_DistributorFeedDetail) dfd
ON dfd.LinkID = fi.LinkID AND dfd.rn = fi.Number
OPTION (maxrecursion 20000);
Depending on what order you want the serial numbers in the EXT_DistributorFeedDetail table to be you would need to change the order by in the Window function ROW_NUMBER() OVER (PARTITION BY LinkID ORDER BY SerialNo) if you take out the Order by the it would be random and could change.

It looks like you want to join the SerialNo in descending order for the number. You can do this by changing the last part of the query to this:
with feedinfo as (
....
)
select f.*, e.*
from feedinfo f
left join (
select *, rn = row_number() over (partition by linkid order by serialno desc)
from ext_distributorfeeddetail
) e on f.linkid = e.linkid and f.number = e.rn
option (maxrecursion 20000);

You have to left join on the EXT_DistributorFeedDetail table to extract serial column. Note that for each recid in the output the serial column will be repeated as many times as the number of entries per linkid in the EXT_DistributorFeedDetail table.
WITH feedInfo AS
(
SELECT df1.RecID, MfgPN, LinkID, ShipQuantity, 1 AS Number
FROM EXT_DistributorFeed df1
WHERE df1.mfgPN IN ('ABC1', 'ABC2')
UNION ALL
SELECT df2.RecID, df2.MfgPN, df2.LinkID, df2.ShipQuantity,
feedInfo.number + 1 AS Number
FROM EXT_DistributorFeed df2
INNER JOIN feedInfo ON df2.RecID = feedInfo.RecID
WHERE number < feedInfo.ShipQuantity
AND df2.mfgPN IN ('ABC1', 'ABC2')
)
Select feedInfo.*, e.serial
From feedInfo
left join EXT_DistributorFeedDetail e
on e.linkid = feedinfo.linkid

Related

Min and max auto Id column plus other columns from the same table

I need to retrieve data from a table relative to 3 columns i.e. max and min Id for every unique reservation_Id and rnoid pair.
This is what I have tried:
SELECT
R1.*,
R2.Id, R2.Res_Id, R2.rNoid
FROM
dbo.Res_Id R1
LEFT OUTER JOIN
dbo.Res_Id R2 ON R2.rNoid = R1.rNoid
AND (R2.Id > R1.Id --min
OR (R2.Id = R1.Id
AND r2.Res_Id <> r1.Res_Id)
)
-- AND R2.rNoid <> R1.rNoid
WHERE
R2.id IS NULL
ORDER BY
R1.Id
Results:
id Res_Id, rNoid, xxx_x, yyy_x, user_id
-------------------------------------------
1 1 33 5 null 1
2 3 44 0 3 1
3 13 22 0 null 1
4 1 22 2 5 2
5 3 5 0 5 2
6 3 77 1 3 2
I am getting some unique pairs skipped.
You may try this. This will create min(id) under cte section and max(id) under ct section. After that you may easily get the inner join on Res_Id and
rNoid to get your records.
; with cte as (
SELECT r.Res_Id, r.rNoid, MIN(r.id) as MinId
FROM dbo.Res_Id R1
GROUP BY r.Res_Id, r.rNoid
),
ct as (
SELECT r.Res_Id, r.rNoid, MAX(r.id) as MaxId
FROM dbo.Res_Id R1
GROUP BY r.Res_Id, r.rNoid
)
SELECT C.Res_Id, C.rNoid, MinId, MaxId FROM CTE AS C INNER JOIN CT AS CT
ON C.Res_Id = CT.Res_Id AND C.rNoid = CT.rNoid
max and min Id for every unique reservation_Id and rnoid pair.
You seem to want a simple aggregation query:
SELECT r.Res_Id, r.rNoid, MIN(r.id), MAX(r.id)
FROM dbo.Res_Id R1
GROUP BY r.Res_Id, r.rNoid;

SQL query with conditions for audit

I want to write a SQL query for several columns with conditions. the table looks like this:
ID Company User
1 Bov LPF
2 Ak LPF
3 Bov LPF
4 Bov ABC
5 Ak ABC
6 ZP ABC
7 REP ABC
8 REP CDE
9 KEK CDE
10 Ak CDE
11 PER CDE
12 Bov BKE
The result must be as followed:
Give me three rows per user (only the users with at least three rows will be in the result).
Company Bov, REP and Ak must return at least two times in the result
It is for an audit so everything must be ordered randomly (order by newid() for example)
Total rows is 9.
Expected result for example:
ID Company User
1 Bov LPF
2 Ak LPF
3 Bov LPF
4 Bov ABC
5 Ak ABC
7 REP ABC
8 REP CDE
10 Ak CDE
11 PER CDE
How should i write the query?
Update:
I was thinking about a query like this, but this doesn't give me the right results:
select *
from
(
select *,row_number() over(partition by user,company order by user, company) as row, ROW_NUMBER() over(order by newid()) as total
from
(
select *
from
(
select *, 0 as Bov, sum(iif(Company= 'Ak',1,0)) over (order by newid()) as Ak
FROM table a
where Company = 'Ak'
) as eu
where eu.Ak <= 2
UNION ALL
select *
from
(
select *, sum(iif(company = 'Bov',1,0)) over (order by newid() ) as Bov, 0 as Ak
FROM table a
where Company = 'Bov'
) as nn
where nn.Bov <= 2
UNION ALL
select *, 0 as Bov, 0 as Ak
FROM table a
where Company not in ('Bov','Ak')
) as z
) as z1
where z1.row <= 3
and z1.total <= 9
Filter <= 2 is for the company that must return at least two times in the result
Filter row <= 3 is for the three rows per user.
Filter total <= 9 is for the total rows that must be 9.
With this query i am not for sure that i have at least two rows for the companies Bov, Ak and REP.
select *
from table
group by id
having count(User) <= 3
order by id
Is this what you need?
You can use this.
DECLARE #MyTable TABLE (ID INT, Company VARCHAR(10), [User] VARCHAR(10))
INSERT INTO #MyTable VALUES
(1 , 'Bov', 'LPF'),
(2 , 'Ak ', 'LPF'),
(3 , 'Bov', 'LPF'),
(4 , 'Bov', 'ABC'),
(5 , 'Ak ', 'ABC'),
(6 , 'ZP ', 'ABC'),
(7 , 'REP', 'ABC'),
(8 , 'REP', 'CDE'),
(9 , 'KEK', 'CDE'),
(10 , 'Ak ', 'CDE'),
(11 , 'PER', 'CDE'),
(12 , 'Bov', 'BKE')
SELECT ID, Company, [User] FROM
( SELECT *
, ROW_NUMBER() OVER (PARTITION BY [User] ORDER BY (CASE WHEN Company IN ('Bov','REP','Ak') THEN 0 ELSE 1 END) ) RN
, COUNT(*) OVER (PARTITION BY [User]) CmpCnt
, COUNT(CASE WHEN Company IN ('Bov','REP','Ak') THEN 1 END) OVER (PARTITION BY [User]) PriCmpCnt
FROM #MyTable
) T
WHERE
T.CmpCnt > 2
and T.PriCmpCnt > 1
and T.RN < 4
order by ID
Result:
ID Company User
----------- ---------- ----------
1 Bov LPF
2 Ak LPF
3 Bov LPF
4 Bov ABC
5 Ak ABC
7 REP ABC
8 REP CDE
10 Ak CDE
11 PER CDE
Here's how I'd approach it; see comments for explanation / let me know if you need anymore info to understand anything...
SQL Fiddle Demo
;with cte as
(
select Id
, Company
, [User]
, Row_Number() over (partition by [User] order by randomOrder) PerUserRowNum
, case
when Company in ('BOV','REP', 'AK') then
case
when Row_Number() over (partition by Company order by randomOrder) <= 2 then 1
else 0
end
else 0
end MustInclude
from (select *, newid() randomOrder from SourceData) x
)
select top 9 --total rows is 9
Id, Company, [User]
from cte
where PerUserRowNum <= 3 --show 3 rows per user
and [User] in ( --only the users with at least three rows will be in the result
select [User]
from cte
where PerUserRowNum = 3
)
order by MustInclude desc, newid() --ensure all the stuff we must include is returned, then make up the rest of the results with whatever

SQL update all records except the last one with a value

I need to make a query where only the last line of each user that has a car gets a license plate number.
ID UserId LicensePlate HasCar
1 1 ABC123 1
2 1 ABC123 1
3 2 NULL 0
4 3 UVW789 1
5 3 UVW789 1
Should become:
ID UserId LicensePlate HasCar
1 1 NULL 1
2 1 ABC123 1
3 2 NULL 0
4 3 NULL 1
5 3 UVW789 1
So I basically need to find all users with a licenseplate and change all but the last one and make the LicensePlate NULL
Assuming the ID column is an identity column so it can provide the ordering, something like this should do the trick:
;WITH CTE AS
(
SELECT Id,
UserId,
LicensePlate,
ROW_NUMBER() OVER(PARTITION BY UserId ORDER BY Id DESC) rn
FROM Table
WHERE HasCar = 1
)
UPDATE CTE
SET LicensePlate = NULL
WHERE rn > 1
You can try this
UPDATE l
SET l.LicensePlate = null
FROM Car l
INNER JOIN (SELECT UserId, Max(Id) AS max_id
FROM Car
GROUP BY UserId) m ON m.UserId = l.UserId
AND m.max_id <> l.id
You can do it with a join on the table itself like that :
UPDATE car c
INNER JOIN car c2 ON c.userId = c2.userId AND c.id < c2.id AND c.HasCar = 1 AND c2.HasCar = 1
SET c.LicensePlate = NULL
The condition c.id < c2.id will avoid to select the last line
By using LAG Function also you can achieve it.
;WITH License(ID,UserId,LicensePlate,HasCar)
as
(
SELECT 1,1,'ABC123',1 UNION ALL
SELECT 2,1,'ABC123',1 UNION ALL
SELECT 3,2,NULL ,0 UNION ALL
SELECT 4,3,'UVW789',1 UNION ALL
SELECT 5,3,'UVW789',1
)
SELECT ID,UserId,LAG(LicensePlate,1,NULL) OVER(PARTITION BY UserId ORDER BY LicensePlate),HasCar FROM License

Grouping in SELECT statement with two columns that contain different values among rows

I have a SQL query I am performing against some tables SQL Server database. The result set looks something like this:
Column1 | Column2 | Column3 | InvoiceNumber | InvoiceID
-------------------------------------------------------
1 1 1 1234 1
1 1 1 5678 2
I only want to get back the row with the smallest InvoiceID and its corresponding InvoiceNumber.
I apply min(InvoiceID) in the SELECT statement. In the GROUP BY clause, I am grouping by every column except InvoiceNumber and InvoiceID. I am getting an error since InvoiceNumber isn't contained in an aggregate function in the SELECT statement and isn't present in the GROUP BY clause.
How do I structure the query so I only get back the row with the smallest InvoiceID and the corresponding InvoiceNumber?
Edit
Here is some additional information:
The result set is actually going to contain many rows. It's only occassionally that multiple rows will have the same values for Column1, Column2, and Column3, but different values for the InvoiceNumber and InvoiceID. So a better example of the result set would look like this:
Column1 | Column2 | Column3 | InvoiceNumber | InvoiceID
-------------------------------------------------------
1 1 1 1234 1
1 1 1 5678 2
2 1 1 1287 3
2 1 2 1287 3
2 2 1 1287 3
3 1 1 5555 4
3 1 2 5555 4
So when Column1, Column2, and Column3 are the same, I only want the result with the smallest InvoiceID, and whatever the InvoiceNumber was for the row with the smallest InvoiceID. Here's what I want it to actually look like in this case:
Column1 | Column2 | Column3 | InvoiceNumber | InvoiceID
-------------------------------------------------------
1 1 1 1234 1
2 1 1 1287 3
2 1 2 1287 3
2 2 1 1287 3
3 1 1 5555 4
3 1 2 5555 4
Only the second row was removed because it had a smaller InvoiceID and was the only row that had the same values for Column1, Column2, and Column3 as a different row.
Here's what my SQL query currently looks like:
SELECT c.CollectionID
, p.Account
, d.TransactionID
, id.InvoiceNumber
, i.InvoiceID
FROM collection c
INNER JOIN documents d on c.GlobalCollectionID = d.GlobalCollectionID
LEFT JOIN payment p on c.GlobalCollectionID = p.GlobalCollectionID
AND d.TransactionID = p.TransactionID
LEFT JOIN invoice i on c.GlobalCollectionID = i.GlobalCollectionID
AND d.TransactionID = i.TransactionID
LEFT JOIN invoicedata id on i.InvoiceID = id.InvoiceID
WHERE c.ProcessDate = '2013-11-29'
AND c.Item = 11805123
AND c.CollectionID in ('1104', '1105', '1106')
ORDER BY c.CollectionID
, d.TransactionID
, i.InvoiceID
, p.Account
It would probably be easier to help if you'd posted the SQL to your initial query.
You probably want to move your MIN() function into a WHERE clause:
SELECT Column1 ,Column2 ,Column3 ,InvoiceNumber , InvoiceID
FROM Table_Name
WHERE InvoiceID = (SELECT MIN(InvoiceID) FROM Table_Name)
Now you have provided some data and have changed your requirement as well anyway with this new data and new requirement , try this
DECLARE #T Table (Column1 INT,Column2 INT,Column3 INT,InvoiceNumber INT, InvoiceID INT)
INSERT INTO #T
VALUES
(1,1,1,1234,1),
(1,1,1,5678,2),
(2,1,1,1287,3),
(2,1,2,1287,3),
(2,2,1,1287,3),
(3,1,1,5555,4),
(3,1,2,5555,4)
;WITH CTE
AS
(
SELECT Column1 ,Column2
,Column3 ,InvoiceNumber , InvoiceID
, rn = ROW_NUMBER() OVER (PARTITION BY Column1 ,Column2 ,Column3 ORDER BY InvoiceID ASC)
FROM #T
)
SELECT * FROM CTE
WHERE rn = 1
Result Set
Column1 Column2 Column3 InvoiceNumber InvoiceID rn
1 1 1 1234 1 1
2 1 1 1287 3 1
2 1 2 1287 3 1
2 2 1 1287 3 1
3 1 1 5555 4 1
3 1 2 5555 4 1
Your Query
;WITH CTE
AS
(
SELECT YourQuery.*
, rn = ROW_NUMBER() OVER (PARTITION BY c.CollectionID , p.Account , d.TransactionID ORDER BY InvoiceID ASC)
(
SELECT c.CollectionID
, p.Account
, d.TransactionID
, id.InvoiceNumber
, i.InvoiceID
FROM collection c
INNER JOIN documents d on c.GlobalCollectionID = d.GlobalCollectionID
LEFT JOIN payment p on c.GlobalCollectionID = p.GlobalCollectionID
AND d.TransactionID = p.TransactionID
LEFT JOIN invoice i on c.GlobalCollectionID = i.GlobalCollectionID
AND d.TransactionID = i.TransactionID
LEFT JOIN invoicedata id on i.InvoiceID = id.InvoiceID
WHERE c.ProcessDate = '2013-11-29'
AND c.Item = 11805123
AND c.CollectionID in ('1104', '1105', '1106')
)YourQuery
)
SELECT C.CollectionID , C.Account , C.TransactionID,C.InvoiceNumber, C.InvoiceID
FROM CTE C
WHERE rn = 1
This is the query you're looking for:
SELECT Column1, Column2, Column3, InvoiceNumber, InvoiceID
FROM (
SELECT *,
rn = ROW_NUMBER() OVER (PARTITION BY Column1, Column2, Column3
ORDER BY InvoiceID ASC)
FROM example
) e
WHERE rn = 1
See it working on SQLFiddle. Just swap in your query for the nested query, but keep the ROW_NUMBER field.
Produces the correct result set:
COLUMN1 COLUMN2 COLUMN3 INVOICENUMBER INVOICEID
----------------------------------------------------
1 1 1 1234 1
2 1 1 1287 3
2 1 2 1287 3
2 2 1 1287 3
3 1 1 5555 4
3 1 2 5555 4

left join without duplicate values using MIN()

I have a table_1:
id custno
1 1
2 2
3 3
and a table_2:
id custno qty descr
1 1 10 a
2 1 7 b
3 2 4 c
4 3 7 d
5 1 5 e
6 1 5 f
When I run this query to show the minimum order quantities from every customer:
SELECT DISTINCT table_1.custno,table_2.qty,table_2.descr
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno AND qty = (SELECT MIN(qty) FROM table_2
WHERE table_2.custno = table_1.custno )
Then I get this result:
custno qty descr
1 5 e
1 5 f
2 4 c
3 7 d
Customer 1 appears twice each time with the same minimum qty (& a different description) but I only want to see customer 1 appear once. I don't care if that is the record with 'e' as a description or 'f' as a description.
First of all... I'm not sure why you need to include table_1 in the queries to begin with:
select custno, min(qty) as min_qty
from table_2
group by custno;
But just in case there is other information that you need that wasn't included in the question:
select table_1.custno, ifnull(min(qty),0) as min_qty
from table_1
left outer join table_2
on table_1.custno = table_2.custno
group by table_1.custno;
"Generic" SQL way:
SELECT table_1.custno,table_2.qty,table_2.descr
FROM table_1, table_2
WHERE table_2.id = (SELECT TOP 1 id
FROM table_2
WHERE custno = table_1.custno
ORDER BY qty )
SQL 2008 way (probably faster):
SELECT custno, qty, descr
FROM
(SELECT
custno,
qty,
descr,
ROW_NUMBER() OVER (PARTITION BY custno ORDER BY qty) RowNum
FROM table_2
) A
WHERE RowNum = 1
If you use SQL-Server you could use ROW_NUMBER and a CTE:
WITH CTE AS
(
SELECT table_1.custno,table_2.qty,table_2.descr,
RN = ROW_NUMBER() OVER ( PARTITION BY table_1.custno
Order By table_2.qty ASC)
FROM table_1
LEFT OUTER JOIN table_2
ON table_1.custno = table_2.custno
)
SELECT custno, qty,descr
FROM CTE
WHERE RN = 1
Demolink