Combining multiple rows into a single row SQL - sql

I have a table like this.
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 8 |
|1 | A | A.1 | 01-05-2020 | 100 | 75 | 1 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 01-01-2020 | 100 | 55 | 11 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 10 |
I need to display a single row based on the latest PricedDate and Sum of Company Fee
|InvID| Client | Group | PricedDate | TotalFee | RepricedFee | CompanyFee|
|1 | A | A.1 | 02-24-2020 | 100 | 80 | 9 |
|2 | A | A.1 | 01-09-2020 | 100 | 60 | 1 |
|3 | B | B.1 | 01-11-2020 | 150 | 95 | 10 |
|4 | B | B.1 | 02-01-2020 | 100 | 90 | 21 |

Is it the latest row per InvID you want? I would probably just get the maximum date and the sum in an aggregation query and then join that row:
select
t.invid,
t.client,
t.group,
t.priceddate,
t.totalfee,
t.repricedfee,
agg.sum_fee as companyfee
from
(
select invid, max(priceddate) as max_date, sum(companyfee) as sum_fee
from mytable
group by invid
) agg
join mytable t on t.invid = agg.invid and t.priceddate = agg.max_date
order by t.invid;

just do aggregation
select invId,client,[group],max(priceddate),max(Totalfee),min(repricedFee),sum(companyfee)
from table
group by invId,client,[group]

Try it like this:
select *
, (select sum(CompanyFee) from my_table mt3 group by InvID) CompanyFee
from my_table mt1
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID);
This part will make sure your data is from the row that has the largest PricedDate :
mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)
Also, if it is not enough to group by InvID only you can add other columns there.
Here is a demo

Try this,
declare #CompanyFee= select sum(CompanyFee) from table1
select InvID,Client,Group,PricedDate,TotalFee,RepricedFee,#CompanyFee from table1
where priceddate=max(priceddate)

Try this.
select *
from my_table mt1
cross apply (
select CompanyFee=sum(CompanyFee) from my_table mt3 where mt3.invid=mt1.invid
) as CompanyFeeTbl
where mt1.PricedDate = (select max(mt2.PricedDate)
from my_table mt2
where mt2.InvID = mt1.InvID)

You can use window function :
select t.InvID, t.Client, t.Group, t.PricedDate,
t.TotalFee, t.RepricedFee, t.SumCompanyFee as CompanyFee
from(select t.*, sum(t.companyfee) over (partition by t.client, t.invId) as SumCompanyFee,
row_number() over (partition by t.client, t.invId order by t.PricedDate desc) as seq
from table t
) t
where seq = 1;

Related

SQL Count In Range

How could I count data in range which could be configured
Something like this,
CAR_AVBL
+--------+-----------+
| CAR_ID | DATE_AVBL |
+--------------------|
| JJ01 | 1 |
| JJ02 | 1 |
| JJ03 | 3 |
| JJ04 | 10 |
| JJ05 | 13 |
| JJ06 | 4 |
| JJ07 | 10 |
| JJ08 | 1 |
| JJ09 | 23 |
| JJ10 | 11 |
| JJ11 | 20 |
| JJ12 | 3 |
| JJ13 | 19 |
| JJ14 | 22 |
| JJ15 | 7 |
+--------------------+
ZONE_CFG
+--------+------------+
| DATE | ZONE_DESCR |
+--------+------------+
| 15 | GREEN_ZONE |
| 25 | YELLOW_ZONE|
| 30 | RED_ZONE |
+--------+------------+
Table ZONE_CFG is configurable, so I could not use static value for this
The DATE column mean maximum date for each ZONE
And the result what I expected :
+------------+----------+
| ZONE_DESCR | AVBL_CAR |
+------------+----------+
| GREEN_ZONE | 11 |
| YELLOW_ZONE| 4 |
| RED_ZONE | 0 |
+------------+----------+
Please could someone help me with this
You can use LAG and group by as following:
SELECT
ZC.ZONE_DESCR,
COUNT(1) AS AVBL_CAR
FROM
CAR_AVBL CA
JOIN ( SELECT
ZONE_DECR,
COALESCE(LAG(DATE) OVER(ORDER BY DATE) + 1, 0) AS START_DATE,
DATE AS END_DATE
FROM ZONE_CFG ) ZC
ON ( CA.DATE_AVBL BETWEEN ZC.START_DATE AND ZC.END_DATE )
GROUP BY
ZC.ZONE_DESCR;
Note: Don't use oracle preserved keywords (DATE, in your case) as the name of the columns. Try to change it to something like DATE_ or DATE_START or etc..
Cheers!!
If you want the zero 0, I might suggest a correlated subquery instead:
select z.*,
(select count(*)
from car_avbl c
where c.date_avbl >= start_date and
c.date_avbl <= date
) as avbl_car
from (select z.*,
lag(date, 1, 0) as start_date
from zone_cfg z
) z;
In Oracle 12C, can phrase this using a lateral join:
select z.*,
(c.cnt - lag(c.cnt, 1, 0) over (order by z.date)) as cnt
from zone_cfg z left join lateral
(select count(*) as cnt
from avbl_car c
where c.date_avbl <= z.date
) c
on 1=1

eSQL multiple join but with conditions

I've 3 tables as under
MERCHANDISE
+-----------+-----------+---------------+
| MERCH_NUM | MERCH_DIV | MERCH_SUB_DIV |
+-----------+-----------+---------------+
| 1 | car | awd |
| 1 | car | awd |
| 2 | bike | 1kcc |
| 3 | cycle | hybrid |
| 3 | cycle | city |
| 4 | moped | fixie |
+-----------+-----------+---------------+
PRIORITY
+----------+-----------+---------+---------+------------+------------+---------------+
| CUST_NUM | SALES_NUM | DOC_NUM | BALANCE | PRIORITY_1 | PRIORITY_2 | PRIORITY_CODE |
+----------+-----------+---------+---------+------------+------------+---------------+
| 90 | 1000 | 10 | 23 | 1 | 6 | NO |
| 91 | 1001 | 20 | 32 | 3 | 7 | PRI |
| 92 | 1002 | 30 | 11 | 2 | 8 | LATE |
| 93 | 1003 | 40 | 22 | 5 | 9 | 1MON |
+----------+-----------+---------+---------+------------+------------+---------------+
ORDER
+----------+-----------+---------+---------+-----------+-----------+
| CUST_NUM | SALES_NUM | DOC_NUM | COUNTRY | MERCH_NUM | MERCH_DIV |
+----------+-----------+---------+---------+-----------+-----------+
| 90 | 1000 | 10 | INDIA | 1 | car |
| 91 | 1001 | 20 | CHINA | 2 | bike |
| 92 | 1002 | 30 | USA | 3 | cycle |
| 93 | 1003 | 40 | UK | 4 | moped |
+----------+-----------+---------+---------+-----------+-----------+
I want to join the left joined table from the last two tables with the first one such that the MERCH_SUB_DIV 'awd' appears only once for each unique combination of merch_num and merch_div
the code I came up with is as under, but I'm not sure how do I eliminate the duplicate row just for the awd
select
ROW#, MERCH.MERCH_NUMBER, ORDPRI.MERCH_NUMBER, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, ITEM_NUM, RANK, PRIORITY_1
from (
select
ROW_NUMBER() OVER(
PARTITION BY ORD.DOC_NUM, ORD.ITEM_NUM
ORDER BY ORD.DOC_NUM, ORD.ITEM_NUM ASC
) AS Row#,
ORD.CUST_NUM, PRI.CUST_NUM, ORD.MERCH_NUM, ORD.MERCH_DIV, PRI.BALANCE,
pri.DOC_NUM, pri.SALES_NUM, pri.PRIORITY_1, pri.PRIORITY_2
from ORDER as ORD
left join PRIORITY as PRI on ORD.DOC_NUM = PRI.DOC_NUM
and ORD.SALES_NUMBER = PRI.SALES_NUM
where country_name in ('USA', ‘INDIA’)
) as ORDPRI
left join MERCHANDISE as MERCH on ORDPRI.DIV = MERCH.DIV
and ORDPRI.MERCH_NUM = MERCH.MERCH_NUM
You have to use 'DISTINCT' keyword to get unique values, but if your 'Priority table' & 'Order table' contains different values for Same MERCH_NUM then the final result contains the repetation of the 'MERCH_NUM'.
SELECT DISTINCT M.MERCH_NUMBER, O.MERCH_NUMBER, O.CUST_NUM, BALANCE, SALES_NUM,ITEM_NUM,RANK,PRIORITY_1
FROM priority_table P
LEFT JOIN order_table O ON P.CUST_NUM = O.CUST_NUM AND P.SALES_NUM=O.SALES_NUM AND P.DOC_NUM = O.DOC_NUM
LEFT JOIN merchandise_table M ON M.MERCH_NUM = O.MERCH_NUM
A way around can be to add one new Row_Number() in the outermost query having Partition by MERCH_SUB_DIV + all the columns in the final list and then filter final results based on the New Row_Number() . Follows a pseudo code that might help:
select
-- All expected columns in final result except the newRow#
ROW#, MERCH_NUM, CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
from (
select
ROW#,
-- the new row number includes all column you want to show in final result
row_number() over ( PARTITION BY MERCH.MERCH_SUB_DIV ,
MERCH.MERCH_NUM, ORDPRI.MERCH_NUM, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
order by (select 1 )) as newRow# ,
MERCH.MERCH_NUM, ORDPRI.CUST_NUM,
BALANCE, SALES_NUM, PRIORITY_1
from (
-- main query goes here
select
ROW_NUMBER() OVER(
PARTITION BY ORD.DOC_NUM --, ORD.ITEM_NUM
ORDER BY ORD.DOC_NUM ASC --, ORD.ITEM_NUM
) AS Row#,
ORD.CUST_NUM, ORD.MERCH_NUM, ORD.MERCH_DIV as DIV, PRI.BALANCE,
pri.DOC_NUM, pri.SALES_NUM, pri.PRIORITY_1, pri.PRIORITY_2
from #ORDER as ORD
left join #PRIORITY as PRI on ORD.DOC_NUM = PRI.DOC_NUM
and ORD.SALES_NUMBER = PRI.SALES_NUM
where country_name in ('USA', 'INDIA')
) as ORDPRI
left join #MERCHANDISE as MERCH on ORDPRI.DIV = MERCH.DIV
and ORDPRI.MERCH_NUM = MERCH.MERCH_NUM
) as T
-- final filter to get distinct values
where newRow# = 1
Sample code here .. Hope this helps!!

SQL: How to return just 1 previous date for a record, not all previous dates

I have a very simple table of ID's and Sign-in dates and I want to use SQL to make a column that shows the previous sign-in date:
Table: SIGNIN
| ID | Sign-in Date |
| A | 01/01/19 |
| B | 01/01/19 |
| C | 02/01/19 |
| A | 02/01/19 |
| A | 03/01/19 |
| B | 03/01/19 |
| A | 04/01/19 |
| C | 04/01/19 |
| B | 05/01/19 |
I've tried doing a join to itself but it's showing all previous sign-in dates rather than just the most recent.
SELECT [SIGNIN].ID
[SIGNIN].SignInDate
FROM [SIGNIN]
INNER JOIN [SIGNIN] as [Prev] on [SIGNIN].ID = [Prev].ID
and [SIGNIN].SignInDate < [Prev].SignInDate
ORDER BY [SIGNIN].ID, [SIGNIN].SignInDate
The result I want:
Table: SIGNIN
| ID | Sign-in Date | Previous |
| A | 01/01/19 | NULL |
| B | 01/01/19 | NULL |
| C | 02/01/19 | NULL |
| A | 02/01/19 | 01/01/19 |
| A | 03/01/19 | 02/01/19 |
| B | 03/01/19 | 01/01/19 |
| A | 04/01/19 | 03/01/19 |
| C | 04/01/19 | 02/01/19 |
| B | 05/01/19 | 03/01/19 |
What I'm getting:
| ID | Sign-in Date | Previous |
| A | 01/01/19 | NULL |
| B | 01/01/19 | NULL |
| C | 02/01/19 | NULL |
| A | 02/01/19 | 01/01/19 |
| A | 03/01/19 | 01/01/19 |
| A | 03/01/19 | 02/01/19 |
| B | 03/01/19 | 01/01/19 |
| A | 04/01/19 | 01/01/19 |
| A | 04/01/19 | 02/01/19 |
| A | 04/01/19 | 03/01/19 |
| C | 04/01/19 | 02/01/19 |
| B | 05/01/19 | 01/01/19 |
| B | 05/01/19 | 03/01/19 |
I'm certain this has been answered elsewhere before, but the biggest problem I'm having is not knowing how to word my problem!
EDIT: Really helpful responses so far, but is there a solution where I can change the date "cut-off" eg:
Cut off: 03/01/19
Table: The same
Desired result:
| ID | Sign-in Date | Previous |
| A | 03/01/19 | 02/01/19 |
| B | 03/01/19 | 01/01/19 |
| A | 04/01/19 | 03/01/19 |
| C | 04/01/19 | 02/01/19 |
| B | 05/01/19 | 03/01/19 |
I think that if you need to do that it's better to make an ordering column like:
SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY SignInDate) AS O FROM [SIGNIN]
So the end result would be like:
SELECT t.ID, t.SignInDate [Sign-In Date], t2.SignInDate as Previous
FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY SignInDate) AS O FROM [SIGNIN]) t
LEFT JOIN
(SELECT *, ROW_NUMBER() OVER(PARTITION BY ID ORDER BY SignInDate) AS O FROM [SIGNIN]) t2 ON t.ID = t2.ID AND t.O = t2.O+1
Which should give Something akin to:
A 2019-01-01 NULL
A 2019-01-04 2019-01-01
A 2019-02-01 2019-01-04
B 2019-01-01 NULL
B 2019-01-05 2019-01-01
C 2019-01-01 NULL
Hope this helps.
Try using LAG assuming you're on a modern version of SQL Server.
SELECT [SIGNIN].ID,
[SIGNIN].SignInDate,
LAG([SIGNIN].SignInDate) OVER (PARTITION BY [SIGNIN].ID ORDER BY [SIGNIN].SignInDate DESC) AS Previous
FROM [SIGNIN]
using this:
SELECT [SIGNIN].ID,
[SIGNIN].SignInDate,
MAX([Prev].SignInDate) as Previous
FROM [SIGNIN]
LEFT JOIN [SIGNIN] as [Prev] on [SIGNIN].ID = [Prev].ID
and [SIGNIN].SignInDate > [Prev].SignInDate
GROUP BY [SIGNIN].ID, [SIGNIN].SignInDate
ORDER BY [SIGNIN].ID, [SIGNIN].SignInDate
Try something like this:
SELECT
ID, SignInDate,
LAG(SignInDate, 1,SignInDate) OVER(order by ID partition by ID)
FROM SIGNIN
The following will give you almost what you are looking for, just without the nulls.
You should probably do a left outer or right outer join in the inner query, and some extra maneuver to add the null rows as well. I am a lit
select id, max(prev) as prev, signindate from
(
SELECT SIGNIN.ID,
SIGNIN.SignInDate as prev,
prev.signindate
FROM SIGNIN
JOIN SIGNIN as Prev on SIGNIN.ID = Prev.ID
and SIGNIN.SignInDate < Prev.SignInDate
ORDER BY SIGNIN.ID, SIGNIN.SignInDate
) a
group by 1,3
I like the APPLY solution because you can add any amount of columns from the matching row(s):
DECLARE #CutOffDate DATE = '2019-01-03'
SELECT
S.ID,
S.SignInDate,
PreviousSignInDate = R.SignInDate
FROM
[SIGNIN] AS S
OUTER APPLY (
SELECT TOP 1
P.* -- Can incorporate many columns (will also have to add them on the outmost SELECT list)
FROM
SIGNIN AS P
WHERE
S.ID = P.ID AND
P.SignInDate < S.SignInDate
ORDER BY
P.SignInDate DESC
) AS R
WHERE
S.SignInDate >= #CutOffDate
ORDER BY
S.SignInDate,
S.ID
For this case, you can use TOP 1 + ORDER BY to fetch the previous one, as long as you have the link S.ID = P.ID and making sure that P.SignInDate < S.SignInDate.
Also get used to writing dates on the YYYY-MM-DD format, since 03/01/19 might lead to confusions.
A correlated subquery is a very simple solution :
SELECT ID, SignInDate,
(SELECT top 1 SigInDate
FROM SIGNIN as S2
WHERE S2.ID = S1.ID and S2.SignInDate < S1.SignInDate
ORDER BY S2.SignInDate desc) as Previous
FROM SIGNIN as S1
ORDER BY S1.ID, S1.SignInDate

SQL Server minimum value within column

I have table_1 with the following data:
| STORE | Add | dis | Cnt |
+-------+-----+-----+-----+
| 101 | X | abc | 2 |
| 101 | X | null| 3 |
| 101 | X |pqrd | 4 |
| 101 | X | null| 1 |
| 102 | y | null| 1 |
| 102 | y | xyz | 3 |
| 102 | y | pqr | 4 |
| 102 | y | null| 2 |
I tried to build a query to get data from table_1 where [dis] is not null and [cnt] should be minumum. So my result should looks like below:
| STORE | Add | dis | Cnt |
+-------+-----+-----+-----+
| 101 | X | abc | 2 |
| 102 | y | xyz | 3 |
My query looks like below :
SELECT store,add,dis,min(TMPLT_PRIORITY_NMBR)
FROM table_1 group by store,add;
But I get the following error:
Column 'dis' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
If I use [dis] in GROUP BY clause, I get the wrong result and giving max(dis) or min(dis) also provides the wrong result.
What would be the solution for this issue?
You could use rank to find the row with the minimal cnt value per store/add combination, and return all the columns from it:
SELECT store, add, dis, cnt
FROM (SELECT *, RANK() OVER (PARTITION BY store, add ORDER BY cnt) AS rk
FROM table_1
WHERE dis IS NOT NULL) t
WHERE rk = 1
Another option would be to use first_value and min with over:
SELECT distinct store,
add,
first_value(dis) over(partition by store, add order by Cnt) as dis,
min(Cnt) over(partition by store, add) as cnt
FROM table_1

Want to JOIN fourth table in query

I have four tables:
mls_category
points_matrix
mls_entry
bonus_points
My first table (mls_category) is like below:
*--------------------------------*
| cat_no | store_id | cat_value |
*--------------------------------*
| 10 | 101 | 1 |
| 11 | 101 | 4 |
*--------------------------------*
My second table (points_matrix) is like below:
*----------------------------------------------------*
| pm_no | store_id | value_per_point | maxpoint |
*----------------------------------------------------*
| 1 | 101 | 1 | 10 |
| 2 | 101 | 2 | 50 |
| 3 | 101 | 3 | 80 |
*----------------------------------------------------*
My third table (mls_entry) is like below:
*-------------------------------------------*
| user_id | category | distance | status |
*-------------------------------------------*
| 1 | 10 | 20 | approved |
| 1 | 10 | 30 | approved |
| 1 | 11 | 40 | approved |
*-------------------------------------------*
My fourth table (bonus_points) is like below:
*--------------------------------------------*
| user_id | store_id | bonus_points | type |
*--------------------------------------------*
| 1 | 101 | 200 | fixed |
| 2 | 102 | 300 | fixed |
| 1 | 103 | 4 | per |
*--------------------------------------------*
Now, I want to add bonus points value into the sum of total distance according to the store_id, user_id and type.
I am using the following code to get total distance:
SELECT MIN(b.value_per_point) * d.total_distance FROM points_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category
) d ON b.store_id = d.store_id AND b.maxpoint >= d.total_distance
The above code is correct to calculate value, now I want to JOIN my fourth table.
This gives me sum (60*3 = 180) as total value. Now, I want (60+200)*3 = 780 for user 1 and store id 101 and value is fixed.
i think your query will be like below
SELECT Max(b.value_per_point)*( max(d.total_distance)+max(bonus_points)) FROM mls_point_matrix b
JOIN
(
SELECT store_id, sum(t1.totald/c.cat_value) as total_distance FROM mls_category c
JOIN
(
SELECT SUM(distance) totald, user_id, category FROM mls_entry
WHERE user_id= 1 AND status = 'approved' GROUP BY user_id, category
) t1 ON c.cat_no = t1.category group by store_id
) d ON b.store_id = d.store_id inner join bonus_points bp on bp.store_id=d.store_id
DEMO fiddle