SQL Server query troubles, many-many relationship - sql

Not sure how to word this question in one line, apologies for the title...
I have 3 tables in my database, for example:
Shop
Item
ShopStock
Shop and Item have a many-many relationship and so the ShopStock table links them.
The fields in ShopStock are:
ID
ShopID
ItemID
CurrentStock
I want to list the items, showing how much stock each shop has, but I'm having trouble with the SQL. Something like this:
ITEM TESCO STOCK ASDA STOCK SAINSBURY STOCK
Apples 5 20 74
Pears 1000 32 250
How do I build the SQL query to display the data like this?

This would be easier to list as item,shop,currentstock in multiple rows. As is, unless you know the number of shops, you're going to need to use dynamic sql for this. If you know the number of potential shops, you can use PIVOT to return your results.
Something like this assuming you had 2 shops (shop1 and shop2):
select item_name, [Shop1], [Shop2]
from
(
select item_name, shop_name, currentstock
from item i
join shopstock ss on i.item_id = ss.item_id
join shop s on s.shop_id = ss.shop_id
) x
pivot
(
max(currentstock)
for shop_name in ([Shop1],[Shop2])
) p
SQL Fiddle Demo
Here is the dynamic sql approach as I suspect you don't know the number of possible shops:
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = stuff((select distinct ',' + quotename(shop_name)
from shop
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'select item_name,' + #cols + '
from
(
select item_name, shop_name, currentstock
from item i
join shopstock ss on i.item_id = ss.item_id
join shop s on s.shop_id = ss.shop_id
) x
pivot
(
max(currentstock)
for shop_name in (' + #cols + ')
) p '
execute(#query)
SQL Fiddle Demo

You might need to add JOINS to get specific names, but this is the idea you're after:
SELECT ItemID
, MAX(CASE WHEN ShopID = 'Tesco' THEN CurrentStock ELSE 0 END)'Tesco Stock'
, MAX(CASE WHEN ShopID = 'ASDA' THEN CurrentStock ELSE 0 END)'ASDA Stock'
, MAX(CASE WHEN ShopID = 'Sainsbury' THEN CurrentStock ELSE 0 END)'SainsburyStock'
FROM ShopStock
GROUP BY ItemID
Assuming one entry per item per shopID. If there are multiples then you would have to SUM() them, but the idea is the same.

Related

How to combine the column values from the same table with condition in sql select query

I want to combine the Currency field by comparing Config and Product Column. If both field is repeated with duplicate values but different currency, the combine the currency into single row as you see in the screenshot.
I tried the code like
Select DISTINCT LC.Config, LC.Product, CONCAT(LC.Currency,',',RC.Currency) as Currencies FROM [t_LimitCurrency] LC INNER JOIN [t_LimitCurrency] RC ON LC.[Config] = RC.[Config] AND LC.Product = RC.Product
Please let me know, how to write select statement for this scenario.
Below Code should do the trick. I am using XML Path but you can use String_AGG in latest version of sql server
select distinct Config,Product,
STUFF((SELECT ' ,' + CAST(Currency AS VARCHAR(max)) [text()]
FROM (
SELECT Currency
FROM Yourtable b
WHERE a.Config=b.Config and a.product=b.product
) ap
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') Currency
from Yourtable a
EDIT 1 : for latest version of sql server code should be like below
select distinct Config,Product,
(SELECT
STRING_AGG(CONVERT(NVARCHAR(max),Currency), ',')
FROM YourTable b WHERE a.Config=b.Config and a.product=b.product)
Currency
from Yourtable a

SQL pivot / VLOOKUP query

I have the following simplified table so each stock code has
multiple warehouses and I want to "pivot" it so I can get
the cost for each warehouse just in 1 line for the stockcode
Stockcode | Warehouse | Cost
A100 WH 100$
A100 ZZ 200$
What I would like to have is the following.
So only 1 stockcode line with the warehouse costs pivoted.
Stockcode | Cost WH | Cost ZZ
A100 100$ 200$
I currently did it by exporting it to excell, creating a new sheet and add a new column to the sheet using a VLOOKUP to the main table and add the ZZ cost like that.
How would you turn this around in SQL? I am a bit stuck with understanding how to transform this tabular data in such a way.
Here's how to do this query using PIVOT:
select Stockcode, [WH] as CostWH, [ZZ] as CostZZ
from (
select Stockcode, Warehouse, Cost
from MyTable
) p
pivot (
MAX(Cost)
for Warehouse in ([WH], [ZZ])
) as pvt
order by Stockcode;
Test it on SQLFiddle
I think (Stockcode,Warehouse) is unique in your table.
And I think you can use PIVOT like it
SELECT Stockcode,[WH],[ZZ]
FROM TestData PIVOT(MAX(Cost) FOR Warehouse IN([WH],[ZZ])) p
And use dynamic query for generate query for all the warehouses
DECLARE #Warehouses varchar(500)=''
SELECT #Warehouses+=CONCAT(',[',Warehouse,']')
FROM
(
SELECT DISTINCT Warehouse
FROM TestData
) q
ORDER BY Warehouse
SET #Warehouses=STUFF(#Warehouses,1,1,'')
PRINT #Warehouses
DECLARE #query varchar(2000)=CONCAT('SELECT Stockcode,',#Warehouses,'
FROM TestData PIVOT(MAX(Cost) FOR Warehouse IN(',#Warehouses,')) p')
PRINT #query
EXEC(#query)
My another answer similar to it - Display count results of requests with results of jobs horizontally and locations vertically 3 tables
However, you could also do that by using simple case expression if you don't want to go pivot or dynamic pivot
SELECT
Stockcode ,
MAX( CASE (Warehouse) WHEN 'WH' THEN Cost END)[Cost WH],
MAX(CASE (Warehouse) WHEN 'ZZ' THEN Cost END) [Cost ZZ]
FROM <table> GROUP BY Stockcode
Dynamic pivot way :
DECLARE #Col nvarchar(max), #query nvarchar(max)
select #Col = STUFF(
(SELECT ',' +QUOTENAME(Warehouse) FROM <table> FOR XML PATH('')),
1, 1, ''
)
SELECT #query = N'select Stockcode, [WH] as CostWH, [ZZ] as CostZZ
from (
select Stockcode, Warehouse, Cost
from <table>) p
pivot (max(Cost) for Warehouse in ('+#Col+')
) as pvt order by Stockcode;'
exec(#query)
Result :
Stockcode Cost WH Cost ZZ
A100 100$ 200$

Find similar sales orders in SQL

This is my first post.
I work at a manufacturing company and most of the products we are making are custom made.
We believe we can find some commonalities in the products we sale.
To do this, we need to analyze sales orders and compare them to all the sales orders in our system to find identical ones.
Here's an example in form of a SQL result:
etc...
+------------------------------+
| OrderId ProductCode Qty |
+------------------------------+
| SS1234 Widget1 1 |
| SS1234 Widget2 3 |
| SS1234 Widget3 1 |
+------------------------------+
I would like to find orders similar to SS1234, ie orders with the same products (widget1, widget2 and widget3) and the same quantities.
How do I do this in SQL Server 2008R2?
Thanks for your help!
Raf
I won't be able to test this before I go to bed for the evening. This is an overly verbose approach, but I wanted to grind this out as quickly as possible so I tried to use structure / syntax that I know well, instead of trying to write more concise, efficient code that would require I lean on the documentation. Basically, we're counting the number of items in each order, selecting a pair of order ids every time we find two matching line items, then we count how many times an exact pair of order IDs appears. Use inner joins to filter out pairs that matched fewer times than there are products in the order.
WITH
ProductCounts AS (
SELECT COUNT(OrderID) AS ProductCodesInOrder, OrderID
FROM Table
GROUP BY OrderID
), MatchingLineItems AS (
SELECT A.OrderID AS FirstOrderID, B.OrderID AS SecondOrderID
FROM Table AS A
INNER JOIN Table AS B
ON A.ProductCode = B.ProductCode AND A.Qty = B.Qty
ORDER BY FirstOrderID, SecondOrderID
), MatchTotals AS (
SELECT
COUNT(FirstOrderID) AS Matches, FirstOrderID, SecondOrderID
FROM MatchingLineItems
GROUP BY FirstOrderID, SecondOrderID
), FirstMatches AS (
SELECT MatchTotals.FirstOrderID, MatchTotals.SecondOrderID, MatchTotals.Matches
FROM MatchTotals
INNER JOIN ProductCounts
ON MatchTotals.FirstOrderID = ProductCounts.OrderID
WHERE MatchTotals.Matches = ProductCounts.ProductCodesInOrder
)
SELECT FirstMatches.FirstOrderID, FirstMatches.SecondOrderID
FROM FirstMatches
INNER JOIN ProductCounts
ON FirstMatches.SecondOrderID = ProductCounts.OrderID
WHERE FirstMatches.Matches = ProductCounts.ProductCodesInOrder
Setup:
CREATE TABLE #ord (
OrderId VARCHAR(20),
ProductCode VARCHAR(40),
qty int
)
INSERT INTO #ord (OrderId, ProductCode, Qty)
VALUES
('SS1234','Widget1',1)
,('SS1234','Widget2',3)
,('SS1234','Widget3',1)
,('SS1234a','Widget1',1)
,('SS1234a','Widget2',3)
,('SS1234a','Widget3',1)
,('xSS1234','Widget1',1)
,('xSS1234','Widget2',3)
,('xSS1234','Widget3',1)
,('xSS1234','Widget4',1)
,('ySS1234','Widget1',10)
,('ySS1234','Widget2',3)
,('ySS1234','Widget3',1)
,('zSS1234','Widget2',3)
,('zSS1234','Widget3',1)
;
Query:
with CTE as (
select distinct
o.OrderID, ca.ProductString, ca.QtyString
from #ord o
cross apply (
SELECT
STUFF((
SELECT
', ' + o2.ProductCode
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
, STUFF((
SELECT
', ' + cast(o2.Qty as varchar)
FROM #ord o2
WHERE o.OrderID = o2.OrderID
ORDER BY o2.ProductCode
FOR XML PATH ('')
)
, 1, 1, '')
) ca (ProductString, QtyString)
)
select
ProductString, QtyString, count(*) Num_Orders
from CTE
group by
ProductString, QtyString
having
count(*) > 1
order by
Num_Orders DESC
, ProductString
Result:
ProductString QtyString Num_Orders
Widget1, Widget2, Widget3 1, 3, 1 2
See: http://rextester.com/DJEN59714

sql program with Group by 2 attributes and 1 condition (Import)

I have a text file that contains Candy issued data.
File contains customer id, issue date, candy name.
C1,2014-01-01,Candy1
C1,2014-01-02,Candy2
C2,2002-06-01,Candy2
C1,2014-01-02,Candy3
C2,2002-06-01,Candy3
I am trying to figure out how can I write a program that creates a list of pairs of candies which were issued together i.e. issued to the same customer on the same day at least twenty-five different times.
Thanks in advance.
--create a table to hold our data
create table CandySales
(
CustomerId nchar(2)
, SaleDate Date
, CandyId nvarchar(10)
)
--upload the data from the csv
bulk insert CandySales
from 'c:\temp\myCsv.csv'
with (fieldterminator = ',', rowterminator = '\n')
--query the data
;with cte as
(
select customerId, candyId, saleDate
from CandySales
group by customerId, candyId, saleDate
having COUNT(1) >= 25
)
select distinct
a.CandyId Item1
, b.CandyId Item2
from cte a
inner join cte b
on a.CandyId > b.CandyId
and a.SaleDate = b.SaleDate
and a.CustomerId = b.CustomerId
Explanation of Query
The cte creates a list of all candies which have had 25 or more sales to the same customer on the same day.
include distinct as we may have the same pair returned for multiple dates/customers; we only want each pair once.
on a.CandyId > b.CandyId is used as we want to ensure the pair contains different candies from one another. We use > instead of != to avoid getting the same pair with item1 and item2 reversed.
we then join on sale date and customer date as we want items which were sold to the same customer on the same day.

subquery the same table in select statement

I have a resturant db and I need to total up the total value of all the items sold individually. So if I sold a hamburger that has a base price of $10.00 with bacon which costs $1.00 and a hambuger(again $10.00) with avacado that costs $0.50 I need to get $21.50 returned. My invoice table looks like this:
invoice_num item_num price item_id parent_item_id
111 hmbg 10.00 guid_1 ''
111 bacn 1.00 guid_2 guid_2
112 hmbg 10.00 guid_3 ''
112 avcd 0.50 guid_4 guid_3
I can get the sum of all the parent items like this:
SELECT item_num, SUM(price) FROM invoices WHERE parent_item_id = ''
it is the adding of the toppings that is confusing me. I feel like I need to add a subquery in the SUM but I'm not sure how to go about doing it and referencing the original query to use the item_id.
SELECT item_num, sum(i.price) + sum(nvl(x.ingred_price,0))
FROM invoices i
LEFT OUTER JOIN
(SELECT parent_item_id
, sum(price) ingred_price
FROM invoices
WHERE parent_item_id IS NOT NULL
GROUP BY parent_item_id) x
ON x.parent_item_id = i.item_id
WHERE i.parent_item_id IS NULL
GROUP BY item_num
Here's a SQL Fiddle that proves the above code works. I used Oracle, but you should be able to adapt it to whatever DB you are using.
Assumption: You don't have more than one level in a parent child relationship. E.g. A can have a child B, but B won't have any other children.
Not clear based on your question (see my comment) but as I understand it a simple group by will give you what you want. If not please explain (in the original question) why does this query does not work --- what is it missing from your requirements?
SELECT item_num, SUM(price)
FROM invoices
GROUP BY item_num
Hard to say, but looks like you need recursive cte.
Here's example for PostgreSQL:
with recursive cte as (
select
t.invoice_num, t.price, t.item_id, t.item_num
from Table1 as t
where t.parent_item_id is null
union all
select
t.invoice_num, t.price, t.item_id, c.item_num
from Table1 as t
inner join cte as c on c.item_id = t.parent_item_id
)
select invoice_num, item_num, sum(price)
from cte
group by invoice_num, item_num
sql fiddle demo
I've used null for empty parent_item_id (it's better solution than using empty strings), but you can change this to ''.