Howto select (almost) unique values in a specific order - sql

In a trip there are several stops, (a stop = an adress whereone or multiple orders are loaded, or delivered), in a specific order.
For example:
Trip A
Trip_order Action Place Ordernumber
10 Load Paris 394798
20 Load Milan 657748
30 UnLoad Athens 657748
40 Unload Thessaloniki 394798
50 Load Thessaloniki 10142
60 Load Thessaloniki 6577
70 Unload Athens 6577
80 Unload Athens 10412
90 Load Thessaloniki 975147
100 Unload Paris 975147
I want to see the specific stops, in order of the trip:
Load Paris
Load Milan
Unload Athens
Unload Thessaloniki
Load Thessaloniki
Unload Athens
Load Thessaloniki
Unload Paris
I did look at This, but if I do that, I only get the unload Athens, unload Thessaloniki and Load Thessaloniki once.
How do I solve this?
EDIT: 11:11 (UTC +01:00)
To be more specific: these are the tables which present this information:
Trips
Trip_ID
100001
100002
100003
....
Actions
Trip_ID Action MatNr RoOr RoVlg OrderID
100001 1 10 10 1 394798
100001 1 10 20 1 657748
100001 1 10 30 1 657748
100001 1 10 40 1 394798
100001 1 10 50 1 10142
100001 1 10 60 1 6577
100001 1 10 70 1 6577
100001 1 10 80 1 10412
100001 1 10 90 1 975147
100001 1 10 100 1 975147
(Action: 1=load, 4=unload)
The combination of MatNr, RoOr and RoVlg is the order of the Trip.
Orders
OrderID LoadingPlace UnloadingPlace
6577 Thessaloniki Athens
10142 Thessaloniki Athens
394798 Paris Thessaloniki
657748 Milan Athens
975147 Thessaloniki Paris

Try this one. No variables, nothing especially fancy:
select a1.action, a1.place
from trip_a a1
left join trip_a a2
on a2.trip_order =
(select min(trip_order)
from trip_a a3
where trip_order > a1.trip_order)
where a1.action != a2.action or a1.place != a2.place or a2.place is null
Demo here: http://sqlfiddle.com/#!9/4b6dc/13
Hopefully it works on whatever sql you're using, it should, so long as subqueries are supported.
Tt simply finds the next highest trip_id, and joins to it, or joins to null if there is no higher trip_order. It then selects only the rows where either the place, the action, or both are different, or if there is no place in the joined table (a2.place is null).
edited after criteria changed completely
If you want to get the same results, built entirely from your base tables, you can do this:
select
case when a.action = 1 then 'load' when a.action = 0 then 'unload' end as action,
case when a.action = 1 then o.loadingplace when a.action = 0 then o.unloadingplace end as place
from trips t
inner join actions a
on t.trip_id = a.trip_id
inner join orders o
on a.orderid = o.orderid
left join actions a2
on a2.roor =
(select min(roor)
from actions a3
where a3.roor > a.roor)
left join orders o2
on a2.orderid = o2.orderid
where a.action != a2.action
or a2.action is null
or
case when a.action = 1 then o.loadingplace != o2.loadingplace
when a.action = 0 then o.unloadingplace != o2.unloadingplace
end
order by a.roor asc
And here's an updated fiddle: http://sqlfiddle.com/#!9/fdf9c/14

You don't need and you don't want to use distinct for that because we can see in your example that several destinations occur multiple times. What you want: filter out records that match the preceding record in terms of action and place.
This could look something like this:
SELECT *
FROM Trips t1 LEFT JOIN Trips t2 ON t1.Trip_Order = t2.Trip_Order - 10
WHERE t1.Action <> t2.Action OR t1.Place <> t2.Place)

In SQL server, you can get difference of ROW_NUMBER() based trip_order and action,place on and try something like this.
You can use it as a reference to create a similar query in USQL.
Sample Data
DECLARE #Trip TABLE (Trip_order INT, Action VARCHAR(10), Place VARCHAR(50),Ordernumber INT)
INSERT INTO #Trip VALUES
(10 ,'Load', 'Paris', 394798),
(20 ,'Load', 'Milan', 657748),
(30 ,'UnLoad', 'Athens', 657748),
(40 ,'UnLoad', 'Thessaloniki', 394798),
(50 ,'Load', 'Thessaloniki', 10142),
(60 ,'Load', 'Thessaloniki', 6577),
(70 ,'UnLoad', 'Athens', 6577),
(80 ,'UnLoad', 'Athens', 10412),
(90 ,'Load', 'Thessaloniki', 975147),
(100 ,'UnLoad', 'Paris', 975147);
Query
SELECT action,place FROM
(
SELECT *,ROW_NUMBER()OVER(ORDER BY trip_order) - ROW_NUMBER()OVER(ORDER BY action,place) n
FROM #trip
)t
GROUP BY n,action,place
ORDER BY MIN(trip_order)

Try this:
Will work in MySQL:::
SELECT IF(#temp=#temp:=A.TripName, #rank, #rank:=#rank+1) AS rank, A.TripName
FROM (SELECT CONCAT(A.Action, A.Place) AS TripName
FROM TripA A
) A, (SELECT #temp:=0, #rank:=0) AS B
GROUP BY rank

SELECT s.*
FROM stops s LEFT JOIN stops prev ON
( prev.Trip_order < s.Trip_order
AND NOT EXISTS ( SELECT 'a'
FROM stops prev2
WHERE prev2.Trip_order < s.Trip_order
AND prev2.Trip_order > prev.Trip_order
)
)
WHERE s.Action <> COALESCE(prev.Action, '')
OR s.Place <> COALESCE(prev.Place, '')
ORDER BY s.Trip_order

select a1.action,a1.place
from tripa a1,tripa a2
where a2.trip_order = (select min(trip_order) from tripa a3 where trip_order > a1.trip_order)
and (a1.action != a2.action or a1.place != a2.place)
or a2.place is null
This gives you the required result.

Related

Calculate total donations based on an attribute table

I am trying to get a list of donors who have cumulatively donated $5K+ between two different campaigns. My data is something like this
Attributes table
transactionid
attributevalue
123231
campaign 1
123456
campaing 2
123217
campaign 1
45623
campaing 2
65791
campaing 3
78931
campaign 4
11111
campaign 5
22222
campaing 6
Donations table
transactionid
donationamount
donorid
123231
2000
1233
123456
30000
1456
45623
8000
1233
78931
90
8521
11111
20
1233
22222
68
1456
Donor table
donorid
name
1233
John
1456
Mary
8521
Karl
This is what I tried, but the total I am getting is not right at all.
WITH test AS (
SELECT don.donorid,don.donationamount,a.attributevalue
FROM attributes table a
INNER JOIN donations don ON don.transactionid=a.transactionid
)
SELECT d.donorid,
SUM(CASE WHEN test.attributevalue='campaign 1' OR test.attributevalue='campaign 2'
THEN test.donationamount END) AS campaing_donation,
SUM(test.donationamount) AS total_donations
FROM donortable d
INNER JOIN test ON d.donorid = test.donorid
GROUP BY d.donorid
HAVING SUM(CASE WHEN test.attributevalue = 'campaign 1' OR test.attributevalue = 'campaign 2' THEN test.donationamount END) > 5000
but this is not working. My total donations sum is giving a value that is several times higher than the actual value.
Ideally, the final result would be something like this:
donorid
campaign_amount
totalamount
1233
10000
10020
1456
30000
30068
Select
sum (Donations.donationamount),
donor.donorid,
donor.name
from
Attributes
join Donations on
Donations.transactionid = attributes.transactionid
Join Donor on
donor.donorid = donations.donorid
Where
Attribute.attributevalue in ('campaign 1','campaign 2')
Group by
donor.donorid,
donor.name
create table #transection_tbl(tran_id int,attributevalue varchar(20))
create table #donation_tbl(tran_id int,donation_amount int ,donar_id int)
select donar_id,max(donation_amount) as 'campaing_amount',
sum(donation_amount) as 'totalamount'
from #transection_tbl as t1
inner join #donation_tbl as t2 on t1.tran_id=t2.tran_id
group by donar_id
having COUNT(attributevalue)=2

How to assign filters to row number () function in sql

I am trying to extract only single row after name = system in each case where the town is not Austin.
In case 1001 there are 8 rows, row # 4 is system, output should be only the row with Name=Terry and Date Moved=7/4/2019 (Next entry with town /= Austin)
Case Name Town Date Moved Row #(Not in table)
1001 Ted Madisson 9/7/2018 1
1001 Joyal Boston 10/4/2018 2
1001 Beatrice Chicago 1/1/2019 3
1001 System Chicago 1/5/2019 4
1001 John Austin 4/11/2019 5
1001 Simon Austin 6/11/2019 6
1001 Terry Cleveland 7/4/2019 7
1001 Hawkins Newyork 8/4/2019 8
1002 Devon Boston 12/4/2018 1
1002 Joy Austin 12/7/2018 2
1002 Rachael Newyork 12/19/2018 3
1002 Bill Chicago 1/4/2019 4
1002 System Dallas 2/12/2019 5
1002 Phil Austin 3/16/2019 6
1002 Dan Seattle 5/18/2019 7
1002 Claire Birmingham 7/7/2019 8
Tried sub query with row number function and not in ('Austin') filter
ROW_NUMBER() OVER(PARTITION BY Case ORDER BY Moved_date ASC) AS ROWNUM
Please note there are > 10k cases.
You can try this below script-
WITH CTE AS
(
SELECT [Case],[Name],Town,[Date Moved],
ROW_NUMBER() OVER (PARTITION BY [Case] ORDER BY [Date Moved]) [Row #]
FROM your_table
)
SELECT A.*
FROM CTE A
INNER JOIN
(
SELECT C.[Case],C.Town,MAX(C.[Row #]) MRN
FROM CTE C
INNER JOIN
(
SELECT *
FROM CTE A
WHERE A.Name = 'System'
)D ON C.[Case] = D.[Case] AND C.[Row #] > D.[Row #]
AND C.Town = 'Austin'
GROUP BY C.[Case],C.Town
)B ON A.[Case] = B.[Case] AND A.[Row #] = B.MRN+1
Output is -
Case Name Town Date Moved Row #
1001 Terry Cleveland 7/4/2019 6
1002 Dan Seattle 5/18/2019 7
Here are three possibilities. I'm still concerned about ties though. The first one will return multiple rows while the others only one per case:
with matches as (
select t1."case", min(t2."Date Moved") as "Date Moved"
from Movements r1 inner join Movements t2 on t1."case" = t2."case"
where t1.name = 'System' and t2.Town <> 'Austin'
and t2."Date Moved" > t1."Date Moved"
group by t1."case"
)
select t.*
from Movements t inner join matches m
on m."case" = t."case" and m."Date Moved" = t."Date Moved";
select m2.*
from Movements m1 cross apply (
select top 1 * from Movements m2
where m2.Town <> 'Austin' and m2."Date Moved" > m1."Date Moved"
order by m2."Date Moved"
) as match
where m1.name = 'System';
with m1 as (
select *,
count(case when name = 'System') over (partition by "case" order by "Date Moved") as flag
from Movements
), m2 as (
select *,
row_number() over (partition by "case" order by "Date Moved") as rn
from m1
where flag = 1 and name <> 'System' and Town <> 'Austin'
)
select * from m2 where rn = 1;
I'm basically assuming this is SQL Server. You might need a few minor tweaks if not.
It also does not require a town named Austin to fall between the "System" row and the desired row as I do not believe that was a stated requirement.

How to change query result without using pivot functions?

I have a query which shows number of cheap and expensive orders for a date:
SELECT od1.date,
(SELECT COUNT( od2.id) FROM myorders od2
INNER JOIN myproducts ON od2.product_id = myproducts.id
WHERE od2.date = od1.date
AND myproducts.price BETWEEN 500 AND 2000
) cheap_orders,
(SELECT SUM(myproducts.price)
FROM myorders od2
INNER JOIN myproducts ON od2.product_id = myproducts.id
WHERE od2.date = od1.date
AND myproducts.price BETWEEN 500 AND 2000
) sum_ch_o,
(SELECT COUNT( od2.id) FROM myorders od2
INNER JOIN myproducts ON od2.product_id = myproducts.id
WHERE od2.date = od1.date
AND myproducts.price > 2000
) expensive_orders,
(SELECT SUM(myproducts.price)
FROM myorders od2
INNER JOIN myproducts ON od2.product_id = myproducts.id
WHERE od2.date = od1.date
AND myproducts.price > 2000
) sum_exp_o
FROM myorders od1
GROUP BY od1.date
It returns me a table:
date cheap_orders sum_ch_o expensive_orders sum_exp_o
2018-06-06 0 0 1 4000
2018-06-04 2 3100 0 0
2018-06-07 1 780 3 28000
2018-06-05 1 560 4 15500
How can I modify my query to get another view of this table:
date order_cat quantity sum
2018-06-06 cheap 0 0
2018-06-06 expensive 1 4000
2018-06-04 cheap 2 3100
2018-06-04 expensive 0 0
2018-06-07 cheap 1 780
2018-06-07 expensive 3 28000

How to find the record which is not exists with some criteria in SQL Server?

I have two tables.
ItemRelation table having 30k records
ID ChildID1 ChildID2 ChildID3
------------------------------------------
9 null null null
49 43 50 //43 in childid1, don't want this record too
111 112 113 null
65 68 null null
222 221 223 224
79 null null null
5773 5834 5838 null
F_ItemDailySalesParent having millions of records
ItemID StoreId
-----------------
9 1001 //ItemID 9,41,5773 belongs to 1001 StoreID
41 1001
43 1400 //ItemID 43,45,65,5834 belongs to 1400 StoreID
45 1400
65 1400
68 2000 //ItemID 68,79 belongs to 2000 StoreID
79 2000
5773 1001
5834 1400
5838 2000
I want to show the record ID from ItemRelation table where the ItemID from F_ItemDailySalesParent not present in ItemRelation
ItemID StoreID
-----------------
49 1001
111 1001
65 1001
222 1001
79 1001
9 1400
111 1400
222 1400
79 1400
9 2000
49 2000
111 2000
222 2000
5773 2000
I tried this following query. But this will work without StoreID. But no idea for the above result
select ID from HQMatajer.dbo.ItemRelation ir
where not exists(
select ID,StoreID
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] Fid
where fid.ItemID=ir.ID
or fid.ItemID = ir.ChildID1
or Fid.ItemID=ir.ChildID2
or Fid.ItemID=ir.ChildID3
and time between '2017-01-01 00:00:00.000' and '2017-02-28 00:00:00.000'
group by ItemID,StoreID
)
Update
I have Hqmatajer.dbo.Store that column name of storeCode = F_ItemDailySalesParent.Storeid
Include checking if StoreId matches when using the not exists()
select ID
from HQMatajer.dbo.ItemRelation ir
cross join (select distinct storeCode from Hqmatajer.dbo.Store) s
where not exists(
select 1
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] Fid
where fid.StoreId = s.StoreCode
and [time] between '2017-01-01 00:00:00.000' and '2017-02-28 00:00:00.000'
and ( fid.ItemID=ir.ID
or fid.ItemID=ir.ChildID1
or Fid.ItemID=ir.ChildID2
or Fid.ItemID=ir.ChildID3
)
)
If I understand correctly, you want to start with a list of all stores and items and then filter out the ones that are present.
select i.id, s.storeId
from (select distinct id from HQMatajer.dbo.ItemRelation ir) i cross join
stores s -- assume this exists
where not exists (select 1
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] idsp
where idsp.ItemID = i.ID and idsp.storeId = s.storeId
) and
not exists (select 1
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] idsp
where idsp.ItemID = i.childID1 and idsp.storeId = s.storeId
) and
not exists (select 1
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] idsp
where idsp.ItemID = i.childID2 and idsp.storeId = s.storeId
) and
not exists (select 1
from [HQWebMatajer].[dbo].[F_ItemDailySalesParent] idsp
where idsp.ItemID = i.childID3 and idsp.storeId = s.storeId
);
I did not include the time condition. It is not in your sample data, so it is unclear where it fits.
First get a unique list of ItemIds and unique list of StoreIDs, then you can see which are missing with a left join and a where cross ref table id is null. I'll do it in generic terms so you get the idea:
select s.StoreId, i.ItemId
from Stores s
cross apply Items i
left join ItemRelation ir
on s.StoreId = ir.StoreId
and i.ItemId = ir.ItemId
where ir.Id is null

How do I write sql query from this result?

I wasn't sure what could be the title for my question so sorry about that.
I'm trying to write a SQL query to achieve the no. of members who should get reimbursed from a pharmacy.
For example : I went to pharmacy, I took a vaccine but by mistake I paid from my pocket. so now Pharmacy needs to reimburse me that amount. Lets say I have the data like:
MemberId Name ServiceDate PresNumber PersonId ClaimId AdminFee(in $)
1 John 1/1/2011 123 345 456 0
1 John 1/21/2011 123 345 987 20
2 Mike 2/3/2011 234 567 342 0
2 Mike 2/25/2011 234 567 564 30
5 Linda 1/4/2011 432 543 575 0
5 Linda 4/6/2011 987 543 890 0
6 Sonia 2/6/2011 656 095 439 0
This data shows all members from that pharmacy who got reimbursed and who haven't.
I need to find out the member having AdminFee 0 but i also need to check another record for the same member having same PresNumber, same PersonId where the ServiceDate falls within 30 Days of the Original Record.
If another record meets this criteria and the AdminFee field contains a value (is NOT 0) then it means that person has already been reimbursed. So from the data you can see John and Mike have already been reimbursed and Linda and Sonia need to be reimbursed.
Can anybody help me how to write an SQL query on this?
You don't mention what SQL engine you're using, so here is some generic SQL. You'll need to adapt the date math and the return of True/False ( in the second option) to whatever engine you're using:
-- Already reimbursed
SELECT * FROM YourTable YT1 WHERE AdminFee = 0 AND EXISTS
(SELECT * FROM YourTable YT2
WHERE YT2.MemberID = YT1.MemberID AND
YT2.PresNumber = YT1.PresNumber AND
YT2.ServiceDate >= YT1.ServiceDate - 30 AND
AdminFee > 0)
-- Need reimbursement
SELECT * FROM YourTable YT1 WHERE AdminFee = 0 AND NOT EXISTS
(SELECT * FROM YourTable YT2
WHERE YT2.MemberID = YT1.MemberID AND
YT2.PresNumber = YT1.PresNumber AND
YT2.ServiceDate >= YT1.ServiceDate - 30 AND
AdminFee > 0)
or
-- Both in one.
SELECT YT1.*,
CASE WHEN YT2.MemberID IS NULL THEN False ELSE True END AS AlreadyReimbursed
FROM YourTable YT1 JOIN YourTable YT2 ON
YT1.MemberID = YT2.MemberID AND
YT1.PresNumber = YT2.PresNumber AND
YT1.ServiceDate <= YT2.ServiceDate + 30
WHERE YT1.AdminFee = 0 AND YT2.AdminFee > 0)
You need to use datediff function in SQL Server and as parameter to pass day and to join the table above by other alias. I do not have SQL Server but I think it should be like this
Select memberid
from PaymentLog p
inner join PaymentLog d on p.serviceid = d.serviceid
and p.memberid = d.memberid
and p.personid = d.personid
Where adminfee = 0
and datediff(day, p.servicedate, d.servicedate) < 30
I called a table paymentlog