SQL combine two rows with common batch number into one line - sql

I have an Oracle SQL (select *) query that spools batch transactional information, the result of which outputs the main sender and receivers to different lines for the same transaction like below:
BATCH_NO DATE AMOUNT SENDER_ACCT_NO SENDER NAME RECEIVER_ACCT_NO RECEIVER NAME
12345678 15-May-20 103.55 400001000 ENDCORP (null)
12345678 15-May-20 34.55 400001000 (null) 78909909 JOHN SMITH
12345678 15-May-20 44.00 400001000 (null) 78853229 HENRY FAGIL
12345678 15-May-20 4.00 400001000 (null) 76849852 BENJAMIN HOOK
12345678 15-May-20 21.00 400001000 (null) 72555099 FELIX MAGS
12222222 16-May-20 1902.00 400000105 ZENDCORP (null)
12222222 17-May-20 1899.00 400000105 (null) 90333300 KEN GOTL
12222222 18-May-20 3.00 400000105 (null) 90127765 JONES MAST
In this case, ENDCORP and ZENDCORP sent out batch payments of a total of 103.55 and 1902.00 to 4 and 2 clients respectively.
However, I would prefer my results to be in the form below:
BATCH_NO DATE AMOUNT SENDER_ACCT_NO SENDER NAME RECEIVER_ACCT_NO RECEIVER NAME
12345678 15-May-20 34.55 400001000 ENDCORP 78909909 JOHN SMITH
12345678 15-May-20 44.00 400001000 ENDCORP 78853229 HENRY FAGIL
12345678 15-May-20 4.00 400001000 ENDCORP 76849852 BENJAMIN HOOK
12345678 15-May-20 21.00 400001000 ENDCORP 72555099 FELIX MAGS
12222222 17-May-20 1899.00 400000105 ZENDCORP 90333300 KEN GOTL
12222222 18-May-20 3.00 400000105 ZENDCORP 90127765 JONES MAST
I would appreciate guidance on how to formulate my query for this result

I'm assuming when you say the query is select * that you are showing us the table structure. Otherwise we'd need more detail of the tables and your query.
If it's one table, you just need to select where sender name is null and join to the same table on sender acct no and sender name is not null, selecting appropriate columns from each relation.

I think you can use window functions to impute the name and then filter:
select . . . -- columns you want
from (select t.*,
max(sender_name) over (partition by batch_no) as imputed_sender_name
from t
) t
where RECEIVER_ACCT_NO is not null;

Related

How to select data in SQL based on a filter which changes if there is no data in a specific table column?

I have tables similar to the three below. I need to join the first two tables based on id, and then join the third table based on second name. However the last table needs a filter where the city should be equal to London unless age is empty in which case the city should equal Manchester.
I tried the code below using CASE statement but it is not working. I am new to SQL so I was not sure how can I combine a where statement with an if clause where the filter for the selection changes depending on whether there is data in a different column than the one used to filter by. The DBMS I am using Toad for Oracle.
FIRST.NAME.TABLE
ID FIRST_NAME ENTRY_DATE
1 JOHN 09/09/2019
2 NICOLA 09/09/2019
3 PATRICK 05/09/2019
4 JOAN 01/09/2019
5 JAKE 09/09/2019
6 AMELIA 01/09/2019
7 CAMERON 09/09/2019
SECOND.NAME.TABLE
ID SECOND_NAME ENTRY_DATE
1 BROWN 09/09/2019
2 SMITH 09/09/2019
3 COLE 05/09/2019
4 HOUSTON 01/09/2019
5 FARRIS 09/09/2019
6 HATHAWAY 01/09/2019
7 JONES 09/09/2019
CITY.AGE.TABLE
CITY SECOND_NAME AGE
LONDON BROWN 24.00
LONDON SMITH
MANCHESTER COLE 30.00
MANCHESTER HOUSTON 66.00
LONDON FARRIS
LONDON HATHAWAY 32.00
GLASGOW JONES 28.00
MANCHESTER SMITH 32.00
LONDON FARRIS 62.00
SELECT FN.ID,
FN.FIRST_NAME,
SN.SECOND_NAME,
AC.CITY,
AC.AGE
FROM FIRST.NAME.TABLE AS FN
INNER JOIN SECOND.NAME.TABLE SN
ON FN.ID=SN.ID
INNER JOIN CITY.AGE.TABLE AS CA
ON SN.SECOND NAME=AC.SECOND_NAME
WHERE FN.ENTRY_DATE='09-SEP-19'
AND SN.ENTRY_DATE='09-SEP-19'
AND (CASE WHEN AC.CITY='LONDON' AND AC.AGE IS NOT NULL
THEN AC.CITY='LONDON'
ELSE AS.CITY='MANCHESTER' END)
You can express this as boolean logic:
WHERE FN.ENTRY_DATE = DATE '2019-09-09' AND
SN.ENTRY_DATE = DATE '2019-09-09' AND
(AC.AGE IS NOT NULL AND AC.CITY = 'LONDON' OR
AC.AGE IS NULL AND AC.CITY = 'MANCHESTER'
)
This answers your question about how to implement the logic using SQL. However, I'm not sure that is the logic that you really want. I speculate that you really want a LEFT JOIN to the age table.

Compute sum of hours/day with overlapping periods, SQL

I have a table with the start date(event.startdate), end date(event.enddate), and the hours/person (event.hrday) of an event. I have another table with weekdays listed which has another field for each person (calendar.name). I want to populate those columns with the total hours worked each day. I can't seem to figure out how to properly sum the hours if two events overlap in dates, I can only come up with a correct value for a single event in a time period.
I believe that in theory this question has the answer I need: compute sum of values associated with overlapping date ranges
But I am very new to SQL and I don't fundamentally understand the solution posted even after some additional research. I am using Access 2013. Apologies if this is a super elementary question, I was hoping what I wanted to do could be handled "visually" with Access...
What I Have: ("event" table)
Startdate | Enddate | Hrsday | Name
5/1/2015 5/12/2015 1.25 Joe
5/7/2015 5/8/2015 8 Joe
What I'm looking for:("calendar" table, days already filled in first column)
Weekdays | Joe | name2 | name3 | ....
5/1/2015 1.25
5/4/2015 1.25
5/5/2015 1.25
5/6/2015 1.25
5/7/2015 9.25
5/8/2015 9.25
5/11/2015 1.25
5/12/2015 1.25
I've tried using the query builder within access to build an UPDATE query, but my result either does not appear at all (no updates, all null) or will only fill in one event with no overlaps. (5/1-5/12 all have 1.25).
I think you will need to create a "date table" if you want to be able to achieve this sort of result in MS-Access (without using windowed functions).
Here is a quick example of how this might work in SQL Server, but only using syntax available to MS-Access (I hope).
--Load the test data into a table variable
DECLARE #event TABLE (
[start_date] DATE,
end_date DATE,
hrsperday NUMERIC(19,2),
name VARCHAR(20));
INSERT INTO #event SELECT '20150401', '20150412', 1.25, 'Joe';
INSERT INTO #event SELECT '20150407', '20150408', 8, 'Joe';
--Add some more test data, to make it more "interesting"
INSERT INTO #event SELECT '20150401', '20150405', 0.1, 'Bill';
INSERT INTO #event SELECT '20150401', '20150430', 7.5, 'Bill';
INSERT INTO #event SELECT '20150412', '20150415', 0.5, 'Bill';
--Make a date table, this creates one on the fly but wouldn't work in MS-Access
--I store a date for each day in 2015/Apr, obviously I would want more dates eventually
DECLARE #dates TABLE (
[date] DATE);
WITH cte AS (
SELECT CONVERT(DATE, '20150401') AS [date]
UNION ALL
SELECT DATEADD(DAY, 1, [date]) FROM cte WHERE [date] < '20150430')
INSERT INTO
#dates
SELECT
[date]
FROM
cte OPTION (MAXRECURSION 0);
--Now the answer is trivial
SELECT
e.name,
d.[date],
SUM(hrsperday) AS hrs
FROM
#dates d
LEFT JOIN #event e ON d.[date] BETWEEN e.[start_date] AND e.end_date
GROUP BY
e.name,
d.[date]
ORDER BY
e.name,
d.[date];
--Note the format you want, but a PIVOT would give you this
--(I don't think PIVOT is supported by MS-Access though)
Results for this are:
name date hrs
Bill 2015-04-01 7.60
Bill 2015-04-02 7.60
Bill 2015-04-03 7.60
Bill 2015-04-04 7.60
Bill 2015-04-05 7.60
Bill 2015-04-06 7.50
Bill 2015-04-07 7.50
Bill 2015-04-08 7.50
Bill 2015-04-09 7.50
Bill 2015-04-10 7.50
Bill 2015-04-11 7.50
Bill 2015-04-12 8.00
Bill 2015-04-13 8.00
Bill 2015-04-14 8.00
Bill 2015-04-15 8.00
Bill 2015-04-16 7.50
Bill 2015-04-17 7.50
Bill 2015-04-18 7.50
Bill 2015-04-19 7.50
Bill 2015-04-20 7.50
Bill 2015-04-21 7.50
Bill 2015-04-22 7.50
Bill 2015-04-23 7.50
Bill 2015-04-24 7.50
Bill 2015-04-25 7.50
Bill 2015-04-26 7.50
Bill 2015-04-27 7.50
Bill 2015-04-28 7.50
Bill 2015-04-29 7.50
Bill 2015-04-30 7.50
Joe 2015-04-01 1.25
Joe 2015-04-02 1.25
Joe 2015-04-03 1.25
Joe 2015-04-04 1.25
Joe 2015-04-05 1.25
Joe 2015-04-06 1.25
Joe 2015-04-07 9.25
Joe 2015-04-08 9.25
Joe 2015-04-09 1.25
Joe 2015-04-10 1.25
Joe 2015-04-11 1.25
Joe 2015-04-12 1.25

SQL get latest record for each ID

I have three tables that contains data as below:
Users
Id Name Other_Columns
---------------------------
1 John Blah
2 Ricky Blah
3 Stella Blah
4 Bob Blah
Saldo
Id User_id Saldo
--------------------
1 3 0.00
2 1 9.00
3 2 0.15
4 4 3.50
Payments
Id User_id Amount Paid_date
------------------------------------------
1 2 10.00 2014-09-01 08:10
2 2 25.00 2014-09-01 09:00
3 3 100.00 2014-05-10 12:47
4 1 20.50 2014-02-23 15:30
How to get result like this:
Id Name Saldo Last Payment
------------------------------------------
1 John 9.00 23.02.2014 20.50
2 Ricky 0.15 01.09.2014 25.00
3 Stella 0.00 0000-00-00 0.00
4 Bob 3.50 10.05.2014 100.00
Thank you.
select u.id, u.name, s.saldo, p.last_paid_date, p2.amount
from users u
join saldo s
on u.id = s.user_id
join (select user_id, max(paid_date) as last_paid_date
from payments
group by user_id) p
on u.id = p.user_id
join payments p2
on p.last_paid_date = p2.paid_date
and p.user_id = p2.user_id
This answer assumes:
(1) On table SALDO, there is one row per USER_ID
(2) On table PAYMENTS, there can be multiple rows per USER_ID
(I'm pretty confident about #2 being true, I don't know about #1, as you didn't say and your sample data doesn't indicate one way or the other)

Select first and last occurrence of repeated colums in sql server

I have a table like this,
Date is in yyyy-mm-dd format
Name Date Credits
--------------------------------
Bill 2013-04-04 5
Paul 2013-04-05 4
Bill 2013-04-05 3
Angel 2013-04-07 9
Bill 2013-05-01 8
Paul 2013-05-02 7
Bill 2013-06-15 6
Angel 2013-07-22 15
Paul 2013-07-23 7
Angel 2013-08-11 9
And my expected result is
Name MinDate MaxDate Credits
-----------------------------------------------
Bill 2013-04-04 2013-06-15 1
Paul 2013-04-05 2013-07-23 3
Angel 2013-04-07 2013-08-11 0
How to form the Query. Help needed.
My approach would be something like this:
SELECT t1.name, MIN(t1.date) AS MinDate, MAX(t1.date) AS MaxDate
FROM table t1
GROUP BY t1.name
I don't know how you calculate your credits, though, so I left this one out.
If it's SUM(t1.credit) or something alike, just add this to the FROM-clause.
Hope this helps.

combining the result of a query with another in sql

The following sql query below produced this result
cust table
CUST_ID AC_NO NAME AREA SALES
---------- ---------- ------------------------- ---
C001 A30045 Smiths Heavy 1 R001
C002 A30145 Heavy jonps 1 R001
C003 A30046 dangote flour 1 R002
C004 A30047 OAU ife 2 R002
Area Table
AREA_NUMBER AREA_NM AREA_Dp
----------- ---------- ----------
1 North Leeds
2 South Newcastle
3 East Surrey
4 West London
Area_geo Table
SALE REPP_ AREAA_ID AREAM AREAMANAGER_NAME
---- ----- ---------- ----- -------------------
SG01 R001 1 R110 mandy Jay
SG02 R002 2 R110 mandy Jay
SG03 R003 3 R111 Kay sole
SG04 R003 3 R111 Kay sole
SG05 R003 3 R111 Kay sole
SG06 R001 4 R110 mandy Jay
select cust.*,
area.AREA_Nm,area.AREA_Dp
from area inner join cust on
area.area_number=customer.area
ORDER BY customer.Cust_ID;
Result
CUST_ID AC_NO NAME ADDRESS AREA SALES AREA_N AREA_Dp
---------- ---------- -------------- ------------------------- ---------- ----- ---
Ac003 A30046 dangote flour court Estate 1 R002 North Leeds
Ac004 A30047 OAU ife 4 Abanishe 2 R002 South Newcastle
my intension is to further include the Areamanager_name into the above result from the table below. the table below however has a common attribute (Area_ID) to the above result (Area)
Table Area_Geo
SALE REPP_ AREA_ID AREAM AREAMANAGER_NAME
---- ----- ---------- ----- ----------------
SG01 R001 1 R110 mandy Jay
SG02 R002 2 R110 mandy Jay
SG03 R003 3 R111 Kay sole
SG04 R003 3 R111 Kay sole
SG05 R003 3 R111 Kay sole
SG06 R001 4 R110 mandy Jay
expected result
CUST_ID AC_NO NAME ADDRESS AREA SALES AREA_N AREA_dp Areamanager
---------- ---------- -------------- ------------------------- ---------- ----- ---
Ac003 A30046 dangote flour court Estate 1 R002 North Leeds mandy Jay
Ac004 A30047 OAU ife 4 Abanishe 2 R002 South Newcastle mandy Jay
I think the below should do it, I reformatted it a bit so I could read it a bit easier. You
would need an outer join if sometimes you didn't have an area manager.
SELECT cust.*
,area.area_Nm
,area.AREA_Dp
,area_geo.Areamanager_name
FROM area inner join cust on area.area_number = customer.area
inner join area_geo on area.area_number = area_geo.area_id
ORDER BY customer.Cust_ID;
edit in response to comment :-
The problem of double values is caused by the fact that the area_geo table can have ( and in the case of area_id 3 appears to have ) duplicate values for an area_id. When you do a join a row is returned for each row in the table, so for area_id 3 rows would be returned every time. You could do use a sub query
SELECT cust.*
,area.area_Nm
,area.AREA_Dp
,(SELECT DISTINCT area_geo.Areamanager_name
FROM area_geo
where area.area_number = area_geo.area_id) Areamanager_name
FROM area inner join cust on area.area_number = customer.area
ORDER BY customer.Cust_ID;
But if there are different values of Areamanager_name for the same area_id, this query will fail. Ideally I think you need to use a more suitable table to retrieve the areamanager_name, or if there isn't one normalize your database so that there is only 1 record in area_geo for each area_id.
You need to use a LEFT OUTER JOIN. http://en.wikipedia.org/wiki/Join_%28SQL%29
select cust.*, area.AREA_Nm,area.AREA_Dp
from area inner join cust on area.area_number=customer.area
inner join Area_Geo on cust.area = Area_Geo.area
ORDER BY customer.Cust_ID;