SQL join master details table - sql

I have the following two tables in SQL server:
Product:
SELECT Id, Name From Product
x------x--------------------x
| Id | Name |
x------x--------------------x
| 1 | A |
| 2 | B |
| 3 | C |
x------x--------------------x
Order:
SELECT Id, Order_Date, QTY From Order
x------x--------------------x-------x
| Id | Order_Date | QTY |
x------x--------------------x-------x
| 1 | 2014-01-13 | 10 |
| 1 | 2014-01-11 | 15 |
| 1 | 2014-01-12 | 20 |
| 2 | 2014-01-06 | 30 |
| 2 | 2014-01-08 | 40 |
x------x--------------------x-------x
I would like to get a table which contains list of all products and if there is any order placed for the product.
Example:
x------x--------------------x
| Id | Has_Order |
x------x--------------------x
| 1 | 1 |
| 2 | 1 |
| 3 | 0 |
x------x--------------------x
I tried left outer join but it includes all the rows from Order table. What is the most efficient way to write this SQL query?

You can use exists in a subquery:
select p.*,
(case when exists (select 1 from orders o where o.id = p.id)
then 1 else 0
end) as order_exists_flag
from products p;
For performance, you want an index on orders(id). I would expect this to be the fastest approach.

Just another option to consider
Select ID
,Has_Order = max(Flg)
From (
Select ID,Flg = 0 From [Product]
Union All
Select Distinct ID,Flg = 1 From [Order]
) src
Group By ID

Related

Limit join with pagination

These are my db tables:
Users
| id | nme |
|----|------|
| 1 | Adam |
| 2 | Bob |
| 3 | Jan |
| 4 | Nico |
Products
| id | price |
|----|-------|
| 1 | 500 |
| 2 | 700 |
| 3 | 900 |
Orders
| id | user_id | product_id |
|----|---------|------------|
| 1 | 1 | 1 |
| 2 | 1 | 2 |
| 3 | 1 | 3 |
| 7 | 3 | 1 |
| 8 | 3 | 2 |
| 9 | 3 | 3 |
| 10 | 4 | 3 |
I want to get up to 2 users, and their products bought. I came with this:
SELECT
users.id AS 'user_id', products.id AS 'product_id'
FROM
users
INNER JOIN
orders ON orders.user_id = users.id
INNER JOIN
products ON products.id = orders.product_id
ORDER BY
orders.id
OFFSET 0 ROWS FETCH NEXT 2 ROWS ONLY
But this returns:
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
What I want to get is up to 2 users, not orders. I want to get this:
| user_id | product_id |
|---------|------------|
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |
Any ideas?
I think you don't need a JOIN among tables but only using [Orders] table along with a window function such as DENSE_RANK() as seems the most suitable to filter out to return the result set such as
;WITH cte AS
(
SELECT *,
DENSE_RANK() OVER (ORDER BY [user_id]) AS rn
FROM [Orders]
)
SELECT [user_id], [product_id]
FROM cte
WHERE rn <= 2 -- returns only two user after sorted by their ids
ORDER BY 1, 2
Demo
You may use DENSE_RANK function with your join query as the following:
SELECT user_id, product_id
FROM
(
SELECT U.id AS 'user_id',
P.id AS 'product_id',
DENSE_RANK() OVER (ORDER BY U.id) dr
FROM users U INNER JOIN orders O
ON O.user_id = U.id
INNER JOIN products P
ON P.id = O.product_id
) T
WHERE dr between 1 and 2
ORDER BY user_id, product_id
WHERE dr between 1 and 2 allows you to perform pagination according to the order of user ids, i.e if you want to get users from 3 to 5, it will be WHERE dr between 3 and 5.
Check this demo.

Retrieving Last Invoice Price in SQL

I know this is simple for most of you!
I have an Invoices table like so:
+-----------+------------+------+-------+
| InvoiceID | Date | Item | Price |
+-----------+------------+------+-------+
| 1 | 15/04/2014 | A | 12 |
| 2 | 20/04/2014 | A | 20 |
| 3 | 20/04/2014 | A | 27 |
| 4 | 20/04/2014 | B | 29 |
| 5 | 28/04/2014 | C | 16 |
| 6 | 28/04/2014 | B | 11 |
+-----------+------------+------+-------+
I would like to retrieve the Price against the Last Invoice ID for each Item, so:
+------+-----------+
| Item | LastPrice |
+------+-----------+
| A | 27 |
| B | 11 |
| C | 16 |
+------+-----------+
What is the most efficient way of doing this? I'm using SQL Server 2008
Many thanks in advance to any help received.
with cte as (
select *, row_number() over (partition by Item order by date desc) as r
from dbo.YourTable
)
select * from cte where r = 1
This may do your work :
SELECT Item,
Price AS 'LastPrice'
FROM MyTable
WHERE InvoiceId IN (SELECT Max(InvoiceId)
FROM MyTable
GROUP BY Item)

group by top two results based on order

I have been trying to get this to work with some row_number, group by, top, sort of things, but I am missing some fundamental concept. I have a table like so:
+-------+-------+-------+
| name | ord | f_id |
+-------+-------+-------+
| a | 1 | 2 |
| b | 5 | 2 |
| c | 6 | 2 |
| d | 2 | 1 |
| e | 4 | 1 |
| a | 2 | 3 |
| c | 50 | 4 |
+-------+-------+-------+
And my desired output would be:
+-------+---------+--------+-------+
| f_id | ord_n | ord | name |
+-------+---------+--------+-------+
| 2 | 1 | 1 | a |
| 2 | 2 | 5 | b |
| 1 | 1 | 2 | d |
| 1 | 2 | 4 | e |
| 3 | 1 | 2 | a |
| 4 | 1 | 50 | c |
+-------+---------+--------+-------+
Where data is ordered by the ord value, and only up to two results per f_id. Should I be working on a Stored Procedure for this or can I just do it with SQL? I have experimented with some select TOP subqueries, but nothing has even come close..
Here are some statements to create the test table:
create table help(name varchar(255),ord tinyint,f_id tinyint);
insert into help values
('a',1,2),
('b',5,2),
('c',6,2),
('d',2,1),
('e',4,1),
('a',2,3),
('c',50,4);
You may use Rank or DENSE_RANK functions.
select A.name, A.ord_n, A.ord , A.f_id from
(
select
RANK() OVER (partition by f_id ORDER BY ord asc) AS "Rank",
ROW_NUMBER() OVER (partition by f_id ORDER BY ord asc) AS "ord_n",
help.*
from help
) A where A.rank <= 2
Sqlfiddle demo

SQL Join Help - Sum and Apply Filter to Tables Before Join

I have a Stock View (that lists all the individual pieces of stock and the stock date) and a Sales View (that lists all of the sales and the date the sale occurred).
Stock View:
+----+------+-----+------------+
| ID | Item | Qty | Date |
+----+------+-----+------------+
| 1 | A | 3 | 01/01/2000 |
| 2 | A | 2 | 02/02/2000 |
| 3 | D | 9 | 05/06/2000 |
| 4 | F | 22 | 09/01/2001 |
| 5 | A | 10 | 01/04/2001 |
| 6 | C | 12 | 01/01/2002 |
+----+------+-----+------------+
Sales View:
+------+-----+------------+
| Item | Qty | Date |
+------+-----+------------+
| B | 3 | 01/01/2001 |
| B | 77 | 01/12/2001 |
| C | 9 | 02/02/2002 |
| A | 10 | 03/03/2002 |
| G | 2 | 05/06/2002 |
| C | 3 | 09/10/2012 |
+------+-----+------------+
I want to join these tables..but before doing so:
Stock view needs to be filtered between 2 date parameters #StockFrom and #StockTo
Sales view needs to be filtered between 2 date parameters #SalesFrom and #SalesTo
Sales view then needs to be grouped by Item and have the Qty Summed (so the date field needs to be dropped although it is being filtered on) and then joined onto the Stock View on the Item field.
So in essence I want to see the Stock View as it is (but filtered on dates) with an extra column showing the sales that have occurred between 2 dates for that item.
Desired Output:
+----+------+-----+------------+-------+
| ID | Item | Qty | Date | Sales |
+----+------+-----+------------+-------+
| 1 | A | 3 | 01/01/2000 | 10 |
| 2 | A | 2 | 02/02/2000 | 10 |
| 3 | D | 9 | 05/06/2000 | 0 |
| 4 | F | 22 | 09/01/2001 | 0 |
| 5 | A | 10 | 01/04/2001 | 10 |
| 6 | C | 12 | 01/01/2002 | 12 |
+----+------+-----+------------+-------+
Thanks to any help in advance!
SELECT
Stock.*,
IFNULL(SUM(Sales.Qty),0) AS Sales
FROM Stock
LEFT JOIN Sales ON Stock.Item=Sales.Item
WHERE Stock.Date BETWEEN #StockFrom AND #StockTo
AND (
Sales.Date BETWEEN #SalesFrom AND #SalesTo
OR Sales.Date IS NULL
)
GROUP BY Stock.ID
This is for MySQL, as you didn't specify the diaclect. SQLfiddle
EDIT
SELECT
Stock.ID AS ID,
MIN(Stock.Item) AS Item,
MIN(Stock.Qty) AS Qty,
MIN(Stock.Date) AS Date,
CASE WHEN SUM(Sales.Qty) IS NULL THEN 0 ELSE SUM(Sales.Qty) END AS Sales
FROM Stock
LEFT JOIN Sales ON Stock.Item=Sales.Item
WHERE Stock.Date BETWEEN #StockFrom AND #StockTo
AND (
Sales.Date BETWEEN #SalesFrom AND #SalesTo
OR Sales.Date IS NULL
)
GROUP BY Stock.ID
works for MS SQL (SQLfiddle)
Please try below query for MS Sql Server:
SELECT DISTINCT
a.ID,
a.Item,
a.Qty,
a.Date,
ISNULL(SUM(b.Qty) OVER (PARTITION BY a.Item, a.[Date]), 0) Sales
FROM
StockView a LEFT JOIN SalesView b on a.Item=b.Item

How to return smallest value inside the resultset as a separate column in SQL?

I've been struggling with the following SQL query.
My resultset is now:
| Id | Customer | Sales |
| 1 | 1 | 10 |
| 2 | 1 | 20 |
| 3 | 2 | 30 |
| 4 | 2 | 40 |
What I'd like to do is to add additional column that shows the smallest sale for that customer:
| Id | Customer | Sales | SmallestSale |
| 1 | 1 | 10 | 10 |
| 2 | 1 | 20 | 10 |
| 3 | 2 | 30 | 30 |
| 4 | 2 | 40 | 30 |
As the select query to get those three columns is now rather complex I'd like to avoid subqueries.
Any ideas?
Mika
Assuming your RDBMS supports windowed aggregates
SELECT Id,
Customer,
Sales,
MIN(Sales) OVER (PARTITION BY Customer) AS SmallestSale
FROM YourTable
select s.Id, s.Customer, s.Sales, sm.SmallestSale
from Sales s
inner join (
select Customer, min(sales) as SmallestSale
from Sales
group by Customer
) sm on s.Customer = sm.Customer