Cross Join Query - sql

Rewording my original post for further clarification.
I current have the below tables:
Product_Ref
product_id
product_name
Products
product_id
so_date (date)
total_sales
Calendar
dt (date field, each row representing a single day for the past/next 10 years)
I am looking to produce a report that will tell me the number of products that were sold in the past 6 months (based on SYSDATE) on a daily basis, the report should be every combination of day in the last 6 months against every possible product_id in the format:
Product id | date | total sales
If I assume that there were 0 entries in the products table (i.e no sales) I would still expect a complete report output but instead it would show 6 months of zero'd data i.e.
1 | 2012-01-01 | 0
2 | 2012-01-01 | 0
3 | 2012-01-01 | 0
1 | 2012-01-02 | 0
2 | 2012-01-02 | 0
3 | 2012-01-02 | 0
…
This would assume there were 3 products in the product_reference table - my original query (noted below) was my starter for 10, but not sure where to go from here.
SELECT products.product_id, calendar.dt, products.total_sales
FROM products RIGHT JOIN calendar ON (products.so_date = calendar.dt)
WHERE calendar.dt < SYSDATE AND calendar.dt >= ADD_MONTHS(SYSDATE, -7)+1
ORDER BY calendar.dt ASC, products.product_id DESC;

The clue is in the question - you are looking for a CROSS JOIN.
SELECT products.product_id, calendar.dt, products.total_sales
FROM Product_Ref
CROSS JOIN calendar
LEFT JOIN products ON products.so_date = calendar.dt
AND products.product_id = Product_Ref.product_id
WHERE calendar.dt < SYSDATE AND calendar.dt >= ADD_MONTHS(SYSDATE, -7)+1
ORDER BY calendar.dt ASC, products.product_id DESC;
I was confused at first by your table names where "Product" in fact means "sale" and "Product_Ref" is a product!
This is very similar to an example of the use of CROSS JOIN I once posted here.

As far as I understood, what do you want is to have no result if there were no sales, write?
So, I think you just need to change the RIGHT JOIN to INNER JOIN.
By RIGHT joining, if there were register in the JOIN table and there weren't in the FROM table it will return the data from the JOIN table, with NULL values in the columns refering to the FROM table.
By INNER joining you will have results just if you there were data that match in both tables.
Hope I understood well and it helps.

Assuming your desired output is to match only the products date with those in the calendar table, you should use an INNER JOIN:
SELECT c.dt, p.product_id, p.total_sales
FROM calendar c
INNER JOIN products p on c.dt = p.so_date
WHERE c.dt < SYSDATE and c.dt >= ADD_MONTHS(SYSDATE,-7)+1
ORDER BY c.dt ASC, p.product_id DESC;
A CROSS JOIN would produce results with every combination from your products table and your calendar table and thus not require the use of ON.
--EDIT
See edits below (UNTESTED):
SELECT PR.Product_ID, C.dt, P.TotalSales
FROM Calendar C
CROSS JOIN Product_Ref PR
LEFT JOIN Product P ON P.Product_Id = PR.Product_Id and p.so_date = c.dt
WHERE c.dt < SYSDATE and c.dt >= ADD_MONTHS(SYSDATE,-7)+1
ORDER BY c.dt ASC, p.product_id DESC;
Good luck.

Related

Count current job with a partition

The next code makes a join between users and payment, to get the last payment.
The query should work if the payment table did not contain duplicated rows with the same max_date as the following one.
Something to notice, is that the row is not completely duplicated, sometimes contains little changes. But we do not care if we select the 'right' one, we only need it to be one, no matter which one of those is.
user_ID | Payment | date | product | credit_card
1 300 1/1/2020 A No
1 300 1/1/2020 Null | No
1 300 1/1/2021 A Yes
1 300 1/1/2021 Null | Yes
This causes the second inner join to duplicate rows because it makes a match twice with the maxDate which is 1/1/2021
SELECT a.*, c.*
FROM users a
INNER JOIN payments c
ON a.id = c.user_ID
INNER JOIN
(
SELECT user_ID, MAX(date) maxDate
FROM payments
GROUP BY user_ID
) b ON c.user_ID = b.user_ID AND
c.date = b.maxDate
I'm looking for a way to select only the first match of the maxDate. Any clue is welcome, thank in advance for any help.
You should be using window functions for this. That would be:
SELECT u.*, p.*
FROM users u JOIN
(SELECT p.*,
ROW_NUMBER() OVER (PARTITION BY p.user_id ORDER BY p.date DESC) as seqnum
FROM payments p
) p
ON p.user_ID = u.id AND p.seqnum = 1;
This returns one row, but which row is arbitrary.
Note the use of meaningful table aliases in the query -- u for users and p for `payments. Don't use meaningless letters. They just make the query hard to read -- and to maintain.

How to join the results of two queries in just one table grouped by YEAR and MONTH?

I have two tables,materials_students and components_students. Both of them has afinished_at column. material_student has a component_student_id column.
I need to count the number of components_students and materials_students (Where finished_at id is not NULL), extract month and year from finished_at, group the result by month and year and plot it in just one table, like this:
| Materials | Components | Month | Year
---------------------------------------------
| 45 3 1 2019
| 37 6 2 2019
| 63 8 3 2019
I know how to do this for one table only, but dont know how to join the results in just one table.
Find below how I did for one table:
FROM materials_students
LEFT JOIN students ON materials_students.student_id = students.id
LEFT JOIN company_profiles ON students.company_profile_id = company_profiles.id
LEFT JOIN companies ON company_profiles.company_id = companies.id
WHERE materials_students.finished_at IS NOT NULL
GROUP BY YEAR, MONTH
ORDER BY YEAR, MONTH
Thanks!
The best is to assemble a subquery for each case, then join them.
select
ISNULL(M.yy, C.yy) [yy],
ISNULL(M.mm, C.mm) [mm],
ISNULL(number_material_students, 0) [number_material_students],
ISNULL(number_components_students, 0) [number_component_students]
from (
SELECT
year(materials_students.finished_at) yy,
month(materials_students.finished_at) mm,
count(*) number_material_students
FROM materials_students
LEFT JOIN students ON materials_students.student_id = students.id
LEFT JOIN company_profiles ON students.company_profile_id = company_profiles.id
LEFT JOIN companies ON company_profiles.company_id = companies.id
WHERE materials_students.finished_at IS NOT NULL
GROUP BY year(materials_students.finished_at), month(materials_students.finished_at)
) M
full outer join (
SELECT
year(components_students.finished_at) yy,
month(components_students.finished_at) mm,
count(*) number_material_students
FROM components_students
LEFT JOIN students ON components_students.student_id = students.id
LEFT JOIN company_profiles ON students.company_profile_id = company_profiles.id
LEFT JOIN companies ON company_profiles.company_id = companies.id
WHERE components_students.finished_at IS NOT NULL
GROUP BY year(materials_students.finished_at), month(materials_students.finished_at)
) C
ON C.yy = M.yy AND C.mm = M.mm
ORDER BY 1, 2
I had to make a FULL OUTER JOIN between the subqueries, because there may have been year/months that appear only on materials, but not on components, and vice-versa.
To retrieve the year I use the ISNULL() function, so in case year is not filled from the materials subquery, I use it from the components subquery. Similar reasoning applies to all other resulting columns.

SQL - group in one table for a max in another

I'm trying to do a (Microsoft) sql query to get the most recent order from one table for each product in another. That is to say - the group by is in table B, while the max is in table A. I tried a number of things, but this is the final.
WITH max_date(maxid, maxdate) as (
SELECT inmost.PROD_ID as maxid
,MAX(inmost.ORDER_DATE) as maxdate
FROM(SELECT od2.PROD_ID
,o2.ORDER_DATE
FROM ORDERS o2
INNER JOIN ORDPRODS od2 ON o2.ORDER_ID = od2.ORDER_ID
) as inmost
GROUP BY inmost.PROD_ID
)
SELECT o.ORDER_DATE
,od.ORDER_QUANT
FROM ORDERS o
INNER JOIN ORDPRODS od ON o.ORDER_ID = od.ORDER_ID
LEFT JOIN max_date mxord ON od.PROD_ID = mxord.maxid
AND o.ORDER_DATE = mxord.maxdate
WHERE o.ORDERS_Canceled = '0'
At the end of things, it's still pulling multiple version of the each product and lots and lots of date for those products. For instance:
PROD_ID ORDER_DATE
111 1/1/2015
111 1/2/2015
112 1/2/2015
112 1/3/2015
112 1/4/2015
112 1/5/2015
What I WANT is:
PROD_ID ORDER_DATE
111 1/2/2015
112 1/5/2015
The join clause here means find matching Max_Date_mxord record on the ID and the date. Return all records found in od
LEFT JOIN max_date mxord ON od.PROD_ID = mxord.maxid
AND o.ORDER_DATE = mxord.maxdate
To visualize this you can add mxord.maxdate to your SELECT clause and you'd likely see many nulls
However you want to exclude records in OD that don't match maxdate. To do that you want an INNER join.
This is labeled as #mysql, but to my knowledge, MYSQL doesn't support the WITH clause, so I'm assuming this is SQL Server.
If that's the case, you could use the CROSS APPLY function, which would look something like this.
SELECT prd.PRODID, apl.ORDER_DATE
FROM ORDPRODS prd
CROSS APPLY (
SELECT TOP 1 ORDER_DATE
FROM ORDERS ord
WHERE ord.ORDER_ID = prd.ORDER_ID
AND ord.ORDERS_Canceled = '0'
ORDER BY ORDER_DATE DESC
) apl

SQL on joining tables (Month and subtextures that has no sales)

DBMS: Derby Embedded
Hello I wonder how I can make some outcome like
SubTextureID Year Month NetSales
1 2013 10 1000
2 2013 10 2000
3 2013 10 0
The third row never appears if that product
has no sales(no records) in the order detail table
Any help would be greatly appreciated!
Thanks
Jack
select s.TextureName, s.SubTextureId, sum(COALESCE(d.NetSales, 0)) NetSales
from (select SubTextureId, TextureName from subtexture) as s
join
(select SubTextureId, ProductCode from products) as p
on (p.SubTextureId = s.SubTextureId)
left outer join
(select ProductCode, OrderCode, NetSales from order_details) as d
on (d.ProductCode = p.ProductCode)
left outer join
( select YEAR(o.PurchaseDateTime) y,
MONTH(o.PurchaseDateTime) m,
OrderCode
from orders o
where o.PurchaseDateTime between '2013-11-01 00:00:00' and '2013-11-30 23:59:59' -- make use of an index if one exists
) as o
on (o.orderCode = d.orderCode)
group by s.TextureName, s.SubTextureId, o.y, o.m
because you used LEFT OUTER JOIN, try to use RIGHT OUTER JOIN, if you understand what's difference about LEFT and RIGHT OUTER JOIN, you will handle your problem

Join query on Date Range

HI
I have following tables
=========================
Periods
=========================
PeriodID StartDate EndDate
1 01-01-11 07-01-11
2 08-01-11 15-01-11
and so on for whole year
=========================
History
=========================
PersonID From To
1 01-01-11 05-04-11
2 17-06-11 NULL
and so on
I want the following output
StartDate EndDate PersonID
01-01-11 07-01-11 1
08-01-11 15-01-11 1
.
.
15-04-11 21-04-11 NULL
.
.
15-06-11 21-06-11 2
I need to take join between these two tables but i couldn't figure how join condition will be look like
Ragards
SELECT
p.StartDate,
p.EndDate,
h.PersonID
FROM Periods p
LEFT JOIN History h
ON h.[From] BETWEEN p.StartDate AND p.EndDate OR
p.StartDate BETWEEN h.[From] AND ISNULL(h.[To], '30000101')
It would affect performance, but I think it is worth just trying the odd looking between:
select x
from table1 t1
inner join table2 t2
on t2.date between t1.startdate and t1.enddate
Whether it works or not will depend on whether this is to be production, or just a one time thing, and how many records are involved. It may be way too slow.
Can you please try:
SELECT P.StartDate, P.EndDate, H.PersonID
FROM Period P INNER JOIN History H ON P.StartDate <= H.Fromand (P.EndDate >= H.To OR H.To IS NULL)
I have edited the SQL after reading the spec more clearly
I have edited the SQL again. I'm using INNER JOIN now.
You need to do a left join in order to show all the periods available even if there are no history entries associated with that period. The criteria would be if the History date was between the period. You would also need to check if the To date was null and include it into your results
SELECT p.StartDate, p.EndDate, h.PersonId
FROM Period p
LEFT JOIN History h
ON p.StartDate >= h.[From] AND
(h.[To] IS NULL OR p.EndDate <= h.[To])
at table 'history', set NULL to '9999-12-31'
select * from periods a inner join history b on a.from < b.to and a.to > b.from