Correct SQL query statement requiring JOIN and UNION - sql

I was trying to solve this question (No. 29) on http://www.sql-ex.ru/
Under the assumption that the income (inc) and expenses (out) of the
money at each outlet are written not more than once a day, get a
result set with the fields: point, date, income, expense. Use Income_o
and Outcome_o tables.
And came up with this solution
SELECT Income_o.point, Income_o.date, Income_o.inc, Outcome_o.out
FROM Income_o
INNER JOIN Outcome_o ON Income_o.point = Outcome_o.point
The result is obviously wrong (and hence my question here). It assumes that a point will never have more than 1 income and expense, so isn't this query correct? I can see from the same page that the correct query has some NULL column values. I would appreciate an explanation (if not the correct answer). My SQL is not a master one (and that's why I am trying to go through those!! So far done 29 out of 125 and only took help from SO on 3 of them)
The expected result is (From the website):
The result of correct query:
A snapshot of the expected result is here - http://snag.gy/yN43V.jpg
P.S. I know that the hint says UNION and JOIN and trying to get my head around this. If I can get the answer myself, I will post it.

You want a full outer join on point and date:
SELECT
COALESCE(i.point, o.point) AS point,
COALESCE(i.date, o.date) AS date,
i.inc,
o.out
FROM
Income_o AS i
FULL JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
;
The COALESCE expressions ensure that NULL is not returned for those columns: if the Income_o side has a NULL (because the table has no match for an Outcome_o row), the value is then taken from the other side.
Alternatively you can go with a union of two outer joins, left and right:
SELECT
i.point,
i.date,
i.inc,
o.out
FROM
Income_o AS i
LEFT JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
UNION
SELECT
o.point,
o.date,
i.inc,
o.out
FROM
Income_o AS i
RIGHT JOIN Outcome_o AS o ON i.point = o.point AND i.date = o.date
;
If the tables have matches on the specified condition, both joins will return them, but UNION will eliminate duplicate entries. This second method is essentially an alternative implementation of full outer join, useful for cases where the FULL JOIN syntax is not supported. (MySQL is one product that does not support FULL JOIN.)

You can use Group By with Aggregate Function to achieve the desired result, the sub query will combine the result set but will give results as per date, if there are two (in and out transaction) on same date, these will appear as two rows, to make it one row we can use Group By with aggregate function.
select point, date, max(inc), max(out)
from
(
select point, date, inc, NULL as out
from income_o
union all
select point, date, NULL, out
from outcome_o
)
dt
group by point, date

I think you are looking for LEFT JOIN
SELECT Income_o.point, Income_o.dat, Income_o.inc, Outcome_o.outc
FROM Income_o
LEFT JOIN Outcome_o ON Income_o.point = Outcome_o.point
try this SQL Fiddle example

Related

SQL: Left Join with three tables

I have three tables
Products (idProduct, name)
Invoices(typeinvoice, numberinvoice, date)
Item-invoices(typeinvoice, numberinvoice, idProduct)
My query has to select all the products not selled in the year 2019. I can use a function to obtain the year from the date, for example year(i.date). I know that the products that don't appear in the Item-invoice table are the not selled products. So I have tried with this two codes and obtain a good output.
SELECT p.name
FROM Products p
EXECPT
SELECT ii.idProduct
FROM Item-invoices ii, Invoices i
WHERE ii.typeinvoice=i.typeinvoice
AND ii.numberinvoice=i.numberinvocice
AND year(i.date)=2019
And the other code use a sub-query:
SELECT p.name
FROM Products p
WHERE p.idProduct NOT IN
(SELECT ii.idProduct
FROM Item-invoices ii, Invoices i
WHERE ii.typeinvoice=i.typeinvoice
AND ii.numberinvoice=i.numberinvocice
AND year(i.date)=2019)
The answer is how can i use the left join command to have the same output. I've tried with
SELECT p.name
FROM Products p
LEFT JOIN Item-invoices ii ON
p.IdProduct=ii.idProduct
LEFT JOIN Invoices i ON
ii.typeinvoice=i.typeinvoice
AND ii.numberinvoice=i.numberinvocice
WHERE year(i.date)=2019
AND ii.idProduct IS NULL
I know this is wrong but can't find the solution
Any help?
You are almost there. You just need to move the condition on the invoice date to from the from clause to the on clause of the join.
Conditions in the WHERE clause are mandatory, so what you did actually turned the LEFT JOI to an INNER JOIN, which can never be fulfilled (since both conditions in the WHERE clause cannot be true at the same time).
SELECT p.name
FROM Products p
LEFT JOIN Item-invoices ii ON
p.IdProduct=ii.idProduct
LEFT JOIN Invoices i ON
ii.typeinvoice=i.typeinvoice
AND ii.numberinvoice=i.numberinvocice
AND i.date >= '2019-01-01'
AND i.date < '2020-01-01'
WHERE ii.idProduct IS NULL
Note that I changed your date filter to a half-open filter that operates directly on the stored date, without using date functions; this is a more efficient way to proceed (since it allows the database to use an existing index).

Do I use inner join or left join?

I have two separate tables I'm pulling data from with the associate_id being the primary key. I'm trying to find all sales(sales_charge found in sales.dim) made by Associate_ID over several transactions within the last 4 months and the last year. I'm having a hard time with the time stamp and the joins.
Here's what I have so far:
SELECT associate_id
, sales.dim.sales_charge
FROM dbo.associate
LEFT JOIN dbo.sales ahd
ON associate_id = ahd.associate_id
AND ahd.end_dt > GETDATE()
I'm new to SQL and coding in general, please let me know what I'm missing.
Thanks
If you want to include all associates, even those with no sales, then use left join:
SELECT a.associate_id, ahd.dim.sales_charge
FROM dbo.associate a LEFT JOIN
dbo.sales ahd
ON a.associate_id = ahd.associate_id AND
ahd.end_dt > DATEADD(month, -4, GETDATE());
Answer to the question "Do I use inner join or left join?" is that you use inner join when you want to include only matching records from both tables, whereas left (outer) join will include all the records from left side table.
In the query you are attempting if you want to have all associates included in the result set even if they do not have any sales in last 4 months, use LEFT JOIN. If you want to have only those associates who have one or more sales then use INNER JOIN.
Another problem is with the condition "ahd.end_dt > GETDATE()". This means all end date after current time. Change it to "ahd.end_dt > DATEADD(month, -4, GETDATE())"

SQL Query to count the records

I am making up a SQL query which will get all the transaction types from one table, and from the other table it will count the frequency of that transaction type.
My query is this:
with CTE as
(
select a.trxType,a.created,b.transaction_key,b.description,a.mode
FROM transaction_data AS a with (nolock)
RIGHT JOIN transaction_types b with (nolock) ON b.transaction_key = a.trxType
)
SELECT COUNT (trxType) AS Frequency, description as trxType,mode
from CTE where created >='2017-04-11' and created <= '2018-04-13'
group by trxType ,description,mode
The transaction_types table contains all the types of transactions only and transaction_data contains the transactions which have occurred.
The problem I am facing is that even though it's the RIGHT join, it does not select all the records from the transaction_types table.
I need to select all the transactions from the transaction_types table and show the number of counts for each transaction, even if it's 0.
Please help.
LEFT JOIN is so much easier to follow.
I think you want:
select tt.transaction_key, tt.description, t.mode, count(t.trxType)
from transaction_types tt left join
transaction_data t
on tt.transaction_key = t.trxType and
t.created >= '2017-04-11' and t.created <= '2018-04-13'
group by tt.transaction_key, tt.description, t.mode;
Notes:
Use reasonable table aliases! a and b mean nothing. t and tt are abbreviations of the table name, so they are easier to follow.
t.mode will be NULL for non-matching rows.
The condition on dates needs to be in the ON clause. Otherwise, the outer join is turned into an inner join.
LEFT JOIN is easier to follow (at least for people whose native language reads left-to-right) because it means "keep all the rows in the table you have already read".

Missing rows when selecting from 2 tables

Hi and sorry if that's a stupud question but I am trying to get results from two different tables and since one of them might have zero records I am not sure how to proceed. Here is the query:
SELECT aid.*, T.ItemId, T.Total, T.Stack
FROM (SELECT ItemId, Stack, count(ItemId) as Total FROM auction_house WHERE Sale = 0 GROUP BY ItemId, Stack) as T, ahbot_item_data aid
WHERE T.ItemId = aid.ItemId
AND T.Stack = aid.Stack
Basically I want to get a list of items from the aid table, and the current count of those items in the ah table. But since the count might just be zero, it might not return that row. I want the row and the Total to be 0.
Thank you in advance ^^;;
Similar to what #Twelfth was saying, you want to have a left outer join, which always outputs data from the left table even if there is no matching data in the right table.
SELECT
aid.ItemId,
count(ah.ItemId) AS "Total",
aid.Stack
FROM
ahbot_item_data aid
LEFT OUTER JOIN auction_house ah
ON (aid.ItemID = ah.ItemID AND aid.Stack = ah.Stack)
GROUP BY
aid.ItemID, aid.Stack
You are using old syntax...I'm going to switch to join syntax (newer and more accepted...same effect, easier to read). What you want is an 'outer join', which will produce nulls where no record is found. I'll arrange the tables and we'll use a left outer join
SELECT aid.*, T.ItemId, T.Total, T.Stack
FROM ahbot_item_data aid left join (SELECT ItemId, Stack, count(ItemId) as Total FROM auction_house WHERE Sale = 0 GROUP BY ItemId, Stack) as T,
on T.ItemId = aid.ItemId
AND T.Stack = aid.Stack
I'm not entirely sure on the syntax anymore...but it's also possible to use != to do this outer join in the syntax you've used (where aid.itemID != t.itemID I think?)

Left and right joining in a query

A friend asked me for help on building a query that would show how many pieces of each model were sold on each day of the month, showing zeros when no pieces were sold for a particular model on a particular day, even if no items of any model are sold on that day. I came up with the query below, but it isn't working as expected. I'm only getting records for the models that have been sold, and I don't know why.
select days_of_months.`Date`,
m.NAME as "Model",
count(t.ID) as "Count"
from MODEL m
left join APPLIANCE_UNIT a on (m.ID = a.MODEL_FK and a.NUMBER_OF_UNITS > 0)
left join NEW_TICKET t on (a.NEW_TICKET_FK = t.ID and t.TYPE = 'SALES'
and t.SALES_ORDER_FK is not null)
right join (select date(concat(2009,'-',temp_months.id,'-',temp_days.id)) as "Date"
from temp_months
inner join temp_days on temp_days.id <= temp_months.last_day
where temp_months.id = 3 -- March
) days_of_months on date(t.CREATION_DATE_TIME) =
date(days_of_months.`Date`)
group by days_of_months.`Date`,
m.ID, m.NAME
I had created the temporary tables temp_months and temp_days in order to get all the days for any month. I am using MySQL 5.1, but I am trying to make the query ANSI-compliant.
You should CROSS JOIN your dates and models so that you have exactly one record for each day-model pair no matter what, and then LEFT JOIN other tables:
SELECT date, name, COUNT(t.id)
FROM (
SELECT ...
) AS days_of_months
CROSS JOIN
model m
LEFT JOIN
APPLIANCE_UNIT a
ON a.MODEL_FK = m.id
AND a.NUMBER_OF_UNITS > 0
LEFT JOIN
NEW_TICKET t
ON t.id = a.NEW_TICKET_FK
AND t.TYPE = 'SALES'
AND t.SALES_ORDER_FK IS NOT NULL
AND t.CREATION_DATE_TIME >= days_of_months.`Date`
AND t.CREATION_DATE_TIME < days_of_months.`Date` + INTERVAL 1 DAY
GROUP BY
date, name
The way you do it now you get NULL's in model_id for the days you have no sales, and they are grouped together.
Note the JOIN condition:
AND t.CREATION_DATE_TIME >= days_of_months.`Date`
AND t.CREATION_DATE_TIME < days_of_months.`Date` + INTERVAL 1 DAY
instead of
DATE(t.CREATION_DATE_TIME) = DATE(days_of_months.`Date`)
This will help make your query sargable (optimized by indexes)
You need to use outer joins, as they do not require each record in the two joined tables to have a matching record.
http://dev.mysql.com/doc/refman/5.1/en/join.html
You're looking for an OUTER join. A left outer join creates a result set with a record from the left side of the join even if the right side does not have a record to be joined with. A right outer join does the same on the opposite direction, creates a record for the right side table even if the left side does not have a corresponding record. Any column projected from the table that does not have a record will have a NULL value in the join result.