PostgreSQL: Outer and self join - sql

I want to do an outer join on the same table, but I'm getting unexpected results.
This is what my "stock" table looks like:
price_timestamp, security, price_type, open, high, low, close
"2014-05-01 00:00:00-07";"SPY US EQUITY";"ASK"; 188.54;188. 57;188.54;188.57
"2014-05-01 07:59:00-07";"SPY US EQUITY";"ASK"; 188.72;188. 72;188.72;188.72
"2014-05-01 08:01:00-07";"SPY US EQUITY";"ASK"; 188.71;188. 72;188.71;188.72
"2014-05-01 13:30:00-07";"SPY US EQUITY";"TRADE"; 188.22;188. 27;188.21;188.26
"2014-05-01 13:31:00-07";"SPY US EQUITY";"TRADE"; 188.27;188. 35;188.26;188.35
...
price_type can be BID, ASK or TRADE. I want a query that returns price_timestamp, security, bid price (which would be close price where price_type = 'BID'), and trade price (which would be close price where price_type = 'TRADE).
This is what I have:
SELECT b.price_timestamp, b.security, b.close AS bid, t.close AS trade
FROM stock b
FULL OUTER JOIN stock t
ON b.price_timestamp = t.price_timestamp
AND b.security = t.security
WHERE b.price_type = 'BID'
AND t.price_type = 'TRADE'
This returns 19370 records. There are 40147 bid prices, 19399 trade prices, so I'm expecting at least max(40147, 19399) = 40147 records. At a glance, it looks it's returning an INNER JOIN.
I've also tried moving "b.security = t.security" in the ON cluse to the WHERE clause -- no luck.

You're getting fewer records because on the where clause you're referring both aliases 'b'& 't'. So, it is filtering only those joined records where b.price_type = 'BID' and t.price_type = 'TRADE'
Try moving the where conditions into the joins
For example:
SELECT b.price_timestamp, b.security, b.close AS bid, t.close AS trade
FROM stock b
FULL OUTER JOIN stock t
ON b.price_timestamp = t.price_timestamp
AND b.security = t.security
AND t.price_type = 'TRADE'
WHERE b.price_type = 'BID'
This would return all records from alias 'b' where b.price_type = 'BID', and at least one record from alias 't'. If no matching records are found from 't', a null records will be returned.

Related

How to add specific if condition in sql

I have built this query (snapshot attached) to check if there is any staleness in prices by comparing today's price with previous day price for certain timezones, what i need is it should only throw only that object_id which shows '0' as BP_move for the mentioned timezones. for eg in the snapshot we can see that AED fx price is 0 for across all the timezones so it is acceptable but other ccy like EUR, GHS ,KES ,NZD and QAR are stale only for one or two timezones so those should not get published in output. is there any if condition i can use in this query ?
select distinct
vyc.asof, vyc.timezone, vyc.object_id, i.name,
vpf.rate * 100 todays_rate, vpf2.rate * 100 prior_rate,
(vpf.rate - vpf2.rate) * 1000 bp_move
from
val_yield_curves vyc
inner join
val_prices_fx vpf on vyc.asof = vpf.asof
and vyc.rowkey = vpf.curve
inner join
val_yield_curves vyc2 on vyc.timezone = vyc2.timezone
and vyc2.asof = case when to_char(sysdate,'DY') = 'MON' then trunc(sysdate-3) else trunc(sysdate-1) end
and vyc.object_id = vyc2.object_id
inner join
val_prices_fx vpf2 on vyc2.asof = vpf2.asof
and vyc2.rowkey = vpf2.curve
and vpf.instrument = vpf2.instrument
inner join
instruments i on i.pkey = vpf.instrument
where
vyc.object_id like '%Spot%'
and vyc.timezone in ('L0200', 'L1000', 'L1200', 'L1500')
and (vpf.rate - vpf2.rate) * 1000 in '0'
and vyc.asof = trunc(sysdate)
order by
vyc.object_id
I don't know if I understood correctly your problem but...
If you don't need timezone information, you can
GROUP BY object_id, name, rates
HAVING COUNT(DISTINCT Timezone) >= 4
If you need timezone information, you can add a "OBJECT_ID IN (SELECT...)" condition in your WHERE. You have to make it check if you have the right number of lines for your OBJECT_ID. You can GROUP BY/HAVING COUNT this subrequest as above and returns OBJECT_ID.

finding highest total price and converting

So I have to use a query where I list the trade id stock id and the total price converted to us dollars where it is the highest price total.
SELECT
tr.trade_id, tr.stock_id, round(tr.price_total * con.exchange_rate,2)
as "US Dollars"
from trade tr
JOIN stock_exchange se
on se.STOCK_EX_ID = tr.STOCK_EX_ID
JOIN currency curr
on curr.CURRENCY_ID = se.currency_id
JOIN conversion con
on con.from_CURRENCY_ID = curr.CURRENCY_ID
WHERE (tr.PRICE_TOTAL) = (Select Max(price_total) from trade) and curr.name =
'Dollar' and tr.stock_ex_id is not NULL
group by tr.trade_id, tr.stock_id, round(tr.price_total), tr.price_total,
round(tr.price_total * con.exchange_rate,2);
Trade (trade_id PK, stock_id FK2, transaction_time, shares, stock_ex_id FK1,price_total)
Stock-exchange( stock_ex_id PK, name, symbol, currency_id FK1)
conversion( from_currency_id PK, to_currency_id)
currency ( currency_id PK, name, symbol
expected output should be -
trade_id - 1 stock_id 1, price (non conversion) (225000000)
I'm not sure why in my output i get nothing. Any suggestions to fix this? Sorry if i did not format the question right
But you could start by seeing what your select max query returns, and then filtering down the trade table by that value. Trace this though all of your tables to see where the data on your trade table isn't on the others.
OR put you curr.name criteria up with the join and specify a LEFT JOIN for the last two tables to see if the data is missing like below:
SELECT tr.trade_id
,tr.stock_id
,round(tr.price_total * con.exchange_rate, 2) AS "US Dollars"
FROM trade tr
LEFT JOIN stock_exchange se
ON se.STOCK_EX_ID = tr.STOCK_EX_ID
LEFT JOIN currency curr
ON curr.CURRENCY_ID = se.currency_id
AND curr.name = 'Dollar'
LEFT JOIN conversion con
ON con.from_CURRENCY_ID = curr.CURRENCY_ID
WHERE (tr.PRICE_TOTAL) = (SELECT Max(trm.price_total) FROM trade trm)
AND tr.stock_ex_id IS NOT NULL
It could be other things as well, but one spot to look is this
You say WHERE tr.PRICE_TOTAL = (Select Max(price_total) from trade)
AND curr.name = 'Dollar'
If the trade with the Maximum price_total is not in Dollars, then you'll get nothing.
You need to either change your "Select Max(price_total) from trade" to get maximum values for Dollar trades only, or else get all the valid trades in a subquery and then get the maximum value from that.
After comment below - to debug - run "Select * from trade where price_total = (Select Max(price_total) from trade)" to get the valid trade record(s). Then look at them - they are going to fail somewhere - no STOCK_EX_ID on trade, or one of the other joins fails - you need to start from known data and go from there.

Query Results across 3 tables

I have a need to see a client was billed for a specific item. The tables are as follows Client, Orders, Details. The way they connect is Client to Orders and Orders to Details. (client.acctno = order.acctno, order.orderno = details.orderno)
How can I search all orders and display only those that have not been sold a specific item (sample code with exact table names below). My data keep showing all clients because they weren't sold the items a specific order I want to bunch them all together as ALL ORDERS and compare.
SELECT *
FROM plshared.dbo.client
LEFT OUTER JOIN PL00.dbo.ordhdr ON ordhdr.ACCT_NO = client.ACCT_NO
WHERE NOT EXISTS (SELECT *
FROM pl00.dbo.orddet
WHERE orddet.ORDER_NO = ordhdr.order_no
AND orddet.ITEM_NO = '2017WI')
--GROUP BY client.ACCT_NO
ORDER BY CLIENT.ACCT_NO
The problem is your query is at the order level. You might find it easier to get the clients by doing:
SELECT o.ACCT_NO
FROM PL00.dbo.ordhdr o JOIN
pl00.dbo.orddet od
ON od.ORDER_NO = o.order_no
GROUP BY o.ACCT_NO
HAVING SUM(CASE WHEN od.ITEM_NO = '2017WI' THEN 1 ELSE 0 END) = 0;
ORDER BY o.ACCT_NO;

Show 0 for null rows

I'm working on a comparison query that will compare what was processed for the different credit card types versus what was actually processed by our POS service. I have ran into an issue where I am not seeing rows for certain card types if they did not process any payments on a given day but may have given a refund. IE: Nothing purchased with VISA but a VISA refund was given, but due to no purchases the row does not show up even though there's a refund for that type.
Here is my query:
WITH tran_total AS (
SELECT CONCAT(c.id_str, ' - ', c.clinic_str) AS Clinics,
t.clinic,
c.xcharge_mid,
p.pay_desc,
CAST(t.time_ran AS date) AS tran_date,
CASE WHEN SUM(t.amount) <> 0 THEN SUM(t.amount) * - 1
ELSE 0.00 END AS collection
FROM dbo.transactions AS t INNER JOIN
dbo.clinic_master AS c ON t.clinic = c.clinic INNER JOIN
dbo.paytype AS p ON t.clinic = p.clinic AND t.paytype_id = p.paytype_id
WHERE (t.time_ran > GETDATE() - 10)
AND (t.paytype_id IS NOT NULL)
AND (p.pay_desc = 'Visa' OR
p.pay_desc = 'MasterCard' OR
p.pay_desc = 'American Express' OR
p.pay_desc = 'Discover')
GROUP BY c.id_str, c.clinic_str, t.clinic, c.xcharge_mid, p.pay_desc, CAST(t.time_ran AS date))
SELECT w.Clinics,
w.pay_desc,
w.tran_date,
w.collection,
CASE WHEN w.pay_desc = 'Visa' THEN (SUM(VI_pur) - SUM(VI_ref))
WHEN w.pay_desc = 'MasterCard' THEN (SUM(MC_pur) - SUM(MC_ref))
WHEN w.pay_desc = 'American Express' THEN (SUM(AX_pur) - SUM(AX_ref))
WHEN w.pay_desc = 'Discover' THEN (SUM(DI_pur) - SUM(DI_ref)) END AS xcharge
FROM tran_total AS w LEFT OUTER JOIN
dbo.xcharge AS x ON w.xcharge_mid = x.xcharge_mid AND w.tran_date = x.settle_date
GROUP BY w.Clinics, w.pay_desc, w.tran_date, w.collection
ORDER BY w.Clinics, w.tran_date
I've thought about switching this around and starting with the comparison from the xcharge table but there is not a pay_desc that links those easily and if something is processed as the wrong card (VISA is chosen but Discover is scanned) then I feel the rows would be missed still.
What I would like, is for all of the pay_desc to show up for each tran_date even if the SUM(t.amount) does not have a value. I've tried a case statement to accomplish this without any luck.
EDIT: I feel the issue is more due to the t.time_ran variable. If there isn't a payment for a given pay_desc then there won't be a t.time_ran either, is there a function I can do to list out dates between GETDATE()-10 AND GETDATE() kind of thing and just have the t.time_ran and x.settle_date match up to that variable instead?
Any thoughts on this? Thanks in advance
UPDATE:
I have created a calendar table with individual dates and have tried the following query:
WITH tran_test AS(
SELECT cal.calendardate AS cd,
p.clinic,
p.pay_desc,
p.paytype_id
FROM calendar cal, paytype p
WHERE cal.calendardate BETWEEN GETDATE()-10 AND GETDATE()+1
AND (p.pay_desc = 'Visa' OR
p.pay_desc = 'MasterCard' OR
p.pay_desc = 'American Express' OR
p.pay_desc = 'Discover'))
SELECT w.cd,
CONCAT(c.id_str, ' - ', c.clinic_str) AS Clinics,
w.pay_desc,
ISNULL(SUM(t.amount)*-1, 0) AS collection,
CASE WHEN w.pay_desc = 'Visa' THEN (SUM(x.VI_pur) - SUM(x.VI_ref))
WHEN w.pay_desc = 'MasterCard' THEN (SUM(x.MC_pur) - SUM(x.MC_ref))
WHEN w.pay_desc = 'American Express' THEN (SUM(x.AX_pur) - SUM(x.AX_ref))
WHEN w.pay_desc = 'Discover' THEN (SUM(x.DI_pur) - SUM(x.DI_ref)) END AS xcharge
FROM tran_test w
INNER JOIN clinic_master c
ON (w.clinic=c.clinic)
LEFT OUTER JOIN transactions t
ON (t.clinic=w.clinic AND t.paytype_id=w.paytype_id AND CAST(t.time_ran AS date) = w.cd)
LEFT OUTER JOIN xcharge x
ON (w.cd=x.settle_date AND c.xcharge_mid=x.xcharge_mid)
GROUP BY w.cd, c.id_str, c.clinic_str, w.pay_desc
ORDER BY w.cd
However, I'm not getting an issue where my xcharge column seems to multiplying itself by random intervals and I'm not sure why.
It sounds like what you need is a Calendar table. You can generate one as part of a CTE (many examples on this site or just search Google), but IMO you should have a persistent table in your database. This way you can mark certain days as bank holidays, what quarter your business considers them to be in, etc.
Once you have that table in place you can SELECT your dates from that table and then LEFT OUTER JOIN to that table from your Transactions table. Any dates without matching rows in Transactions will still show up with a row, but you'll have 0.00 value for your SUM.

Using query results in further procedcures

I'm fairly new to SQL especially PL/SQL, I have a grasp of the basics but what i'm trying to do goes over my head. This is a small part of a larger system but I will focus on 3 tables Sale_Head, Sale_Line and Product. This work is being done in Orcale SQL developer.
http://imgur.com/OrfhM0g,i4pMnTx - Sale Table
http://imgur.com/OrfhM0g,i4pMnTx#1 - Product Table
(Im a newbie so i dont have enough rep to directly post images)
Im attempting to build up some procedures that process pending sales. Sales have a status in the Sale_Head which can either be p(pending), s(shipped), i(in progress), x(cancelled), or b(back orders). The procedure(s) need to find sales with a pending status (i've got that far in my code), then check the products stock levels are above the minimum level enough to complete the sale. If all these conditions are met pending sales become i (in progress) and if there is inadequate stock it comes backordered (b). I have a general idea of how to go about this, but when it comes having to referencing the product table using the foreign from sale_line I get confused.
--create or replace procedure Process_Products is
--begin
select Sale_Head.Sale_Num , Sale_Line.Product_Code as
from Sale_Head
inner join Sale_Line on
Sale_Head.Sale_Num = Sale_Line.SALE_NUM
where Sale_head.Status = 'P';
--if Sale_Line.Product_Code = Product.Product_Code >
--end;
This returns the product codes for orders that have a pending status, but now I need to check their stock levels in the product table and make adjustments.
Please correct me if I am wrong, but if you are saying "for those sales in a pending status where enough inventory exists to fulfill the order, change the status to i (in progress), otherwise not enough inventory exists, so change the status to b (backordered)", then the following should help:
-- This is a useful query to display what you need - debugging, use in cursor, etc
SELECT sh.Sale_Num, sl.Product_Code, sl.Quantity as QtyToBuy,
p.Stock_Count, p.Minimum_Level,
(Case When sl.Quantity <= p.Minimum_Level Then 'i'
Else 'b' end) as NewStatus
FROM Sale_Head sh
INNER JOIN Sale_Line sl
ON sh.Sale_Num = sl.SALE_NUM
INNER JOIN Product p
ON sl.Product_Code
WHERE Sale_head.Status = 'P';
The status code can be updated in one query if that is what you want:
--This query will actully do the update (if view is updatable)
UPDATE
(
SELECT sh.Status, (Case When sl.Quantity <= p.Minimum_Level Then 'i'
Else 'b' end) as NewStatus
FROM Sale_Head sh
INNER JOIN Sale_Line sl
ON sh.Sale_Num = sl.SALE_NUM
INNER JOIN Product p
ON sl.Product_Code
WHERE sh.Status = 'P'
) a
SET a.Status = a.NewStatus;
If the above view is not updatable, try this syntax:
--Otherwise try this syntax
UPDATE Sale_Head sh1 Set Sale_Head.Status =
SELECT (Case When sl.Quantity <= p.Minimum_Level Then 'i'
Else 'b' end) as NewStatus
FROM Sale_Head sh
INNER JOIN Sale_Line sl
ON sh.Sale_Num = sl.SALE_NUM
INNER JOIN Product p
ON sl.Product_Code
WHERE sh1.Sale_Num = sh.Sale_Num
)
WHERE Sale_Head.Status = 'P';