select from table A when condition is met in table B - sql

Not the best title hence why I was unable to find a solution that would fix mine, point me in the direction if you know of one.
Currently I have the below query,
SELECT PRODUCT_NAME
,LIVE
,LOCATION
FROM PRODUCT_TABLE
WHERE ORDER = 'ONLINE'
AND LIVE = '0' OR '1'
This essentially is a really simple query that pulls back a lot of data in which I've been using Excel to drill down to what I need however as I'm sure you can imagine a really tedious process so preferably prefer to do it straight with SQL as I know it can be done just my knowledge has completely disappeared after not using it in a while.
But essentially what I'm wanting to achieve is looking for all products online that are live at a location (0 means YES in the above query) and have matching product name at another location that isn't live (in this case not live = 1).
For example below is some data that is formatted in a similar sort of way.
LOCATION LIVE PRODUCT_NAME
BERLIN 0 CHAIR
LONDON 1 CHAIR
PARIS 0 LAMP
PARIS 0 SOFA
WARSAW 1 CHAIR
MADRID 0 CHAIR
MANCHESTER 1 SOFA
If someone could provide a solution or point me in the right direction that would be great, thanks!

You want something like this:
select pt.*
from product_table pt
where pt.live = '0' and
exists (select 1
from product_table pt2
where pt2.product_name = pt.product_name and
pt2.location <> pt.location and
pt2.live = '1'
);

SELECT DISTINCT
pt.PRODUCT_NAME
,pt.LIVE
,pt.LOCATION
FROM PRODUCT_TABLE pt
LEFT JOIN PRODUCT_TABE pt2
on pt.NAME = pt2.NAME
AND pt2.LIVE = 1
AND pt.LOCATION != pt2.LOCATION
WHERE pt.ORDER = 'ONLINE'
AND pt.LIVE = '0'
AND pt2.NAME IS NOT NULL

Related

Counting prefixes in a joined table in SQL

I am trying to count how many makes of car a person owns. Car makes are only defined by a prefix in my Links table.
Table 1 (Person)
UniqueID Name
PER0001 Adrian
PER0002 Michael
Per0003 James
Table 2 (Links)
UniqueID LinkEnd1_ID LinkEnd2_ID
LIN0001 PER0001 FER02332
LIN0002 PER0001 FER02112
LIN0003 PER0001 POR12122
LIN0004 PER0002 FER12321
LIN0005 PER0003 MAS12382
LIN0006 PER0003 FER22982
LIN0006 PER0003 MAS12232
Output (option 1)
Name Car_Make Count
Adrian FER 2
Adrian POR 1
Michael FER 1
James MAS 2
James FER 1
Output (option 2 - preferred)
Name FER POR MAS
Adrian 1 2
Michael 1
James 1 2
The reason I am using a link table to count the number of car makes is because every car make has a different table I would need to join in.
I've tried
select count left(LinkEnd2_ID,3) which doesnt work, i've also tried group by which I cant seem to crack.
I guess what I want to be able to do is
select
count(left(LinkEnd2_ID,3)='FER'
,count(left(LinkEnd2_ID,3)='POR'
,count(left(LinkEnd2_ID,3)='MAS'
but thats a query in a select and I decipher how to code that properly.
Heres where I am starting from (or the base I keep going back to start afresh)-
SELECT
Person.Unique_ID
,Person.Name
,left(Link.LinkEnd2_ID,3) as Car_Make
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
Any help you can offer would be appreciated.
Nearly there, you just need to add a group by, and change all the columns to aggregate functions.
Your option 1:
SELECT
max(Person.Name) as Person_Name
,left(Link.LinkEnd2_ID,3) as Car_Make
,count(*) as No_of_Car
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID
For your option 2, you need to wrap your aggregate functions around case statements
you have to hardcode the 3 different car make, so if you have unknown number of them, it wouldn't work.
SELECT
max(Person.Name) as Person_Name
,sum(case when left(Link.LinkEnd2_ID,3) ='FER' then 1 else 0 end) as FER
,sum(case when left(Link.LinkEnd2_ID,3) ='POR' then 1 else 0 end) as POR
,sum(case when left(Link.LinkEnd2_ID,3) ='MAS' then 1 else 0 end) as MAS
FROM
Person
LEFT JOIN
Links as Link
on Person.Unique_ID = Link.LinkEnd1_ID
GROUP BY
Person.Unique_ID

SQL query: return specific value in 2nd table, else return NULL

I'm trying to make a join on 2 tables - let's call them CAT and DRINK. What I'm trying to do is return only a specific "Type" from the DRINK table, else return NULL for that table. That said, I still want all rows from my CAT table.
So if the type of drink I'm trying to return is "Milk", the result of my query: -
Garfield | Milk
Tom | Milk
Hello Kitty | NULL
In the above example, Garfield and Tom have "Milk" in the DRINK table (they might also have some other values, like "Wine" or "Beer") and Hello Kitty does not have "Milk" (hence the NULL).
I've been trying to solve this doing some UNION or UNION ALL queries combined with a WHERE on "Type" (am I on the right path here?) but have had no luck.
Would anybody please be able to point me in the right direction?
Thanks.
Use LEFT JOIN:
SELECT C.Cat, D.Drink
FROM CAT AS C
LEFT JOIN DRINK AS D
ON C.CatId = D.CatId
AND D.Drink = 'Milk'

SQL select output to XML

Trying to do a select in SQL Server 2005 and send the output to xml. Table 2 is a general use table with various types of info. Some product info is in there if it's type 2, it's a sales lead if it's type 1. We can have multiple sales leads and products for each case_num from table 1.
Table 1
case_num,
date
table 2 (general use)
case_num,
rec_type (1=sales lead; 2=product),
various info based on type in generic columns =
col_a,
col_b,
I'm trying something like:
select
case.case_num
,case.date
,product.col_a as product_name
,product.col_b as product_price
,lead.col_a as sales_lead_name
,lead.col_b as sales_lead_address
from
table_1 case
,table_2 product
,table_2 lead
where
(case.case_num = product.case_num AND product.rec_type = 2)
OR
(case.case_num = lead.case_num AND lead.rec_type = 1)
for xml auto, elements
This is bringing back results like
<case>
<case_num>1</case_num>
<date>1/1/2013</date>
<product>
<product_name>name</product_name>
<product_price>1.00</product_price>
<lead>
<sales_lead_name>bob smith</sales_lead_name>
<sales_lead_address>address 1</sales_lead_address>
</lead>
</product>
<product>
<product_name>name2</product_name>
<product_price>2.00</product_price>
<lead>
<sales_lead_name>bob smith</sales_lead_name>
<sales_lead_address>address 1</sales_lead_address>
</lead>
</product>
</case>
I don't want the name repeating for every product. With multiple products and multiple leads, how do I format the SQL so it doesn't make sort of a Cartesian product in my results?
I made another example to illustrate my problem. SQL Fiddle example
This is making a cartesian result, matching all parts to all persons. I want to have one case then each part then each person, then close case.
I was trying DISTINCT and getting errors. I thought about UNION to tie two together, but I don't think I can do that within a bigger select for my case.
What I’m getting:
CASE_NUM DATE PART_NAME PART_PRICE PERSON_NAME COMPANY
1 2013-01-01 stapler 1.00 bob smith acme supplies
1 2013-01-01 matches 2.00 bob smith acme supplies
1 2013-01-01 stapler 1.00 john doe john supply inc
1 2013-01-01 matches 2.00 john doe john supply inc
What I want:
CASE_NUM DATE PART_NAME PART_PRICE PERSON_NAME COMPANY
1 2013-01-01 bob smith acme supplies
1 2013-01-01 john doe john supply inc
1 2013-01-01 matches 2.00
1 2013-01-01 stapler 1.00
As #marc_s points out, you create your Cartesian product yourself by 'joining' the tables the way you do. Always try to use JOIN instead.
I believe the following query would fit you needs:
select
[case].case_num
,[case].date
,lead.col_a as sales_lead_name
,lead.col_b as sales_lead_address
,product.col_a as product_name
,product.col_b as product_price
from
table_1 [case]
JOIN table_2 lead ON [case].case_num = lead.case_num
AND lead.rec_type = 1
JOIN table_2 product ON [case].case_num = product.case_num
AND product.rec_type = 2
FOR XML auto, elements;
You can view it on SQLFiddle.com
The output will look like this:
<case>
<case_num>1</case_num>
<date>2013-01-01</date>
<lead>
<sales_lead_name>bob smith</sales_lead_name>
<sales_lead_address>address 1</sales_lead_address>
<product>
<product_name>name</product_name>
<product_price>1.00</product_price>
</product>
<product>
<product_name>name2</product_name>
<product_price>2.00</product_price>
</product>
</lead>
</case>
A friend suggested only joining once, then filtering the select based on case statements and I think this is going to work. Thanks folks
select case_num = case
when child.rec_type = '1' then mast.case_num
when child.rec_type = '2' then mast.case_num
else '' end
,mast_date = case
when child.rec_type = '1' then mast.date
when child.rec_type = '2' then mast.date
else '' end
,child.rec_type
,part_name = case when child.rec_type = '1' then child.col_a else '' end
,part_price = case when child.rec_type = '1' then child.col_b else '' end
,subject_name = case when child.rec_type = '2' then child.col_a else '' end
,subject_type = case when child.rec_type = '2' then child.col_b else '' end
from table_master mast
join table_child child on mast.case_num = child.case_num
--for xml auto, elements;
Since no one has answered the question I have done something similar in the past I cant exactly remember how I did it but I will give you something to play with its really hard to guess things when you dont have data available , as far as I remember I did something like this to get the format you are after and it was on SQL Server 2005 so it should work for you
select case.case_num, case.date,
(SELECT col_a [#productname]
,col_b [#productprice]
FROM table_2 t2
WHERE t2.case_num = case.case_num
FOR XML PATH('Details'), TYPE)
from table_1 case
FOR XML PATH('Case'), ROOT('Cases')

Sql Server Query To Group By Name

I have the following data from 2 tables Notes (left) and scans (right) :
Imagine the picker and packers were all varying, like you can have JOHN, JANE etc.
I need a query that outputs like so :
On a given date range :
Name - Picked (units) - Packed (units)
MASI - 15 - 21
JOHN - 21 - 32
etc.
I can't figure out how to even start this, any tips will be helpful thanks.
Without a "worker" take that lists each Picker/Packer individually, I think you'd need something like this...
SELECT
CASE WHEN action.name = 'Picker' THEN scans.Picker ELSE scans.Packer END AS worker,
SUM(CASE WHEN action.name = 'Picker' THEN notes.Units ELSE 0 END) AS PickedUnits,
SUM(CASE WHEN action.name = 'Packer' THEN notes.Units ELSE 0 END) AS PackedUnits
FROM
notes
INNER JOIN
scans
ON scans.PickNote = notes.Number
CROSS JOIN
(
SELECT 'Picker' AS name
UNION ALL SELECT 'Packer' AS name
)
AS action
GROUP BY
CASE WHEN action.name = 'Picker' THEN scans.Picker ELSE scans.Packer END
(This is actually just an algebraic re-arrangement of the answer that #RaphaëlAlthaus posted at the same time as me. Both use UNION to work out the Picker values and the Packer values separately. If you have separate indexes on scans.Picker and scans.Packer then I would expect mine MAY be slowest. If you don't have those two indexes then I would expect mine to be fastest. I recommend creating the indexes and testing on a realtisic data set.)
EDIT
Actually, what I would recommend is a change to scans table completely; normalise it.
Your de-normalised set has one row per PickNote, with fields picker and packer.
A normalised set would have two rows per PickNote with fields role and worker.
id | PickNote | Role | Worker
------+----------+------+--------
01 | PK162675 | Pick | MASI
02 | PK162675 | Pack | MASI
03 | PK162676 | Pick | FRED
04 | PK162676 | Pack | JOHN
This allows you to create simple indexes and simple queries.
You may initially baulk at the extra unecessary rows, but it will yield simpler queries, faster queries, better maintainability, increased flexibility, etc, etc.
In short, this normalisation may cost a little extra space, but it pays back dividends forever.
SELECT name, SUM(nbPicked) Picked, SUM(nbPacked) Packed
FROM
(SELECT n.Picker name, SUM(n.Units) nbPicked, 0 nbPacked
FROM Notes n
INNER JOIN scans s ON s.PickNote = n.Number
--WHERE s.ProcessedOn BETWEEN x and y
GROUP BY n.Picker
UNION ALL
SELECT n.Packer name, 0 nbPicked, SUM(n.Units) nbPacked
FROM Notes n
INNER JOIN scans s ON s.PickNote= n.Number
--WHERE s.ProcessedOn BETWEEN x and y
GROUP BY n.Packer)
GROUP BY name;

SQL Select Certain Value First

SELECT AssetValue
FROM Assets
WHERE (AssetType = 'Country')
Very simple Select Statement, but it outputs
Afghanistan
Albania
Algeria
....
How do I make United States and Canada appear on the top of this? Is this possible with SQL or do I need to do something in my ASP code? My PK is a INT Identity.
SELECT AssetValue
FROM Assets
WHERE (AssetType = 'Country')
ORDER BY CASE AssetValue
WHEN 'US' THEN 1
WHEN 'Canada' THEN 2 ELSE 3 END, AssetValue
If you do not wish to hard code, the most generic solution is having an extra field to indicate the preferential items. You would call it OrderPreference. The higher OrderPreference, the first the item would appear. Every item else would have OrderPreference equals ZERO. The query:
SELECT AssetValue
FROM Assets
WHERE (AssetType = 'Country')
ORDER BY OrderPreference desc, AssetValue
By the looks of it, Assets is a table you use for other things (not only countries), so you could use this approach to solve the same problem for others asset types, if they come up.
Give them a a group ID, so group 1 is US, UK etc, group2 is the rest of the world, then you can do "priority" countries.