Joining results of two joins - sql

I am looking for the best way to create a query that will do the following(Note: This is just an example, I know this example can be written better, my real goal is a much more complex example, but follows a similar structure)
items
item_id | name | cost
1 | Water | 1.00
2 | Chips | 1.50
trans_local
local_id | item_id | date
1 | 1 | '2018-03-12'
2 | 1 | '2018-03-13'
3 | 2 | '2018-03-13'
trans_international
international_id | item_id | currency | date
1 | 1 | 'GBP' | '2018-03-11'
2 | 2 | 'EUR' | '2018-03-12'
3 | 2 | 'GBP' | '2018-03-12'
I'm looking to create a select statement a table that lists all transactions in it.
local_id | international_id | item_id | item_name | item_cost | currency | date
1 | null | 1 | Water | 1.00 | null | '2018-03-12'
2 | null | 1 | Water | 1.00 | null | '2018-03-13'
3 | null | 2 | Chips | 1.50 | null | '2018-03-13'
null | 1 | 1 | Water | 1.00 | 'GBP' | '2018-03-11'
null | 2 | 2 | Chips | 1.50 | 'EUR' | '2018-03-12'
null | 3 | 2 | Chips | 1.50 | 'GBP' | '2018-03-12'
items should be joined to either trans_local or trans_international. I know I can do this with LEFT OUTER JOIN, but I am unsure how to get results of all transactions together. I believe it would be a FULL JOIN but getting everything together is confusing me a bit. Any help would be greatly appreciated.

I think the the only way to achieve your result is to use a UNION query, something like following:
(
SELECT
NULL as international_id,
trans_local.local_id,
items.name as item_name,
items.cost as item_cost,
NULL as currency,
trans_local.date as date
FROM items
LEFT JOIN trans_local ON items.item_id = trans_local.item_id
)
UNION
(
SELECT
trans_international.international_id as international_id,
NULL as local_id,
items.name as item_name,
items.cost as item_cost,
trans_international.currency as currency,
trans_international.date as date
FROM items
LEFT JOIN trans_international ON items.item_id = trans_international.item_id
)

select local_id , international_id, A.item_id, item_name, item_cost,
currency, B.DATE
from items A
left outer join
trans_local B
on (A.item_id = B.item_id)
left outer join
trans_international C
on (A.item_id = C.item_id)
as per your requirement your item_id column is having all the values. so find the column on table for which u want all the values and make it as left outer join.

I think you want a union all:
select i.*, t.*
from ((select local_id, NULL as internationalid, item_id, null as currency, date
from trans_local
) union all
(select NULL as local_id, internationalid, item_id, currency, date
from trans_international
)
) t join
item i
on t.item_id = i.item_id;

Related

tSQL aggregate functions and group bys

So, I can't seem to find a way to get this to work. But, what I need is as follow.
I have a table that has lets say types.
Type_ID, Type_Description
Then I have a table of Items. [Item Type is fk to type table]
Item_ID, Item_Type
Then I have a Results Table. [Item_ID is fk to Item table]
Result_ID, Item_ID, Cost
So what i am needing for output is Grouped by the Type_ID
- The Count of Items(can be 0),
- The Count of Results(Can be 0),
- and the sum of Cost(can be 0)
I dont have direct access to these tables. I am having to build the sql and send it to an api so I dont get to know the error simply the results if successful and error 500 if not.
Seems to be older tSQL. As List and STRING_AGG dont seem to be available.
EDIT: As requested - Sample Data
+---------+------------------+
| Type_ID | Type_Description |
+---------+------------------+
| 1 | Example 1 |
+---------+------------------+
| 2 | Example 2 |
+---------+------------------+
+---------+---------------+
| ITEM_ID | ITEM_TYPE |
+---------+---------------+
| 1 | 1 |
+---------+---------------+
| 2 | 1 |
+---------+---------------+
| 3 | 1 |
+---------+---------------+
| 4 | 2 |
+---------+---------------+
| 5 | 2 |
+---------+---------------+
+-----------+---------+------+
| Result_ID | Item_ID | Cost |
+-----------+---------+------+
| 1 | 1 | 10 |
+-----------+---------+------+
| 2 | 1 | 20 |
+-----------+---------+------+
| 3 | 2 | 5 |
+-----------+---------+------+
| 4 | 5 | 100 |
+-----------+---------+------+
Desired Output
+---------+------------+--------------+------+
| Type_ID | Item_Count | Result_Count | Cost |
+---------+------------+--------------+------+
| 1 | 3 | 3 | 35 |
+---------+------------+--------------+------+
| 2 | 2 | 1 | 100 |
+---------+------------+--------------+------+
I think GMB's answer was quite good, but in case there is a type with no items (something in your requirements), it will not be displayed.
So first of all let's create the input data:
select 1 as Type_ID, 'Example 1' as Type_Description into #type
union all
select 2, 'Example 2'
union all
select 3, 'Example 3'
select 1 Result_ID, 1 Item_ID, 10 Cost into #item
union all
select 2, 1, 20 Cost
union all
select 3, 2, 5 Cost
union all
select 4, 5, 100 Cost
select 1 Item_ID, 1 Item_Type INTO #item_type
union all
select 2, 1
union all
select 3, 1
union all
select 4, 2
union all
select 5, 2
Note I added also a type 3 with no items to test the no item case.
And then the query you need:
SELECT
t.Type_ID,
COUNT(DISTINCT it.Item_ID) Item_Count,
COUNT(DISTINCT i.Result_ID) Result_Count,
SUM(ISNULL(Cost, 0)) Cost
FROM #type t
LEFT JOIN #item_type it on it.Item_Type = t.Type_ID
LEFT JOIN #item i on i.Item_ID = it.Item_ID
GROUP BY t.Type_ID
I think it is pretty straightforward and doesn't need much explanation, but feel free to ask in the comments if necessary.
The results are just like you requested, with also a line for type 3:
+---------+------------+--------------+------+
| Type_ID | Item_Count | Result_Count | Cost |
+---------+------------+--------------+------+
| 1 | 3 | 3 | 35 |
+---------+------------+--------------+------+
| 2 | 2 | 1 | 100 |
+---------+------------+--------------+------+
| 3 | 0 | 0 | 0 |
+---------+------------+--------------+------+
You mentioned that the count of items could be 0 and also the count of results. But aren't both values always either 0, or both > 0? Only in case your type-item many-to-many table doesn't have a FK, you could have that scenario. For example, if I add:
insert into #item_type
select 6, 3
Then the last row is:
+---------+------------+--------------+------+
| 3 | 1 | 0 | 0 |
+---------+------------+--------------+------+
I am not sure if that makes sense in your scenario, but as your post implies that items and results can be 0 independently, that confused me a bit.
You can join and aggregate. Assuming that your tables are called types, types_costs and costs, that would be:
select
t.type_id,
count(distinct tc.item_id) item_count,
count(distinct c.result_id) result_count,
sum(c.cost) cost
from types t
inner join types_costs tc on tc.item_type = t.type_id
left costs c on c.item_id = tc.item_id
group by t.type_id
An important thing is to use a left join to bring the costs table so item_ids that do not exist in costs are not eliminated before you get a change to count them. Depending on your actual use case, you might also want a left join on table types_costs.

SQL Server : display distinct list of orders, and indicate which orders contain specific products

I have a table that contains a lot of data, but the relevant data in the table looks something like this:
Orders table:
+----------+-----------+---------------+
| OrderID | Product | Date |
+----------+-----------+---------------+
| 1 | Apple | 01/01/2001 |
| 1 | Pear | 01/01/2001 |
| 1 | Pear | 01/01/2001 |
| 1 | Orange | 01/01/2001 |
| 1 | Pineapple | 01/01/2001 |
| 2 | Cherry | 02/02/2002 |
| 2 | Cherry | 02/02/2002 |
| 3 | Orange | 03/03/2003 |
| 3 | Apple | 03/03/2003 |
| 3 | Cherry | 03/03/2003 |
+----------+-----------+---------------+
I'd like a query to return a distinct list of orders, and if the order contains certain products, to indicate as such:
+----------+-----------+--------+-------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+--------+-------+
| 1 |01/01/2001 | X | X |
| 2 |02/02/2002 | | |
| 3 |03/03/2003 | X | |
+----------+-----------+--------+-------+
Here's where I've left off and decided to seek out help:
WITH CTEOrder AS
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS OrderRN
FROM
Orders
)
CTEApple as
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS AppleRN
FROM
Orders
WHERE
Product = 'Apple'
),
CTEPear
(
SELECT
OrderID, Product, Date,
ROW_NUMBER() OVER (PARTITION BY OrderID ORDER BY OrderID ASC) AS PearRN
FROM
Orders
WHERE
Product = 'Pear'
)
SELECT
o.OrderID, o.Product, o.Date,
co.OrderRN, a.AppleRN, p.PearRN
FROM
Orders AS o
OUTER JOIN
CTEOrder AS co ON o.OrderID = co.Orderid
OUTER JOIN
CTEApple AS a ON o.OrderID = a.OrderID
OUTER JOIN
CTEPear AS p ON o.OrderID = p.OrderID
WHERE
(co.OrderRN IS NULL AND a.AppleRN IS NULL AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN IS NULL AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN = 1 AND p.PearRN IS NULL
OR co.OrderRN = 1 AND a.AppleRN = 1 AND p.PearRN = 1
OR co.OrderRN = 1 AND a.AppleRN IS NULL AND p.PearRN = 1
OR co.OrderRN IS NULL AND a.AppleRN = 1 AND p.PearRN IS NULL
OR co.OrderRN IS NULL AND a.AppleRN = 1 AND p.PearRN = 1
OR co.OrderRN IS NULL AND a.AppleRN IS NULL AND p.PearRN = 1)
Currently my result set is unwieldy with a significant amount of duplication.
I'm thinking that I am heading in the wrong direction, but I don't know what other tools are available to me within SQL Server to cut up this data the way I need.
Thanks for any guidance!
Here's my result set after Nik Shenoy's guidance:
+----------+-----------+----------------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+----------------+
| 1 | 01/01/2001| x | NULL |
| 1 | 01/01/2001| NULL | x |
| 1 | 01/01/2001| NULL | x |
| 1 | 01/01/2001| NULL | NULL |
| 1 | 01/01/2001| NULL | NULL |
| 2 | 02/02/2002| NULL | NULL |
| 2 | 02/02/2002| NULL | NULL |
| 3 | 03/03/2003| NULL | NULL |
| 3 | 03/03/2003| x | NULL |
| 3 | 03/03/2003| NULL | NULL |
+----------+-----------+----------------+
What is my next step to have only 1 row per Order:
+----------+-----------+--------+-------+
| OrderID | Date | Apple? | Pear? |
+----------+-----------+--------+-------+
| 1 |01/01/2001 | X | X |
| 2 |02/02/2002 | | |
| 3 |03/03/2003 | X | |
+----------+-----------+--------+-------+
You can just use conditional aggregation:
select o.orderid, date,
max(case when product = 'Apple' then 'X' end) as IsApple,
max(case when product = 'Pear' then 'X' end) as IsPear
from orders o
group by o.orderid, date;
If you know all the products in advance, you can use the Transact-SQL PIVOT relational operator to cross-tabulate the data by product. If you use MAX or COUNT, you can just transform non-NULL or non-ZERO output to an 'x'
SELECT
PivotData.OrderID
, PivotData.OrderDate
, CASE WHEN PivotData.Apple IS NULL THEN '' ELSE 'X' END AS [Apple?]
, CASE WHEN PivotData.Pear IS NULL THEN '' ELSE 'X' END AS [Pear?]
, CASE WHEN PivotData.Orange IS NULL THEN '' ELSE 'X' END AS [Orange?]
, CASE WHEN PivotData.Pineapple IS NULL THEN '' ELSE 'X' END AS [Pineapple?]
, CASE WHEN PivotData.Cherry IS NULL THEN '' ELSE 'X' END AS [Cherry?]
FROM
(SELECT OrderID, Product, OrderDate) AS [Order]
PIVOT (MAX(Product) FOR Product IN ( [Apple], [Pear], [Orange], [Pineapple], [Cherry] )) AS PivotData

SUM values in last row

I have table with values
+--------+------------+-------------+
| XPK | Money | NumOfDevices|
+--------+------------+-------------+
| 1 | 1000 | 2 |
| 2 | 2000 | 3 |
| 3 | 3000 | 4 |
+--------+------------+-------------+
Need to sum all values and to enter the asterisk "*" in entire row to separate TOTAL Values from other values, so result need to look something like this
+--------+------------+-------------+
| XPK | Money | NumOfDevice |
+--------+------------+-------------+
| 1 | 1000 | 2 |
| 2 | 2000 | 3 |
| 3 | 3000 | 4 |
|***********************************|
| TOTAL | 6000 | 9 |
+--------+------------+-------------+
Any idea ?
The easiest way for this is to use a UNION selecting the totals from the table:
Select Convert(Varchar (10), XPK) XPK,
Money,
NumOfDevices
From YourTable
Union
Select 'TOTAL' As XPK,
Sum(Money),
Sum(NumOfDevices)
From YourTable
Order By Case When XPK = 'TOTAL' Then 1 Else 0 End, XPK
Another method of doing this would be to use a GROUP BY WITH ROLLUP
Select Case When Grouping(XPK) = 1
Then 'TOTAL'
Else Convert(Varchar (10), XPK)
End As XPK,
Sum(Money) Money,
Sum(NumOfDevices) NumOfDevices
From YourTable
Group By XPK With Rollup
Order By Grouping(XPK)
IN Table ERP System SAP
select B.ItemCode as'Item No.',B.Dscription as'Item Description',B.Quantity,B.Price as'Sale Amt',B.LineTotal as'Total'
from ODLN A
inner join DLN1 B On A.DocEntry=B.DocEntry
union all
select 'Total',convert(nvarchar(10),sum(case when b.Dscription is not null then 1 else 0 end)),sum(b.quantity),sum(b.price),sum(b.lineTotal)
from ODLN A
inner join DLN1 B On A.DocEntry=B.DocEntry

SQL Find all rows with foreign key 1 when any of foreign key 2 appear in second table

I'm up against the limit of my query writing expertise.
I have the following table in which the combination of extid + extdt us a sort of compound key:
ents
entid | extid | extdt | itemid |
=======================================
1000 | 100 | '2016-08-01' | 1 |
1001 | 100 | '2016-08-01' | 2 |
1002 | 200 | '2016-08-01' | 3 |
1003 | 100 | '2016-08-02' | 4 |
1004 | 200 | '2016-08-02' | 5 |
1005 | 100 | '2016-08-02' | 6 |
So if itemid (1 or 2)are in the items table, the query will return both row 1000 and 1001. If itemid 3 exists, row 1002 is returned and so on...
items
itemid | itemDesc |
===================
1 | 'fu' |
3 | 'bar' |
4 | 'blah' |
With the above items table, I would expect to get back :
entid | extid | extdt | itemid |
=======================================
1000 | 100 | '2016-08-01' | 1 |
1001 | 100 | '2016-08-01' | 2 |
1002 | 200 | '2016-08-01' | 3 |
1003 | 100 | '2016-08-02' | 4 |
1005 | 100 | '2016-08-02' | 6 |
I can't think of an aggregate function that would do what I'm looking for, nor does it seem like ANY/EXISTS would work. I'm getting hung up on the grouping the itemids... Could anyone please point me in the right direction?
First you need get the composite keys matching your items, but include DISTINCT to avoid duplicates
SELECT DISTINCT extid, extdt
FROM ents
JOIN items
ON ents.itemid = items.itemid
Now you retrive every row matching the selected composite key
SELECT *
FROM ents
JOIN ( SELECT DISTINCT extid, extdt
FROM ents
JOIN items
ON ents.itemid = items.itemid
) comp_key
ON ents.extid = comp_key.extid
AND ents.extdt = comp_key.extdt
select *
from ents e1
where e1.extid in
(select extid
from ents e2
where e2.itemid in (select itemid from items))
Maybe? You could also modify the last inner query for the item ids you specificly want.
Just join em up based on the logic
SELECT e.*
-- records from the ents table
FROM ents e
-- with an extid that matches
JOIN ents extid on e.extid = extid.extid
-- all the records with an itemid in the items table.
JOIN items i on extid.itemid = i.itemid
if the unique key is id and date then use
JOIN ents extid on e.extid = extid.extid and e.extdt = extid.extdt
From your description (but not the example, which seems to contradict it):
SELECT e.*
FROM item i
JOIN ent e ON e.itemid = i.itemid
But I suspect the problem isn't that simple?
SELECT e.*
FROM [Test].[dbo].[ents] e,[Test].[dbo].[items] i
WHERE e.extid in (SELECT extid from [Test].[dbo].[ents] oe where oe.itemid=i.itemid)
and e.extdt in (SELECT extdt from [Test].[dbo].[ents] oe where oe.itemid=i.itemid)
order by itemid

Sql Inner Join among 2 tables summing the qty field multiple times

I have two tables , A and B
Table A contains:
OrderNo | StyleNo | Qty
O-20 | S-15 | 20
O-20 | S-18 | 40
O-25 | S-19 | 50
Table B contains:
OrderNo | StyleNo | Ship Qty
O-20 | S-15 | 5
O-20 | S-18 | 30
O-20 | S-15 | 12
O-20 | S-18 | 6
Result Requires
OrderNo | StyleNo | Qty | Ship Qty
O-20 | S-15 | 20 | 17
O-20 | S-18 | 40 | 36
O-25 | S-19 | 50 | 0
The following query is not working
select
B.Orderno, B.StyleNo, sum(A.Qty), sum(B.QtyShip)
from
A
inner join
B on A.OrderNo = B.OrderNo and A.StyleNo = B.StyleNo
group by
B.OrderNo, B.StyleNo
The issue you're having is that it's summing the qty field multiple times. Move the sums to subqueries and use a join on those:
select a.orderno, a.styleno, a.qty, b.qtyship
from (
select orderno, styleno, sum(qty) qty
from a
group by orderno, styleno
) a
join (
select orderno, styleno, sum(qtyship) qtyship
from b
group by orderno, styleno
) b on a.orderno = b.orderno and a.styleno = b.styleno
SQL Fiddle Demo