SQL query with SUM, ORDER BY, with limited values - tsql

I am having a troubles to get expected result.
I have successfully joined 2 tables (database is SQL Server), however I would like to have something more.
Excel - it's table which contains list of PartNumbers(GBC) with corresponding Quantity of this part needed for build.
I'm joining this Excel with my Inventory database to retrieve information about what I have, what is missing, what I need to purchase.
Current query:
string sqlCheck = #"SELECT e.GBC, e.Replaced, e.Description, Barcode, Location, Bookstand, Quantity, Buildneed, p.Quantity - e.Buildneed as Afterbuild FROM Parts p Right JOIN Excel e ON e.GBC = p.GBC ORDER BY GBC ASC, Quantity DESC";
Results as below image:
It is needed to use ALL duplicated GBC but in specific order.
First I need to take GBC(86911) with Quantity = 100, this should result in Afterbuild = 0.
But in column Buildneed I see that I need 768 in total, so next step would be to take GBC(86911) with quantity = 500, this should result in Afterbuild 0, and in this line I would like to see new column called Totals which will be equal to -168 (which will mean I need to buy 168 pcs of this part).
I can have many same parts with different quantities
I always want to start from lowest quantity on particular duplicated GBC
This should not show me more GBC if Buildneed value will be exceeded
Expected output as on below image:
I have added column 'UseInOrder' - it is not neccessery but would be awesome if its possible also, it will point me that I will need to use all parts from each in that order.
As suggested, below table definitions:
CREATE TABLE [dbo].[Excel] (
[GBC] INT NULL,
[Description] VARCHAR (50) NULL,
[Buildneed] INT NULL,
[Replaced] VARCHAR (50) NULL
);
CREATE TABLE [dbo].[Parts] (
[Barcode] INT IDENTITY (201900001, 1) NOT NULL,
[GBC] INT NULL,
[Description] VARCHAR (50) NULL,
[Location] VARCHAR (50) NULL,
[Bookstand] VARCHAR (50) NULL,
[Value] VARCHAR (50) NULL,
[Quantity] INT NULL,
[MQuantity] INT NULL,
[Manufacturer1] VARCHAR (50) NULL,
[MPN1] VARCHAR (50) NULL,
[Manufacturer2] VARCHAR (50) NULL,
[MPN2] VARCHAR (50) NULL,
[Manufacturer3] VARCHAR (50) NULL,
[MPN3] VARCHAR (50) NULL,
CONSTRAINT [PK_Parts] PRIMARY KEY CLUSTERED ([Barcode] ASC)
);
EDIT Sample data
declare #tblParts table(
GBC int,
Barcode varchar(256),
[Location] varchar(256),
Quantity int
)
declare #tblPartsUsed table(
GBC int,
Replaced varchar(1) default '',
[Description] varchar(50),
Buildneed int
)
insert into #tblParts (GBC,[Description], Barcode, [Location], Quantity)
select 86911, 'CAP_CER,10nF,0603,10%,100V,X7R' ,201901200, 'JD-01/  14' ,500
union all
select 86911, 'CAP_CER,10nF,0603,10%,100V,X7R' ,201901166, 'ESB-03' ,100
union all
select 99529, 'DIO_ZENR,5,6V,2%,MM3Z5V6ST1G,SOD323' ,201901024, 'ESB-01' ,100
union all
select 128082, 'CAP_CER,100nF,0603,10%,50V,X7R, poly' ,201901120, 'JD-01/  3' ,500
union all
select 128082, 'CAP_CER,100nF,0603,10%,50V,X7R, poly' ,201901121, 'JD-01/  3' ,500
union all
select 168078, 'CAP_CER,470nF,0805,10%,50V,X7R' ,201901207, 'JD-01/  19' ,170
union all
select 168078, 'CAP_CER,470nF,0805,10%,50V,X7R' ,201901152, 'ESB-03' ,140
union all
select 196881, 'BJT,C,SMBT3946DW1T1G,SOT363' ,201901085, 'ESB-02' ,100
union all
select 199296, 'BJT_DIG,C,SMUN5311DW1T1G,SOT363' ,201901083, 'ESB-02' ,100
union all
select 207735, 'DIO_LED, NFSA123DT' ,201902132, 'KRK' ,10
insert into #tblPartsUsed(GBC, [Description], Buildneed)
select 71744, 'RES_TF,10k,0402,1%,0,1W,100PPM/C' ,192
union all
select 71746, 'RES_TF,10k,0603,1%,0,1W,100PPM/C' ,168
union all
select 76527, 'CAP_CER,10nF,0402,10%,50V,X7R' ,288
union all
select 86911, 'CAP_CER,10nF,0603,10%,100V,X7R' ,1464
union all
select 92854, 'RES_TF,30k,0603,1%,0,1W,100PPM/C' ,72
union all
select 93018, 'RES_TF,68k,0603,1%,0,1W,100PPM/C' ,72
union all
select 95241, 'RES_TF,2k2,0402,1%,0,1W,100PPM/C' ,192
union all
select 95549, 'RES_TF,47k,0603,1%,0,1W,100PPM/C' ,72
union all
select 99529, 'DIO_ZENR,5,6V,2%,MM3Z5V6ST1G,SOD323' ,72
union all
select 112117, 'RES_TF,2k2,0603,1%,0,1W,100PPM/C' ,96
union all
select 126486, 'RES_TF,0R,0603' ,24
union all
select 128082, 'CAP_CER,100nF,0603,10%,50V,X7R, poly' ,72
union all
select 168078, 'CAP_CER,470nF,0805,10%,50V,X7R' ,72
union all
select 196200, 'BJT_DIG,N,PDTC114EU,SOT323' ,72
union all
select 196881, 'BJT,C,SMBT3946DW1T1G,SOT363' ,144
union all
select 199296, 'BJT_DIG,C,SMUN5311DW1T1G,SOT363' ,504
union all
select 199302, 'RES_TF,100R,0603,10%,0,1W,200PPM/C' ,72
union all
select 202047, 'UNI,N,PMPB215ENEA,DFN2020MD-6' ,72
union all
select 202054, 'DIO_LED,SPMWHT346EA3' ,648
union all
select 203509, 'CONN_HEADER,MOLEX,5023521100' ,24
union all
select 207735, 'DIO_LED, NFSA123DT' ,648
union all
select 207843, 'Thermistor,10k,0603,1%,NTC' ,24
union all
select 208252, 'FOOTPRINT_BOARD-IN,MOLEX,350220011' ,48
union all
select 212145, 'DIO_SIGN,200V,250mA,50nS,BAV21WSQ-7-F,SOD323' ,72
Based on this date, output should be like on below image of table:

Try this
EDIT2 (after change initial data)
SELECT distinct u.GBC,p2.Replaced, p2.Description, IIF(u.Afterbuild>0, MIN(p1.Barcode) OVER(PARTITION BY u.GBC), p1.Barcode) as Barcode, p1.Location, u.Quantity, u.Buildneed,
CASE
when (u.Total <0 AND p1.Barcode is NULL) OR (SUM(u.Quantity) OVER(PARTITION BY u.GBC) - u.Buildneed)>0 then u.Afterbuild
else 0
end
as Afterbuild,
CASE
when u.Total <0 AND p1.Barcode is NULL then ABS(u.Total)
when (SUM(u.Quantity) OVER(PARTITION BY u.GBC) - u.Buildneed)>0 then u.Total
else u.Total
END as Total FROM
(
SELECT distinct b.GBC, b.Quantity, b.Buildneed,
case
when b.Total <0 then null
when b.Total >=0 AND MIN(b.Quantity) OVER(PARTITION BY b.GBC) - b.Buildneed < 0 AND b.Quantity = MAX(b.Quantity) OVER(PARTITION BY b.GBC) then SUM(b.Quantity) OVER(PARTITION BY b.GBC) - b.Buildneed
when b.Total >=0 AND MIN(b.Quantity) OVER(PARTITION BY b.GBC) - b.Buildneed >= 0 AND b.Quantity = MIN(b.Quantity) OVER(PARTITION BY b.GBC) then MIN(b.Quantity) OVER(PARTITION BY b.GBC) - b.Buildneed
END AS Afterbuild,
case
when b.Total >=0 then null
when b.Total <0 AND b.RowNumber = MAX(b.RowNumber) OVER(PARTITION BY b.GBC) then b.Total
END AS Total
FROM
(
select r.GBC, r.Quantity, r.Buildneed,
(SUM(r.Quantity) OVER(PARTITION BY r.GBC)) - r.Buildneed as Total, ROW_NUMBER() OVER(order by r.GBC, r.Quantity) as RowNumber
from
(
SELECT e.GBC, ISNULL(Quantity,0) as Quantity, ISNULL(Buildneed,0) as Buildneed FROM #tblParts p RIGHT JOIN #tblPartsUsed e ON e.GBC = p.GBC
) as r
) as b
) as u
left join #tblParts as p1 on u.GBC = p1.GBC and ISNULL(u.Quantity,0) = ISNULL(p1.Quantity,0)
left join #tblPartsUsed as p2 on u.GBC = p2.GBC and ISNULL(u.Buildneed,0) = ISNULL(p2.Buildneed,0)
where (u.Afterbuild is not null or u.Total is not null or u.Quantity - u.Buildneed < 0)
order by u.GBC, u.Quantity

As per #RomaRuzich this question needs expected results in a table format.
Also the Parts and Excel table structure is needed with some data to clarify the question.
Made some assumptions and created script with output results.
declare #tblParts table(
GBC int,
Barcode varchar(20),
[Location] varchar(20),
Bookstand varchar(10) default '',
Quantity int
)
declare #tblPartsUsed table(
GBC int,
Replaced varchar(1) default '',
[Description] varchar(50),
Buildneed int
)
insert into #tblParts(GBC, Barcode, [Location], Quantity)
select 72223, '', '', 0
union all
select 86911, '201901200','JD-01/',500
union all
select 86911, '201901166','JD-01/ 14', 100
insert into #tblPartsUsed(GBC, [Description], Buildneed)
select '72223', 'RES_TF', 60
union all
select '86911', 'CAP_CER, 10nf,0603', 768
union all
select '86911', 'CAP_CER, 10nf,0603', 768
SELECT distinct e.GBC, e.Replaced, e.[Description], Barcode, [Location],
Bookstand, Quantity, Buildneed,
p.Quantity - e.Buildneed as Afterbuild,
x.TotalQuantity - Buildneed as Totals
FROM #tblParts p Right JOIN #tblPartsUsed e ON e.GBC = p.GBC
left join (select GBC, sum(Quantity) TotalQuantity from #tblParts group by GBC) x <br/>on e.GBC = x.GBC
ORDER BY e.GBC ASC, p.Quantity ASC

Related

How to order by result set in union query in T-SQL

I want to use order by clause in my last sql query and I have more than 3 union queries. I do not want to order the top 2 union query but I want to use order by clause in my last sql statement.
Currently, getting error
ORDER BY items must appear in the select list if the statement contains a UNION, INTERSECT or EXCEPT operator.
select 'Total Number of pat' Name, convert(varchar(20), count(id)) Number from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number from table3
union
select x.usertype, count(distinct userid) cnt
from [dbo].table1 t
cross apply (values (
case when t.userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end,
t.userid
)) x(usertype, userid)
where t.date >= dateadd(day,-7, getdate())
group by x.usertype
order by usertype desc
order by is sorting the result of the unions all together however you can introduce a orderIndex column for imlementing the right ordering.
Here the sample:
I've tried to build sample data in the following code.
create table table1(
userid varchar(100),
usertype varchar(100),
date date
)
insert into table1(userid, date) values ('Einsmayr', '2020-10-27')
insert into table1(userid, date) values ('Eins123', '2020-10-27')
insert into table1(userid, date) values ('Einschmid', '2020-10-27')
insert into table1(userid, date) values ('Einshuber', '2020-10-27')
insert into table1(userid, date) values ('Einsreitmayr', '2020-10-27')
create table table2 (
Name varchar(100),
id int
)
insert into table2(Name, id) values('Zweirich', 5)
insert into table2(Name, id) values('Zweifel', 6)
create table table3 (
Name varchar(100),
id int
)
insert into table3(Name, id) values('Dreisinger', 17)
insert into table3(Name, id) values('Dreibert', 18)
This allows the following queries:
select usertype, Number
from (
select 'Total Number of pat' usertype, convert(varchar(20), count(id)) Number, 1 orderIndex from table2 where id = 5
union
select 'Total Number of Doc' Name, convert(varchar(20), count(id)) Number, 2 orderIndex from table3
union
select usertype, count(distinct userid) Number, 3 orderIndex
from (
select userid, case when userid like '%[0-9][0-9[0-9]' then 'transition' else 'non transition' end usertype
from table1
where date >= dateadd(day,-7, getdate())
) x
group by x.usertype
) y
order by y.orderIndex, y.usertype
Find the solution here: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ac396c48f5dbcb4a53ad40fac70e9236

SQL Server split overlapping date ranges

I need to split date ranges that overlap. I have a primary table (I've called it Employment for this example), and I need to return all Begin-End date ranges for a person from this table. I also have multiple sub tables (represented by Car and Food), and I want to return the value that was active in the sub tables during the times given in the main tables. This will involve splitting the main table date ranges when a sub table item changes.
I don't want to return sub table information for dates not in the main tables.
DECLARE #Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )
INSERT INTO #Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
, ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
, ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )
INSERT INTO #Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
, ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )
INSERT INTO #Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
, ( 123, 'Donuts', '2001-02-23', '2001-02-25' )
For the above data, the results should be:
Person_ID Employment Food Car Begin_Date End_Date
123 ACME 1986-01-01 1986-04-30
123 ACME Red Car 1986-05-01 1990-12-31
123 Office Corp Red Car 1995-05-15 1996-12-31
123 Office Corp Eggs Red Car 1997-01-01 1997-03-09
123 Office Corp Red Car 1997-03-10 1997-06-23
123 Office Corp 1997-06-24 1997-07-02
123 Office Corp Blue Car 1997-07-03 1998-10-03
123 Job 3 Blue Car 1998-10-04 2001-02-22
123 Job 3 Donuts Blue Car 2001-02-23 2001-02-25
123 Job 3 Blue Car 2001-02-26 2999-12-31
The first row is his time working for ACME, where he didn't have a car or a weird food obsession. In the second row, he purchased a car, and still worked at ACME. In the third row, he changed jobs to Office Corp, but still has the Red Car. Note how we're not returning data during his unemployment gap, even though he had the Red Car. We only want to know what was in the Car and Food tables during the times there are values in the Employment table.
I found a solution for SQL Server 2012 that uses the LEAD/LAG functions to accomplish this, but I'm stuck with 2008 R2.
To change the 2012 solution from that blog to work with 2008, you need to replace the LEAD in the following
with
ValidDates as …
,
ValidDateRanges1 as
(
select EmployeeNo, Date as ValidFrom, lead(Date,1) over (partition by EmployeeNo order by Date) ValidTo
from ValidDates
)
There are a number of ways to do this, but one example is a self join to the same table + 1 row (which is effectively what a lead does). One way to do this is to put a rownumber on the previous table (so it is easy to find the next row) by adding another intermediate CTE (eg ValidDatesWithRowno). Then do a left outer join to that table where EmployeeNo is the same and rowno = rowno + 1, and use that value to replace the lead. If you wanted a lead 2, you would join to rowno + 2, etc. So the 2008 version would look something like
with
ValidDates as …
,
ValidDatesWithRowno as --This is the ValidDates + a RowNo for easy self joining below
(
select EmployeeNo, Date, ROW_NUMBER() OVER (ORDER BY EmployeeNo, Date) as RowNo from ValidDates
)
,
ValidDateRanges1 as
(
select VD.EmployeeNo, VD.Date as ValidFrom, VDLead1.Date as ValidTo
from ValidDatesWithRowno VD
left outer join ValidDatesWithRowno VDLead1 on VDLead1.EmployeeNo = VD.EmployeeNo
and VDLead1.RowNo = VD.RowNo + 1
)
The rest of the solution described looks like it will work like you want on 2008.
Here is the answer I came up with. It works, but it's not very pretty.
It goes it two waves, first splitting any overlapping Employment/Car dates, then running the same SQL a second time add the Food dates and split any overlaps again.
DECLARE #Employment TABLE
( Person_ID INT, Employment VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Car TABLE
( Person_ID INT, Car VARCHAR(50), Begin_Date DATE, End_Date DATE )
DECLARE #Food TABLE
( Person_ID INT, Food VARCHAR(50), Begin_Date DATE, End_Date DATE )
INSERT INTO #Employment ( [Person_ID], [Employment], [Begin_Date], [End_Date] )
VALUES ( 123 , 'ACME' , '1986-01-01' , '1990-12-31' )
, ( 123 , 'Office Corp' , '1995-05-15' , '1998-10-03' )
, ( 123 , 'Job 3' , '1998-10-04' , '2999-12-31' )
INSERT INTO #Car ( [Person_ID] , [Car] , [Begin_Date] , [End_Date] )
VALUES ( 123, 'Red Car', '1986-05-01', '1997-06-23' )
, ( 123, 'Blue Car', '1997-07-03', '2999-12-31' )
INSERT INTO #Food ( [Person_ID], [Food], [Begin_Date], [End_Date] )
VALUES ( 123, 'Eggs', '1997-01-01', '1997-03-09' )
, ( 123, 'Donuts', '2001-02-23', '2001-02-25' )
DECLARE #Person_ID INT = 123;
--A table to hold date ranges that need to be merged together
DECLARE #DatesToMerge TABLE
(
ID INT,
Person_ID INT,
Date_Type VARCHAR(10),
Begin_Date DATETIME,
End_Date DATETIME
)
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Car])
, Person_ID
, 'Car'
, Begin_Date
, End_Date
FROM #Car
WHERE Person_ID = #Person_ID
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Employment])
, Person_ID
, 'Employment'
, Begin_Date
, End_Date
FROM #Employment
WHERE Person_ID = #Person_ID;
--A table to hold the merged #Employment and Car records
DECLARE #EmploymentAndCar TABLE
(
RowNumber INT,
Person_ID INT,
Begin_Date DATETIME,
End_Date DATETIME
)
;
WITH CarCTE AS
(--This CTE grabs just the Car rows so we can compare and split dates from them
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
WHERE Date_Type = 'Car'
),
NewRowsCTE AS
( --This CTE creates just new rows starting after the Car dates for each #Employment date range
SELECT a.ID,
a.Person_ID,
a.Date_Type,
DATEADD(DAY,1,b.End_Date) AS Begin_Date,
a.End_Date
FROM #DatesToMerge a
INNER JOIN CarCTE b
ON a.Begin_Date <= b.Begin_Date
AND a.End_Date > b.Begin_Date
AND a.End_Date > b.End_Date -- This is needed because if both the Car and #Employment end on the same date, there is split row after
),
UnionCTE AS
( -- This CTE merges the new rows with the existing ones
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
UNION ALL
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM NewRowsCTE
),
FixEndDateCTE AS
(
SELECT CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID,
MIN(d.Begin_Date) AS Begin_Date
FROM UnionCTE c
LEFT OUTER JOIN CarCTE d
ON c.Begin_Date < d.Begin_Date
AND c.End_Date >= d.Begin_Date
WHERE c.Date_Type <> 'Car'
GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),
Finalize AS
(
SELECT ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
e.Person_ID,
e.Begin_Date,
CASE WHEN f.Begin_Date IS NULL THEN e.End_Date
ELSE DATEADD (DAY,-1,f.Begin_Date)
END AS EndDate
FROM UnionCTE e
LEFT OUTER JOIN FixEndDateCTE f
ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID
)
INSERT INTO #EmploymentAndCar ( RowNumber, Person_ID, Begin_Date, End_Date )
SELECT F.RowNumber
, F.Person_ID
, F.Begin_Date
, F.EndDate
FROM Finalize F
INNER JOIN #Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = #Person_ID
ORDER BY F.Begin_Date
--------------------------------------------------------------------------------------------------
--Now that the Employment and Car dates have been merged, empty the DatesToMerge table
DELETE FROM #DatesToMerge;
--Reload the DatesToMerge table with the newly-merged Employment and Car records,
--and the Food records that still need to be merged
INSERT INTO #DatesToMerge
SELECT RowNumber
, Person_ID
, 'PtBCar'
, Begin_Date
, End_Date
FROM #EmploymentAndCar
WHERE Person_ID = #Person_ID
INSERT INTO #DatesToMerge
SELECT ROW_NUMBER() OVER(ORDER BY [Food])
, Person_ID
, 'Food'
, Begin_Date
, End_Date
FROM #Food
WHERE Person_ID = #Person_ID
;
WITH CarCTE AS
(--This CTE grabs just the Food rows so we can compare and split dates from them
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
WHERE Date_Type = 'Food'
),
NewRowsCTE AS
( --This CTE creates just new rows starting after the Food dates for each Employment date range
SELECT a.ID,
a.Person_ID,
a.Date_Type,
DATEADD(DAY,1,b.End_Date) AS Begin_Date,
a.End_Date
FROM #DatesToMerge a
INNER JOIN CarCTE b
ON a.Begin_Date <= b.Begin_Date
AND a.End_Date > b.Begin_Date
AND a.End_Date > b.End_Date -- This is needed because if both the Food and Car/Employment end on the same date, there is split row after
),
UnionCTE AS
( -- This CTE merges the new rows with the existing ones
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM #DatesToMerge
UNION ALL
SELECT ID,
Person_ID,
Date_Type,
Begin_Date,
End_Date
FROM NewRowsCTE
),
FixEndDateCTE AS
(
SELECT CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date) AS FixID,
MIN(d.Begin_Date) AS Begin_Date
FROM UnionCTE c
LEFT OUTER JOIN CarCTE d
ON c.Begin_Date < d.Begin_Date
AND c.End_Date >= d.Begin_Date
WHERE c.Date_Type <> 'Food'
GROUP BY CONVERT (CHAR,c.ID)+CONVERT (CHAR,c.Begin_Date)
),
Finalize AS
(
SELECT ROW_NUMBER() OVER (ORDER BY e.Begin_Date) AS RowNumber,
e.Person_ID,
e.Begin_Date,
CASE WHEN f.Begin_Date IS NULL THEN e.End_Date
ELSE DATEADD (DAY,-1,f.Begin_Date)
END AS EndDate
FROM UnionCTE e
LEFT OUTER JOIN FixEndDateCTE f
ON (CONVERT (CHAR,e.ID)+CONVERT (CHAR,e.Begin_Date)) = f.FixID
)
SELECT DISTINCT
F.Person_ID
, Employment
, Car
, Food
, F.Begin_Date
, F.EndDate
FROM Finalize F
INNER JOIN #Employment Employment
ON F.Begin_Date BETWEEN Employment.Begin_Date AND Employment.End_Date AND Employment.Person_ID = #Person_ID
LEFT JOIN #Car Car
ON Car.[Begin_Date] <= F.Begin_Date
AND Car.[End_Date] >= F.[EndDate]
AND Car.Person_ID = #Person_ID
LEFT JOIN #Food Food
ON Food.[Begin_Date] <= F.[Begin_Date]
AND Food.[End_Date] >= F.[EndDate]
AND Food.Person_ID = #Person_ID
ORDER BY F.Begin_Date
If anyone has a more elegant solution, I will be happy to accept their answer.

sql recursion: find tree given middle node

I need to get a tree of related nodes given a certain node, but not necessary top node. I've got a solution using two CTEs, since I am struggling to squeeze it all into one CTE :). Might somebody have a sleek solution to avoid using two CTEs? Here is some code that I was playing with:
DECLARE #temp AS TABLE (ID INT, ParentID INT)
INSERT INTO #temp
SELECT 1 ID, NULL AS ParentID
UNION ALL
SELECT 2, 1
UNION ALL
SELECT 3, 2
UNION ALL
SELECT 4, 3
UNION ALL
SELECT 5, 4
UNION ALL
SELECT 6, NULL
UNION ALL
SELECT 7, 6
UNION ALL
SELECT 8, 7
DECLARE #startNode INT = 4
;WITH TheTree (ID,ParentID)
AS (
SELECT ID, ParentID
FROM #temp
WHERE ID = #startNode
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN TheTree tr ON t.ParentID = tr.ID
)
SELECT * FROM TheTree
;WITH Up(ID,ParentID)
AS (
SELECT t.id, t.ParentID
FROM #temp t
WHERE t.ID = #startNode
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN Up c ON t.id = c.ParentID
)
--SELECT * FROM Up
,TheTree (ID,ParentID)
AS (
SELECT ID, ParentID
FROM Up
WHERE ParentID is null
UNION ALL
SELECT t.id, t.ParentID
FROM #temp t
JOIN TheTree tr ON t.ParentID = tr.ID
)
SELECT * FROM TheTree
thanks
Meh. This avoids using two CTEs, but the result is a brute force kludge that hardly qualifies as "sleek" as it won’t be efficient if your table is at all sizeable. It will:
Recursively build all possible hierarchies
As you build them, flag the target NodeId as you find it
Return only the targeted tree
I threw in column “TreeNumber” on the off-chance the TargetId appears in multiple hierarchies, or if you’d ever have multiple values to check in one pass. “Depth” was added to make the output a bit more legible.
A more complex solution like #John’s might do, and more and subtler tricks could be done with more detailed table sturctures.
DECLARE #startNode INT = 4
;WITH cteAllTrees (TreeNumber, Depth, ID, ParentID, ContainsTarget)
AS (
SELECT
row_number() over (order by ID) TreeNumber
,1
,ID
,ParentID
,case
when ID = #startNode then 1
else 0
end ContainsTarget
FROM #temp
WHERE ParentId is null
UNION ALL
SELECT
tr.TreeNumber
,tr.Depth + 1
,t.id
,t.ParentID
,case
when tr.ContainsTarget = 1 then 1
when t.ID = #startNode then 1
else 0
end ContainsTarget
FROM #temp t
INNER JOIN cteAllTrees tr
ON t.ParentID = tr.ID
)
SELECT
TreeNumber
,Depth
,ID
,ParentId
from cteAllTrees
where TreeNumber in (select TreeNumber from cteAllTrees where ContainsTarget = 1)
order by
TreeNumber
,Depth
,ID
Here is a technique where you can select the entire hierarchy, a specific node with all its children, and even a filtered list and how they roll.
Note: See the comments next to the DECLAREs
Declare #YourTable table (id int,pt int,name varchar(50))
Insert into #YourTable values
(1,null,'1'),(2,1,'2'),(3,1,'3'),(4,2,'4'),(5,2,'5'),(6,3,'6'),(7,null,'7'),(8,7,'8')
Declare #Top int = null --<< Sets top of Hier Try 2
Declare #Nest varchar(25) = '|-----' --<< Optional: Added for readability
Declare #Filter varchar(25) = '' --<< Empty for All or try 4,6
;with cteP as (
Select Seq = cast(1000+Row_Number() over (Order by name) as varchar(500))
,ID
,pt
,Lvl=1
,name
From #YourTable
Where IsNull(#Top,-1) = case when #Top is null then isnull(pt,-1) else ID end
Union All
Select Seq = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.name)) as varchar(500))
,r.ID
,r.pt
,p.Lvl+1
,r.name
From #YourTable r
Join cteP p on r.pt = p.ID)
,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
,cteR2 as (Select A.Seq,A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select Distinct
A.R1
,B.R2
,A.ID
,A.pt
,A.Lvl
,name = Replicate(#Nest,A.Lvl-1) + A.name
From cteR1 A
Join cteR2 B on A.ID=B.ID
Join (Select R1 From cteR1 where IIF(#Filter='',1,0)+CharIndex(concat(',',ID,','),concat(',',#Filter+','))>0) F on F.R1 between A.R1 and B.R2
Order By A.R1

Creating a Void Function in PostgreSQL

I am getting an error on this create function code in Postgresql. The error says it is happening around Line 2 at DELETE, but it happens at WITH if I remove that line so I think it is a problem with the format of my Creat Function
create or replace function retention_data(shopId integer) returns void as $$
delete from retention where shop_id = shopId;
WITH ret_grid_step1 as (
select * from (
SELECT
order_id as order_name,
cust_name as cust_name,
email as email,
date(order_date) as created_at,
count(*) as num_items_in_order,
sum(total_price) as sales ,
rank() over (partition BY order_id ORDER BY cust_name ASC) as rnk_shipping_name,
rank() over (partition BY order_id ORDER BY email ASC) as rnk_email
FROM orders
WHERE shop_id = shopId
and order_date is not null and order_date > now()::date - 365 and order_date < now()::date + 1
group by 1,2,3,4
) x
where rnk_shipping_name = 1 and rnk_email = 1
)
insert into retention(shop_id, cust_name, email, last_purchase_dt, total_sales, num_orders, days_since_last_order)
select
shopId as shop_id,
coalesce(b.cust_name,'null') as cust_name,
a.email,
a.last_purchase_dt,
total_sales,
num_orders,
current_date - last_purchase_dt as days_since_last_order
from (
select
email,
max(created_at) as last_purchase_dt,
count(*) as num_orders,
sum(sales) as total_sales
from ret_grid_step1
group by 1
) as a
left join (
select
email,
cust_name,
rank() over (partition BY email ORDER BY created_at DESC) as rnk
from ret_grid_step1
--where cust_name is not null
group by 1,2,created_at
) as b
on a.email = b.email
where b.rnk = 1
and a.email <> '';
$$ language plpgsql;

getting distinct rows based on two column values

I am trying to get distinct rows from a temporary table and output them to an aspx page. I am trying to use the value of one column and get the last entry made into that column.
I have been trying to use inner join and max(). However i have been unsuccessful.
Here is the code i have been trying to do it with.
Declare #TempTable table (
viewIcon nvarchar(10),
tenderType nvarchar(20),
diaryIcon int,
customerName nvarchar(100),
projectName nvarchar(100),
diaryEntry nvarchar(max),
diaryDate nvarchar(20),
pid nvarchar(20)
)
insert into #TempTable(
viewIcon,
tenderType,
diaryIcon,
customerName,
projectName,
diaryEntry ,
diaryDate ,
pid
)
select p.viewicon,
p.[Tender Type],
1 diaryicon,
c.[Customer Name],
co.[Last Project],
d.Action,
co.[Diary Date],
p.PID
From Projects2 p Inner Join
(select distinct Pno, max(convert(date,[date of next call],103)) maxdate from ProjectDiary group by Pno
) td on p.PID = td.Pno
Inner Join contacts3 co on co.[Customer Number] = p.[Customer Number]
Inner Join Customers3 c on p.[Customer Number] = c.[Customer Number]
Inner Join ProjectDiary d on td.Pno = d.Pno
Where CONVERT(Date, co.[Diary Date], 103) BETWEEN GETDATE()-120 AND GETDATE()-60
DECLARE #contactsTable TABLE
(pid nvarchar(200),
diaryDate date)
insert into #contactsTable (t.pid, t.diarydate)
select distinct pid as pid, MAX(CONVERT(DATE, diaryDate, 103)) as diaryDate from # TempTable t group by pid
DECLARE #tempContacts TABLE
(pid nvarchar(200))
insert into #tempContacts(pid)
select pid from #contactsTable
DECLARE #tempDiaryDate TABLE (diaryDate date)
insert into #tempDiaryDate(diaryDate)
select distinct MAX(CONVERT(DATE, diaryDate, 103)) from #TempTable
select t.* from #TempTable t inner join (select distinct customerName, M AX(CONVERT(DATE, diaryDate, 103)) AS diaryDate from #TempTable group by customerName) tt on t t.customerName=t.customerName
where t.pid not in
(select Pno from ProjectDiary where convert(date,[Date Of Next Call],103) > GETDATE())
and t.viewIcon <> '098'
and t.viewIcon <> '163'
and t.viewIcon <> '119'
and t.pid in (select distinct pid from #tempContacts)
and CONVERT(DATE, t.diaryDate, 103) in (select distinct CONVERT(DATE, diaryDate, 103) f rom #tempDiaryDate)
order by CONVERT(DATE, tt.diaryDate, 103)
I am trying to get all the distinct customerName's using the max date to determine which record it uses.
Use a subquery. Without going through your entire sql statement, the general idea is:
Select [Stuff]
From table t
Where date = (Select Max(Date) from table
where customer = t.customer)