SQL Full Join with Where - sql

I am trying to join two tables in SQL, one containing a list of items, and the other with the dates they were sold. I got the join part down, but I need to get the information from the REPORT table, but all items in the VENDORS table. I need the items that were not sold to show up as NULL, or preferably 0. This is the code I'm using so far, and it's only showing the items that were sold on the given day.
SELECT t2.[DATE]
,t1.[VENDOR]
,t1.[UPC]
,t2.[QTY]
,t2.[AMOUNT]
FROM [STORESQL].[dbo].[VENDORS] t1
LEFT OUTER JOIN [STORESQL].[dbo].[REPORT] t2 ON t1.UPC=t2.UPC
WHERE VENDOR='119828' AND DATE='2011-11-8'
and example of the tables are
VENDORS:
VENDOR UPC
119828 1
119828 2
119828 3
REPORT:
DATE UPC QTY AMOUNT
2011-11-8 1 1 9.99
2011-11-8 3 2 18.98
The current code is resulting in
DATE VENDOR UPC QTY AMOUNT
2011-11-8 119828 1 1 9.99
2011-11-8 119828 3 2 18.98
I need it to show
DATE VENDOR UPC QTY AMOUNT
2011-11-8 119828 1 1 9.99
2011-11-8 119828 2 0 0.00
2011-11-8 119828 3 2 18.98
I know i'm doing something wrong, but I don't know what that is.
Thanks in advance.

Try this instead:
SELECT t2.[DATE]
,t1.[VENDOR]
,t1.[UPC]
,t2.[QTY]
,t2.[AMOUNT]
FROM [STORESQL].[dbo].[VENDORS] t1
LEFT OUTER JOIN [STORESQL].[dbo].[REPORT] t2 ON t1.UPC=t2.UPC AND DATE='2011-11-8'
WHERE VENDOR='119828'
The where clause against DATE makes your outer join to function as an inner join because the comparison will be against a null value in DATE. Moving the comparison to the ON clause should deal with that.
To get a 0 instead of null as a result you can use COALESCE:
,COALESCE(t2.[QTY], 0)
,COALESCE(t2.[AMOUNT], 0)
Your date column will also be null and that can be fixed with COALESCE as well:
COALESCE(t2.[DATE], '2011-11-8')

This is incompatible:
WHERE t2.DATE='2011-11-8'
I need the items that were not sold to show up as NULL.
The first WHERE clause will cancel all effects of an OUTER JOIN, because you remove all those records where there was no match for your row in T1. Maybe you want:
WHERE VENDOR='119828' AND (DATE='2011-11-8' OR DATE IS NULL)

WITH VENDORS
AS
(
SELECT *
FROM (
VALUES (119828, 1),
(119828, 2),
(119828, 3)
) AS T (VENDOR, UPC)
),
REPORT
AS
(
SELECT *
FROM (
VALUES ('2011-11-08T00:00:00', 1, 1, 9.99),
('2011-11-08T00:00:00', 3, 2, 18.98)
) AS T ("DATE", UPC, QTY, AMOUNT)
),
AllReportDates
AS
(
SELECT DISTINCT "DATE"
FROM REPORT
)
SELECT r."DATE", v.VENDOR, v.UPC, r.QTY, r.AMOUNT
FROM VENDORS v
JOIN REPORT r
ON v.UPC = r.UPC
UNION
SELECT a."DATE", v.VENDOR, v.UPC, 0 AS QTY, 0.00 AS AMOUNT
FROM VENDORS v
CROSS JOIN AllReportDates a
WHERE NOT EXISTS (
SELECT *
FROM REPORT r
WHERE r.UPC = v.UPC
AND r."DATE" = a."DATE"
);

Related

ORACLE SQL - multiple JOINs from same table

I have data related material transactions in one table and log history header data related to materials is in another table and detailed log history data in third table. I'm trying to get different status update dates matched to material table but I get duplicate rows for one material transaction
Original material transaction table:
ORDER_NO
MATERIAL
QTY
0001
MAT01
2
0002
MAT02  
5
Original Log History Header transaction table:
ORDER_NO
LOG_ID
0001
1001
0001
1002
Status code 1 refers to Opened and code 2 to Closed
Detailed Log History table:
LOG_ID
STATUS_CODE
DATE
1001
1
11/12/2021
1002
2  
15/12/2021
With following SQL query:
SELECT
TO_CHAR (m.order_no) order_no,
m.material,
a.date opened_date,
ab.closed_date
FROM MATERIAL_TRANSACTIONS m
INNER JOIN HISTORY_LOG t
ON m.ORDER_NO = t.ORDER_NO
INNER JOIN HISTORY_LOG_DETAILED a
ON t.LOG_ID = a.LOG_ID
AND a.STATUS_CODE = '1'
INNER JOIN HISTORY_LOG_DETAILED ab
ON t.LOG_ID = ab.LOG_ID
AND ab.STATUS_CODE = '2'
I get following result:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
0001
MAT01  
2
15/12/2021
And I would like to get the status dates to the same row as below:
ORDER_NO
MATERIAL
QTY
OPENED_DATE
CLOSED_DATE
0001
MAT01
2
11/12/2021
15/12/2021
I would appreciate all the help I can get and am very sorry if there already is topic for similar issue.
Your problem occurs because you join the history table, which holds 2 records for the order. You could flatten this if you use 2 inline tables that hold exactly 1 record.
with opened_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '1'
), closed_dates as (
select h.order_id, d.date
from history h
inner join details d on h.log_id = d.log_id and d.status_code = '2'
)
select to_char (m.order_no) order_no,
m.material,
o.date opened_date,
c.date closed_date
from material_transactions m
join opened_dates o on m.order_no = o.order_no
join closed_dates c on m.order_no = c.order_no
;
Just an idea :
I joined HISTORY_LOG and HISTORY_LOG_DETAILED tables to get dates for specific status, and set as OPENED_DATE and CLOSED_DATE (if status 1 , then opened date is DATE column, otherwise set it as 01.01.0001)
After that grouped those records by ORDER_NO and maxed the date values to get actual OPENED_DATE and CLOSED_DATE .
Finally joined this subquery with MATERIAL_TRANSACTIONS table :
SELECT
TO_CHAR (M.ORDER_NO) ORDER_NO,
M.MATERIAL,
QTY,
L_T.OPENED_DATE,
L_T.CLOSED_DATE
FROM MATERIAL_TRANSACTIONS M
INNER JOIN
(
SELECT L.ORDER_NO ,
MAX( CASE WHEN LD.STATUS_CODE = 1 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) OPENED_DATE
MAX( CASE WHEN LD.STATUS_CODE = 2 THEN LD.DATE ELSE TO_DATE('01.01.0001','dd.mm.yyyy') END ) CLOSED_DATE
FROM
HISTORY_LOG L
INNER JOIN HISTORY_LOG_DETAILED LD ON LD.LOG_ID = L.LOG_ID
GROUP BY L.ORDER_NO
) L_T on L_T.ORDER_NO = M.ORDER_NO
Note: I didnt test it. So there can be small syntax errors. Please check it and for better help add a fiddle so i can test my query

Query to get data from 3 tables, main tables and sum column from each other 2 tables

I'm struggling in query to get data from 3 tables :
Suppliers :
supp_id int
name nvarchar(200)
phone nvarchar(10)
location nvarchar(50)
note nvarchar(MAX)
date date
suppliers_invoices:
ID int
supp_id int
o_number nvarchar(50)
price decimal(18, 2)
note nvarchar(MAX)
date date
image image
suppliers_payments:
ID int
supp_id int
value decimal(18, 2)
method nvarchar(30)
note nvarchar(MAX)
date date
What I'm trying to get is this information:
suppliers.supp_id,suppliers.name,suppliers.phone,(sum(suppliers_invoices.price),
(sum(suppliers_payments.value), ((sum(suppliers_invoices.price) - (sum(suppliers_payments.value))
group by suppliers.supp_id.
I want to get each supplier invoices price sum and value paid sum.
what I'm getting now is wrong data because I think the price is multiplied by the other table row counts.
this is what I tried :
SELECT dbo.suppliers.supp_id, dbo.suppliers.name, dbo.suppliers.phone,
SUM(dbo.suppliers_invoices.price) AS Expr1, SUM(dbo.suppliers_payments.value) AS Expr2
FROM dbo.suppliers INNER JOIN dbo.suppliers_invoices ON dbo.suppliers.supp_id =
dbo.suppliers_invoices.supp_id INNER JOIN dbo.suppliers_payments ON dbo.suppliers.supp_id =
dbo.suppliers_payments.supp_id GROUP BY dbo.suppliers.supp_id, dbo.suppliers.name,dbo.suppliers.phone
Here some data I'm testing on :
suppliers:
supp_id name phone location note date
1 test 0543642256 NULL NULL 2020-11-17
2 test2 0543642211 NULL NULL 2020-11-17
suppliers_invoices:
ID supp_id o_number price note date image
1 1 123 5000.00 NULL 2020-11-17 NULL
2 1 1235 3000.00 NULL 2020-11-17 NULL
3 2 55 2000.00 NULL 2020-11-17 NULL
suppliers_payments:
ID supp_id value method note date
1 1 2000.00 cash NULL 2020-11-17
2 1 2000.00 visa NULL 2020-11-17
what I want to get is :
supp_id name phone price value remain
1 test 0543642256 8000 4000 4000
2 test2 0543642211 2000 0 2000
Thank you.
You need to sum the price and value separately, then interact with them. Don't kill yourself trying to do complicated summations and joins beforehand; you will get incorrect calculations if you joined them wrong.
SELECT
s.name,
s.phone,
inv.sumPrice,
val.sumValue,
isnull(inv.sumPrice, 0) - isnull(val.sumValue, 0) as sumProfit
FROM suppliers s
LEFT JOIN ( /* see this subquery, pretending to be a table */
SELECT
supp_id,
sum(price) as sumPrice
FROM suppliers_invoices
GROUP BY supp_id) inv on s.supp_id = inv.supp_id
LEFT JOIN ( /* see this subquery, pretending to be a table */
SELECT
supp_id,
sum([value]) as sumValue
FROM suppliers_payments
GROUP BY supp_id) val on s.supp_id = val.supp_id ;
Demo here
You need to pre-aggregate in subqueries. One option uses lateral joins:
select s.*, si.price, sp.value, si.price - sp.value as remain
from suppliers s
cross apply (
select coalesce(sum(si.price), 0) as price
from suppliers_invoices si
where si.supp_id = s.supp_id
) si
cross apply (
select coalesce(sum(sp.value), 0) as value
from suppliers_payments sp
where sp.supp_id = s.supp_id
) sp
I think you are missing out on a left join instead of a inner join.
Please find the below query:
SELECT sup.name, sum(sup_inv.price), sum(sup_pay.value), sup.supp_id
FROM suppliers sup
left JOIN suppliers_invoices sup_inv
ON sup.supp_id = sup_inv.supp_id
left JOIN suppliers_payments sup_pay
ON sup.supp_id = sup_pay.supp_id
group by sup.supp_id, sup.name;

SQL: looking for rounded duplicates

I have a table that possibly seems to have transactions that are rounded to the nearest dollar before being reversed off
Given something like this:
AccountID Transaction
1 20.05
1 -20.00
1 17.00
2 32.35
3 40.78
3 -41.00
4 15.00
4 -15.00
5 10.03
5 10.00
I want to see how many accounts have this pattern of a value, and a rounded negative counter-part. So, I want to pull AccountIDs 1 and 3 from the table above, for example.
I am not interested in AccountID 4, as that is an exact (absolute value) match, or AccountID 5, which has a "rounded duplicate" but is not a negative counter-part.
Any one know how I might accomplish this in SQL?
Just compare with rounded values.
SELECT
T.*
FROM
Transactions AS T
WHERE
EXISTS (
SELECT
'inverted rounded operation detected'
FROM
Transactions AS N
WHERE
T.AccountID = N.AccountID AND
ROUND(T.[Transaction], 0) = -1 * ROUND(N.[Transaction], 0))
You may have multiple transactions that match. Hence, you should use row_number() for the matching:
with t as (
select t.*,
row_number() over (partition by accountid, floor(transaction) order by transaction) as seqnum
from transactions t
)
select t.*
from t
where exists (select 1
from t t2
where t2.accountid = t.accountid and
floor(t2.transaction) = floor(t.transaction) and
t2.seqnum = t.seqnum
);
Round all the positive transaction values and find all the accounts that have an equal negative value.
select acct.accountid
from
acct
inner join (
select
accountid,
round(transaction, 0) as whole_trans
from
acct
where
transaction > 0
and transaction != round(transaction, 0)
) as unround on unround.accountid = acct.accountid
where
unround.whole_trans = -1 * acct.transaction;

How to query SQLite to find sum, latest record, grouped by id, in between dates?

Items (itemId, itemName)
Logs (logId, itemId, qtyAdded, qtyRemoved, availableStock, transactionDate)
Sample Data for Items:
itemId itemName
1 item 1
2 item 2
Sample Data for Logs:
logid itemId qtyAdded qtyRemoved avlStock transDateTime
1 2 5405 0 5405 June 1 (4PM)
2 2 1000 0 6405 June 2 (5PM)
3 2 0 6000 405 June 3 (11PM)
I need to see all items from Items table and their SUM(qtyAdded), SUM(qtyRemoved), latest availableStock (there's an option for choosing the range of transactionDate but default gets all records). Order of date in final result does not matter.
Preferred result: (without date range)
itemName qtyAddedSum qtyRemovedSum avlStock
item 1 6405 6000 405
item 2 <nothing here yet>
With date Range between June 2 (8AM) and June 3 (11:01PM)
itemName qtyAddedSum qtyRemovedSum avlStock
item 1 1000 6000 405
item 2 <no transaction yet>
So as you can see, final result is grouped which makes almost all my previous query correct except my availableStock is always wrong. If I focus in the availableStock, I can't get the two sums.
you could use group by sum, and between
select itemName, sum(qtyAdded), sum(qtyRemoved), sum(avlStock)
from Items
left join Logs on logs.itemId = items.itemId
where transDateTime between '2017-06-02 08:00:00' and '2017-06-03 23:00:00'
group by itemId
or
If you need the last avlStock
select itemName, sum(qtyAdded), sum(qtyRemoved), tt.avlStock
from Items
left join Logs on logs.itemId = items.itemId
INNER JOIN (
select logid,avlStock
from logs
inner join (
select itemId, max(transDateTime) max_trans
from Logs
group by itemId
) t1 on logs.itemId = t1.ItemId and logs.transDateTime = t1.max_trans
) tt on tt.logId = Logs.itemId
where transDateTime between '2017-06-02 08:00:00' and '2017-06-03 23:00:00'
group by itemId
Okay, I tried both of these and they worked, can anyone confirm if these are already efficient or if there are some more efficient answers there.
SELECT * FROM Items LEFT JOIN
(
SELECT * FROM Logs LEFT JOIN
(
SELECT SUM(qtyAdd) AS QtyAdded, SUM(qtySub) AS QtyRemoved, availableStock AS Stock
FROM Logs WHERE transactionDate BETWEEN julianday('2017-07-18 21:10:40')
AND julianday('2017-07-18 21:12:00') GROUP BY itemId
)
ORDER BY transactionDate DESC
)
USING (itemId) GROUP BY itemName;
SELECT * FROM Items LEFT JOIN
(
SELECT * FROM Logs LEFT JOIN
(
SELECT SUM(qtyAdd) AS QtyAdded, SUM(qtySub) AS QtyRemoved, availableStock AS Stock
FROM Logs GROUP BY itemId
)
ORDER BY transactionDate DESC
)
USING (itemId) GROUP BY itemName;

To find if a item's price has changed

This is probably easy to do but I have a table which list items by price and has a version number for each item, so there is duplicate items with the same itemID and item name but the different versions might have different prices. I wanted a simple sql statement to check which items prices have changed
itemid | item | price |version | date
1 a 1.13 1 2011-12-01
2 b 5.13 1 2011-12-01
3 c 3.66 1 2011-12-01
4 a 1.03 2 2012-01-09
5 b 5.13 2 2012-01-09
6 c 3.33 2 2012-01-09
There will be numerous versions so guess I need to some kind of comparison function but not sure where to start in SQL.
So the above example I would only want to fetch back item a and c as there price has changed from the previous version.
Try:
SELECT item, COUNT(DISTINCT price) prices
FROM yourTable
/* WHERE clause would go here */
GROUP BY item
HAVING COUNT(DISTINCT price) > 1
Optionally include:
WHERE version in (1,2)
- if you only want to compare the specific versions 1 and 2.
This gives you a list of items where the prices has changed from the previous version:
WITH V AS (
SELECT MAX(version) CurrentFileVersion
FROM yourTable
), C AS (
SELECT item, price, version
FROM yourTable INNER JOIN V ON yourTable.version = V.CurrentFileVersion
), P AS (
SELECT item, price, version
FROM yourTable INNER JOIN V ON yourTable.version = (V.CurrentFileVersion - 1)
)
SELECT C.item
FROM C INNER JOIN
P ON C.item = P.item
WHERE C.price <> P.price;
This even shows all the intermediate steps. I honestly can't make it any more simple or explicit to follow than this.
Try this
SELECT * FROM Prices T
where exists (
SELECT 1 from Prices
where item = T.item and version = T.version - 1 and price != T.price
)
Ps. change "Prices" with your table name
I'm going to assume you want this information to update your table with data from this table.
I would pull the most current version of each record inot a temp table or CTE. THen I would run a MERGE statement to insert any new records or update any where the price is different from the existing price.