how to self join a table? - sql

After Updating query i got following output with this query
i am writing following query to put two sets of records next to each other.
WITH cte AS
(SELECT SubSubsidaryAccountCode,
sum(Debit) AS debit,
sum(Credit) AS credit,
ROW_NUMBER() over (PARTITION BY SubSubsidaryAccountCode
ORDER BY SubSubsidaryAccountCode) AS RN
FROM TBLLedgerLevel4
WHERE SubSubsidaryAccountCode LIKE '4%'
OR SubSubsidaryAccountCode LIKE '5%'
GROUP BY SubSubsidaryAccountCode ),
cte2 AS
(SELECT *
FROM TBLLevel4)
SELECT a.SubSubsidaryAccountCode ,
c.SubSubsidaryAccountName AS RevenuName ,
sum(a.Debit) AS debit ,
b.SubSubsidaryAccountCode AS SubsidaryAccount2 ,
d.SubSubsidaryAccountName AS ExpenseName ,
sum(b.Credit) AS credit
FROM cte a
JOIN cte2 c ON a.SubSubsidaryAccountCode = c.SubSubsidaryAccountCode
FULL JOIN cte b ON a.RN = b.RN
JOIN cte2 d ON b.SubSubsidaryAccountCode=d.SubSubsidaryAccountCode
WHERE a.SubSubsidaryAccountCode LIKE '4%'
AND b.SubSubsidaryAccountCode LIKE '5%'
GROUP BY a.SubSubsidaryAccountCode,
b.SubSubsidaryAccountCode,
c.SubSubsidaryAccountName,
d.SubSubsidaryAccountName
Output:
SubSubsidaryAccountCode RevenuName debit SubsidaryAccount2 ExpenseName credit
4-106-1001-10026 Cash Sale 52889 5-105-1005-10011 Rf Battles 18091289
4-108-1012-10037 New Sale1 1000 5-105-1005-10011 Rf Battles 18091289
The above output contains records against two Accounting codes that starts with 4 and 5 .Now the records against accounting code that are starting with accounting code like 4 are populated and are according to desired results but the 2nd row of record starting with accounting code 5 does not have any value and must have null value in it.Please help me out to solve this problem.

Your sample makes it seem like it's arbitrary which ones are populated and which are NULL, like you just want to put two sets of records next to each other. You could do that by adding a ROW_NUMBER() and using that in a LEFT JOIN:
;with cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY MainAccountID ORDER BY LedgerDate) AS RN
FROM TBLLedgerLevel1
)
SELECT a.LedgerDate
, a.MainAccountId
, a.VoucherCode
, a.Debit
, b.LedgerDate AS Date2
, b.MainAccountId AS MainAccount2
, b.VoucherCode AS VoucherCode2
, b.Credit
FROM cte a
LEFT JOIN cte b
ON a.RN = b.RN
and b.MainAccountId='5'
WHERE a.MainAccountId='4'
If there could be more MainAccountId='5' records than MainAccountId='4' you'd probably want a FULL JOIN and could use COALESCE() to choose which field to display. Also, making use of aliases cleans up code significantly in my opinion.
Update: Not sure exactly on this, but to add the name you'll need to add a JOIN to TBLLevel1, something like:
;with cte AS (SELECT *,ROW_NUMBER() OVER(PARTITION BY MainAccountID ORDER BY LedgerDate) AS RN
FROM TBLLedgerLevel1
)
SELECT a.LedgerDate
, a.MainAccountId
, a.VoucherCode
, a.Debit
, b.LedgerDate AS Date2
, b.MainAccountId AS MainAccount2
, b.VoucherCode AS VoucherCode2
, b.Credit
, c.MainAccountName
FROM cte a
JOIN TBLLevel1 c
ON a.MainAccountID = c.ID
LEFT JOIN cte b
ON a.RN = b.RN
and b.MainAccountId='5'
WHERE a.MainAccountId='4'
If there's more than 1 record per MainAccountId in the TBLLevel1 table you'll need to add criteria to the JOIN to make sure only the proper value is included.

Related

Multiple results - Need only the latest price

I need to find the latest price for some items
This is my query:
SELECT
MAX("POPORH1"."DATE") as "PO DATE",
"ICSHEH"."DOCNUM",
"ICSHEH"."TRANSDATE",
"ICSHEH"."FISCYEAR",
"ICSHEH"."FISCPERIOD",
"ICSHEH"."REFERENCE",
"ICSHED"."ITEMNO",
"ICSHED"."ITEMDESC",
"ICSHED"."LOCATION",
"ICSHED"."QUANTITY",
"ICSHED"."UNIT",
"POPORL"."UNITCOST"
FROM (("CABDAT"."dbo"."ICSHEH" "ICSHEH"
INNER JOIN
"CABDAT"."dbo"."ICSHED" "ICSHED" ON "ICSHEH"."SEQUENCENO"="ICSHED"."SEQUENCENO")
INNER JOIN "CABDAT"."dbo"."POPORL" "POPORL" ON "ICSHED"."ITEMNO"="POPORL"."ITEMNO")
INNER JOIN "CABDAT"."dbo"."POPORH1" "POPORH1" ON "POPORL"."PORHSEQ"="POPORH1"."PORHSEQ"
WHERE "ICSHED"."SEQUENCENO"=55873
group by
"ICSHEH"."DOCNUM",
"ICSHEH"."TRANSDATE",
"ICSHEH"."FISCYEAR",
"ICSHEH"."FISCPERIOD",
"ICSHEH"."REFERENCE",
"ICSHED"."ITEMNO",
"ICSHED"."ITEMDESC",
"ICSHED"."LOCATION",
"ICSHED"."QUANTITY",
"ICSHED"."UNIT",
"POPORL"."UNITCOST"
This query returns multiple results
These are the results:
"PODATE"='20180405' "ITEMNO"='2944' "UNITCOST"='0.266750'
"PODATE"='20180405' "ITEMNO"='2946' "UNITCOST"='0.266750'
"PODATE"='20170208' "ITEMNO"='2944' "UNITCOST"='0.250780'
"PODATE"='20170208' "ITEMNO"='2944' "UNITCOST"='0.250780'
"PODATE"='20170208' "ITEMNO"='2946' "UNITCOST"='0.250780'
"PODATE"='20170208' "ITEMNO"='2946' "UNITCOST"='0.250780'
I need to have only
"PODATE"='20180405' "ITEMNO"='2944' "UNITCOST"='0.266750'
"PODATE"='20180405' "ITEMNO"='2946' "UNITCOST"='0.266750'
I am learning SQL, so please be patient with my ignorance...
Thanks a lot!
You just need row_number().
WITH cte as (
SELECT *, ROW_NUMBER() OVER (PARTITION BY "ITEMNO" ORDER BY "PODATE" DESC) as rn
FROM "ICSHED" -- or join tables
WHERE "ICSHED"."SEQUENCENO"=55873
)
SELECT *
FROM cte where rn = 1
Or if you only need the highest value without any grouping can use TOP 1
SELECT TOP 1 *
FROM "ICSHED" -- or join tables
WHERE "ICSHED"."SEQUENCENO"=55873
ORDER "PODATE" DESC
By my understanding, you want top 2 rows with recent date. so I try this,
select top 2 * from yourtable order by dateCol desc

Group by not working to get count of a column with other max record in sql

I have a table named PublishedData, see image below
I'm trying to get the output like, below image
I think you can use a query like this:
SELECT dt.DistrictName, ISNULL(dt.Content, 'N/A') Content, dt.UpdatedDate, mt.LastPublished, mt.Unpublished
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY DistrictName ORDER BY UpdatedDate DESC, ISNULL(Content, 'zzzzz')) seq
FROM PublishedData) dt
INNER JOIN (
SELECT DistrictName, MAX(LastPublished) LastPublished, COUNT(CASE WHEN IsPublished = 0 THEN 1 END) Unpublished
FROM PublishedData
GROUP BY DistrictName) mt
ON dt.DistrictName = mt.DistrictName
WHERE
dt.seq = 1;
Because I think you use an order over UpdatedDate, Content to gain you two first columns.
Check out something like this (I don't have your tables, but you will get the idea where to follow with your query):
SELECT DirectName,
MAX(UpdatedDate),
MAX(LastPublished),
(
SELECT COUNT(*)
FROM PublishedData inr
WHERE inr.DirectName = outr.DirectName
AND inr.IsPublished = 0
) AS Unpublished
FROM PublishedData outr
GROUP BY DirectName
We should required a unique identity for that required output in PublishedData Table,Because We can't get the Latest content from given Schema.
If you want data apart from content like DistictName,updatedDate,LastPublishedDate and count of Unpublished records ,Please use Query given below :
select T1.DistrictName,T1.UpdatedDate,T1.LastPublished,T2.Unpublished from
(select DistrictName,Max(UpdateDate) as UpdatedDate,Max(LastPublished) as LastPublished from PublishedData group by DistrictName) T1
inner join
(select DistrictName,count(IsPublished) as Unpublished from PublishedData where isPublished=0 group by DistrictName) T2 ON T1.DistrictName=T2.DistrictName ORDER BY T2.Unpublished DESC

Find duplicates in MS SQL table

I know that this question has been asked several times but I still cannot figure out why my query is returning values which are not duplicates. I want my query to return only the records which have identical value in the column Credit. The query executes without any errors but values which are not duplicated are also being returned. This is my query:
Select
_bvGLTransactionsFull.AccountDesc,
_bvGLAccountsFinancial.Description,
_bvGLTransactionsFull.TxDate,
_bvGLTransactionsFull.Description,
_bvGLTransactionsFull.Credit,
_bvGLTransactionsFull.Reference,
_bvGLTransactionsFull.UserName
From
_bvGLAccountsFinancial Inner Join
_bvGLTransactionsFull On _bvGLAccountsFinancial.AccountLink =
_bvGLTransactionsFull.AccountLink
Where
_bvGLTransactionsFull.Credit
IN
(SELECT Credit AS NumOccurrences
FROM _bvGLTransactionsFull
GROUP BY Credit
HAVING (COUNT(Credit) > 1 ) )
Group By
_bvGLTransactionsFull.AccountDesc, _bvGLAccountsFinancial.Description,
_bvGLTransactionsFull.TxDate, _bvGLTransactionsFull.Description,
_bvGLTransactionsFull.Credit, _bvGLTransactionsFull.Reference,
_bvGLTransactionsFull.UserName, _bvGLAccountsFinancial.Master_Sub_Account,
IsNumeric(_bvGLTransactionsFull.Reference), _bvGLTransactionsFull.TrCode
Having
_bvGLTransactionsFull.TxDate > 01 / 11 / 2014 And
_bvGLTransactionsFull.Reference Like '5_____' And
_bvGLTransactionsFull.Credit > 0.01 And
_bvGLAccountsFinancial.Master_Sub_Account = '90210'
That's because you're matching on the credit field back to your table, which contains duplicates. You need to isolate the rows that are duplicated with ROW_NUMBER:
;WITH CTE AS (
SELECT *, ROW_NUMBER() OVER(PARTITION BY CREDIT ORDER BY (SELECT NULL)) AS RN
FROM _bvGLTransactionsFull)
Select
CTE.AccountDesc,
_bvGLAccountsFinancial.Description,
CTE.TxDate,
CTE.Description,
CTE.Credit,
CTE.Reference,
CTE.UserName
From
_bvGLAccountsFinancial Inner Join
CTE On _bvGLAccountsFinancial.AccountLink = CTE.AccountLink
WHERE CTE.RN > 1
Group By
CTE.AccountDesc, _bvGLAccountsFinancial.Description,
CTE.TxDate, CTE.Description,
CTE.Credit, CTE.Reference,
CTE.UserName, _bvGLAccountsFinancial.Master_Sub_Account,
IsNumeric(CTE.Reference), CTE.TrCode
Having
CTE.TxDate > 01 / 11 / 2014 And
CTE.Reference Like '5_____' And
CTE.Credit > 0.01 And
_bvGLAccountsFinancial.Master_Sub_Account = '90210'
Just as a side note, I would consider using aliases to shorten your queries and make them more readable. Prefixing the table name before each column in a join is very difficult to read.
I trust your code in terms of extracting all data per your criteria. With this, let me have a different approach and see your script "as-is". So then, lets keep first all the records in a temp.
Select
_bvGLTransactionsFull.AccountDesc,
_bvGLAccountsFinancial.Description,
_bvGLTransactionsFull.TxDate,
_bvGLTransactionsFull.Description,
_bvGLTransactionsFull.Credit,
_bvGLTransactionsFull.Reference,
_bvGLTransactionsFull.UserName
-- temp table
INTO #tmpTable
From
_bvGLAccountsFinancial Inner Join
_bvGLTransactionsFull On _bvGLAccountsFinancial.AccountLink =
_bvGLTransactionsFull.AccountLink
Where
_bvGLTransactionsFull.Credit
IN
(SELECT Credit AS NumOccurrences
FROM _bvGLTransactionsFull
GROUP BY Credit
HAVING (COUNT(Credit) > 1 ) )
Group By
_bvGLTransactionsFull.AccountDesc, _bvGLAccountsFinancial.Description,
_bvGLTransactionsFull.TxDate, _bvGLTransactionsFull.Description,
_bvGLTransactionsFull.Credit, _bvGLTransactionsFull.Reference,
_bvGLTransactionsFull.UserName, _bvGLAccountsFinancial.Master_Sub_Account,
IsNumeric(_bvGLTransactionsFull.Reference), _bvGLTransactionsFull.TrCode
Having
_bvGLTransactionsFull.TxDate > 01 / 11 / 2014 And
_bvGLTransactionsFull.Reference Like '5_____' And
_bvGLTransactionsFull.Credit > 0.01 And
_bvGLAccountsFinancial.Master_Sub_Account = '90210'
Then remove the "single occurrence" data by creating a row index and remove all those 1 time indexes.
SELECT * FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY Credit ORDER BY Credit) AS rowIdx
, *
FROM #tmpTable) AS innerTmp
WHERE
rowIdx != 1
You can change your preference through PARTITION BY <column name>.
Should you have any concerns, please raise it first as these are so far how I understood your case.
EDIT : To include those credits that has duplicates.
SELECT
tmp1.*
FROM #tmpTable tmp1
RIGHT JOIN (
SELECT
Credit
FROM (
SELECT
ROW_NUMBER() OVER (PARTITION BY Credit ORDER BY Credit) AS rowIdx
, *
FROM #tmpTable) AS innerTmp
WHERE
rowIdx != 1
) AS tmp2
ON tmp1.Credit = tmp2.Credit

what is the easiest way to put two sets of records in SQL SERVER next to each other?

i am writing following query to put two sets of records next to each other.
WITH cte AS
(SELECT SubSubsidaryAccountCode,
sum(Debit) AS debit,
sum(Credit) AS credit,
ROW_NUMBER() over (PARTITION BY SubSubsidaryAccountCode
ORDER BY SubSubsidaryAccountCode) AS RN
FROM TBLLedgerLevel4
WHERE SubSubsidaryAccountCode LIKE '4%'
OR SubSubsidaryAccountCode LIKE '5%'
GROUP BY SubSubsidaryAccountCode ),
cte2 AS
(SELECT *
FROM TBLLevel4)
SELECT a.SubSubsidaryAccountCode ,
c.SubSubsidaryAccountName AS RevenuName ,
sum(a.Debit) AS debit ,
b.SubSubsidaryAccountCode AS SubsidaryAccount2 ,
d.SubSubsidaryAccountName AS ExpenseName ,
sum(b.Credit) AS credit
FROM cte a
JOIN cte2 c ON a.SubSubsidaryAccountCode = c.SubSubsidaryAccountCode
FULL JOIN cte b ON a.RN = b.RN
JOIN cte2 d ON b.SubSubsidaryAccountCode=d.SubSubsidaryAccountCode
WHERE a.SubSubsidaryAccountCode LIKE '4%'
AND b.SubSubsidaryAccountCode LIKE '5%'
GROUP BY a.SubSubsidaryAccountCode,
b.SubSubsidaryAccountCode,
c.SubSubsidaryAccountName,
d.SubSubsidaryAccountName
Output:
SubSubsidaryAccountCode RevenuName debit SubsidaryAccount2 ExpenseName credit
4-106-1001-10026 Cash Sale 52889 5-105-1005-10011 Rf Battles 18091289
4-108-1012-10037 New Sale1 1000 5-105-1005-10011 Rf Battles 18091289
The above output contains records against two Accounting codes that starts with 4 and 5 .Now the records against accounting code that are starting with accounting code like 4 are populated and are according to desired results but the 2nd row of record starting with accounting code 5 is not populated and must have null value in it.Please help me out to solve this problem.

Find Segment with Longest Stay Per Booking

We have a number of bookings and one of the requirements is that we display the Final Destination for a booking based on its segments. Our business has defined the Final Destination as that in which we have the longest stay. And Origin being the first departure point.
Please note this is not the segments with the Longest Travel time i.e. Datediff(minute, DepartDate, ArrivalDate) This is requesting the one with the Longest gap between segments.
This is a simplified version of the tables:
Create Table Segments
(
BookingID int,
SegNum int,
DepartureCity varchar(100),
DepartDate datetime,
ArrivalCity varchar(100),
ArrivalDate datetime
);
Create Table Bookings
(
BookingID int identity(1,1),
Locator varchar(10)
);
Insert into Segments values (1,2,'BRU','2010-03-06 10:40','FIH','2010-03-06 20:20:00')
Insert into Segments values (1,4,'FIH','2010-03-13 21:50:00','BRU', '2010-03-14 07:25:00')
Insert into Segments values (2,2,'BOD','2010-02-10 06:50:00','AMS','2010-02-10 08:50:00')
Insert into Segments values (2,3,'AMS','2010-02-10 10:40:00','EBB','2010-02-10 20:40:00')
Insert into Segments values (2,4,'EBB','2010-02-28 22:55:00','AMS','2010-03-01 05:35:00')
Insert into Segments values (2,5,'AMS','2010-03-01 10:25:00','BOD','2010-03-01 12:15:00')
insert into Segments values (3,2,'BRU','2010-03-09 12:10:00','IAD','2010-03-09 14:46:00')
Insert into Segments Values (3,3,'IAD','2010-03-13 17:57:00','BRU','2010-03-14 07:15:00')
insert into segments values (4,2,'BRU','2010-07-27','ADD','2010-07-28')
insert into segments values (4,4,'ADD','2010-07-28','LUN','2010-07-28')
insert into segments values (4,5,'LUN','2010-08-23','ADD','2010-08-23')
insert into segments values (4,6,'ADD','2010-08-23','BRU','2010-08-24')
Insert into Bookings values('5MVL7J')
Insert into Bookings values ('Y2IMXQ')
insert into bookings values ('YCBL5C')
Insert into bookings values ('X7THJ6')
I have created a SQL Fiddle with real data here:
SQL Fiddle Example
I have tried to do the following, however this doesn't appear to be correct.
SELECT Locator, fd.*
FROM Bookings ob
OUTER APPLY
(
SELECT Top 1 DepartureCity, ArrivalCity
from
(
SELECT DISTINCT
seg.segnum ,
seg.DepartureCity ,
seg.DepartDate ,
seg.ArrivalCity ,
seg.ArrivalDate,
(SELECT
DISTINCT
DATEDIFF(MINUTE , seg.ArrivalDate , s2.DepartDate)
FROM Segments s2
WHERE s2.BookingID = seg.BookingID AND s2.segnum = seg.segnum + 1) 'LengthOfStay'
FROM Bookings b(NOLOCK)
INNER JOIN Segments seg (NOLOCK) ON seg.bookingid = b.bookingid
WHERE b.Locator = ob.locator
) a
Order by a.lengthofstay desc
)
FD
The results I expect are:
Locator Origin Destination
5MVL7J BRU FIH
Y2IMXQ BOD EBB
YCBL5C BRU IAD
X7THJ6 BRU LUN
I get the feeling that a CTE would be the best approach, however my attempts do this so far failed miserably. Any help would be greatly appreciated.
I have managed to get the following query working but it only works for one at a time due to the top one, but I'm not sure how to tweak it:
WITH CTE AS
(
SELECT distinct s.DepartureCity, s.DepartDate, s.ArrivalCity, s.ArrivalDate, b.Locator , ROW_NUMBER() OVER (PARTITION BY b.Locator ORDER BY SegNum ASC) RN
FROM Segments s
JOIN bookings b ON s.bookingid = b.BookingID
)
SELECT C.Locator, c.DepartureCity, a.ArrivalCity
FROM
(
SELECT TOP 1 C.Locator, c.ArrivalCity, c1.DepartureCity, DATEDIFF(MINUTE,c.ArrivalDate, c1.DepartDate) 'ddiff'
FROM CTE c
JOIN cte c1 ON c1.Locator = C.Locator AND c1.rn = c.rn + 1
ORDER BY ddiff DESC
) a
JOIN CTE c ON C.Locator = a.Locator
WHERE c.rn = 1
You can try something like this:
;WITH CTE_Start AS
(
--Ordering of segments to eliminate gaps
SELECT *, ROW_NUMBER() OVER (PARTITION BY BookingID ORDER BY SegNum) RN
FROM dbo.Segments
)
, RCTE_Stay AS
(
--recursive CTE to calculate stay between segments
SELECT *, 0 AS Stay FROM CTE_Start s WHERE RN = 1
UNION ALL
SELECT sNext.*, DATEDIFF(Mi, s.ArrivalDate, sNext.DepartDate)
FROM CTE_Start sNext
INNER JOIN RCTE_Stay s ON s.RN + 1 = sNext.RN AND s.BookingID = sNext.BookingID
)
, CTE_Final AS
(
--Search for max(stay) for each bookingID
SELECT *, ROW_NUMBER() OVER (PARTITION BY BookingID ORDER BY Stay DESC) AS RN_Stay
FROM RCTE_Stay
)
--join Start and Final on RN=1 to find origin and departure
SELECT b.Locator, s.DepartureCity AS Origin, f.DepartureCity AS Destination
FROM CTE_Final f
INNER JOIN CTE_Start s ON f.BookingID = s.BookingID
INNER JOIN dbo.Bookings b ON b.BookingID = f.BookingID
WHERE s.RN = 1 AND f.RN_Stay = 1
SQLFiddle DEMO
You can use the OUTER APPLY + TOP operators to find the next values SegNum. After finding the gap between segments are used MIN/MAX aggregate functions with OVER clause as conditions in the CASE expression
;WITH cte AS
(
SELECT seg.BookingID,
CASE WHEN MIN(seg.segNum) OVER(PARTITION BY seg.BookingID) = seg.segNum
THEN seg.DepartureCity END AS Origin,
CASE WHEN MAX(DATEDIFF(MINUTE, seg.ArrivalDate, o.DepartDate)) OVER(PARTITION BY seg.BookingID)
= DATEDIFF(MINUTE, seg.ArrivalDate, o.DepartDate)
THEN o.DepartureCity END AS Destination
FROM Segments seg (NOLOCK)
OUTER APPLY (
SELECT TOP 1 seg2.DepartDate, seg2.DepartureCity
FROM Segments seg2
WHERE seg.BookingID = seg2.BookingID
AND seg.SegNum < seg2.SegNum
ORDER BY seg2.SegNum ASC
) o
)
SELECT b.Locator, MAX(c.Origin) AS Origin, MAX(c.Destination) AS Destination
FROM cte c JOIN Bookings b ON c.BookingID = b.BookingID
GROUP BY b.Locator
See demo on SQLFiddle
The statement below:
;WITH DataSource AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY BookingID ORDER BY DATEDIFF(SS,DepartDate,ArrivalDate) DESC) AS Row
,Segments.BookingID
,Segments.SegNum
,Segments.DepartureCity
,Segments.DepartDate
,Segments.ArrivalCity
,Segments.ArrivalDate
,DATEDIFF(SS,DepartDate,ArrivalDate) AS DiffInSeconds
FROM Segments
)
SELECT *
FROM DataSource DS
INNER JOIN Bookings B
ON DS.[BookingID] = B.[BookingID]
Will give the following output:
So, adding the following clause to the above statement:
WHERE Row = 1
will give you what you need.
Few important things:
As you can see from the screenshot below, there are two records with same difference in second. If you want to show both of them (or all of them if there are), instead ROW_NUMBER function use RANK function.
The return type of DATEDIFF is INT. So, there is limitation for seconds max deference value. It is as follows:
If the return value is out of range for int (-2,147,483,648 to
+2,147,483,647), an error is returned. For millisecond, the maximum difference between startdate and enddate is 24 days, 20 hours, 31
minutes and 23.647 seconds. For second, the maximum difference is 68
years.