SQL select output to XML - sql

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')

Related

Select columns value based on logic applied on the rows

It is a self-join I need, but I'm having difficulty with this problem and I hope someone can help me.
I have a table with MAT_CODE, MATERIAL and VENDOR and I am trying to generate a new column with NEW_MATCODE as per the below scenario.
Sample Data :
NEW_MATCODE MAT_CODE MATERIAL VENDOR WIN_VENDOR
X-043223065 GP002134 GP002134
3065 X-043223065 USD005 P10011
3065 3065 X-043223065 EUR003 P10011
4567 4567 X-023065 UD00005 UD00005
4567 X-023065 DF00388 UD00005
4321 X-04065 P24005 P24005
4321 4321 X-04065 D41111 P24005
4321 X-04065 D46732 P24005
X-0432065 US7800 D0230005
X-0432065 EUR234 D067805
123 123 X-04322 P0008 P0008
123 1234 X-04322 EU0323 P0008
123 1262 X-04322 EUR0032 P0008
2345 2345 X-04322 DFGH322 P12008
123456 123456 X-04322 EUR00323 P12008
1113 1113 X-04322 EUR0032 P12008
Logic for 1,2 and 3 sets of data:
Pick up the MATERIAL AND WIN_VENDOR combination and get the unique MAT_CODE and apply it across all MATERIAL- WIN_VENDOR combinations as the NEW_MATCODE
Logic for 4th set :
If no combination for MAT_CODE exists then leave it as-is
Logic for 5th set:
When different MAT_CODE exists for the same MATERIAL and WIN_VENDOR combination, apply NEW_MATCODE as the MAT_CODE from MATERIAL - VENDOR where VENDOR = WIN_VENDOR
Logic for 6th set:
When different MAT_CODE exists for the same MATERIAL and WIN_VENDOR combination, and VENDOR <> WIN_VENDOR leave MAT_CODE as-is.
Hope it is clear. Any help would be appreciated.
Thanks.
I think the following query will get you most of the way to what you are looking for:
SELECT mat_code, material, vendor, win_vendor,
CASE
WHEN COUNT(DISTINCT mat_code) OVER (PARTITION BY material, win_vendor) = 0 THEN mat_code
WHEN COUNT(DISTINCT mat_code) OVER (PARTITION BY material, win_vendor) = 1 THEN MAX(mat_code) OVER (PARTITION BY material, win_vendor)
ELSE NVL((SELECT sub.mat_code FROM material_info sub WHERE sub.material = mi.material AND sub.vendor = sub.win_vendor), mi.mat_code)
END AS NEW_MAT
FROM material_info mi;
The case statement is making use of the analytical functions to handle cases 1-4. The else branch is attempting to grab Case 5 and if it isn't found defaulting to Case 6.

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

Using a field to filter a selection on a second field in SQL Server

I have a table ClientContacts, which holds basic information about a pairing of clients. Some of the details held in this table include P1Title, P2Title, P1FirstName, P2FirstName. For each row in this table there may be details of one or two clients, with a CustomerId that represents the pairing. Within this table is also ContactId, which is used to link to the table described below.
In a second table ContactDetails which contains rows that hold a specific contact detail that is associated with a client. Each client may have a number of rows in this table, each row holding a different detail such as HomeNumber, MobileNumber and Email. This table also contains a Type field which represents the type of contact detail held in the row. 1 = Home number, 2 = Mobile number and 3 = email. The Note field is also included, which may hold either Mr or Mrs denoting whether the mobile number held belongs to Person1 or Person2 in the client pairing.
Here is a visual structure of the tables.
ClientContacts
CustomerId ContactId Person1Title Person1FirstName Person1LastName Person2Title Person2FirstName Person2LastName
1 100 Mr Bob BobLastname Mrs Bobette BobetteLastname
2 101 Mr John JohnLastname Mrs Johnette JohnetteLastname
ContactDetails
ContactId Detail Type Note
100 012345 1
100 077777 2 P1
100 012333 1
100 088888 2 P2
101 099999 1
101 012211 1
101 066666 2
101 email#email.com 3
I want to construct a query that allows me to pull back the information of both of the clients, as well as figure out whether any of the mobile numbers stored in the ContactDetails table belongs to either of the two clients, if it does, I need to be able to tell which belongs to Person1 or Person2 in the pairing.
In addition, if the note field is null for a particular mobile number (type = 2), the first mobile number should be used for Person1 and the second should be used for Person2.
Below is my desired output:
Output
CustomerId Person1Firstname
Person1Lastname Person2Firstname Person2Lastname Home Person1Mobile Person2Mobile Person2Email
1 Bob BobLastname Bobette BobetteLastname 012211 077777 088888 null
I have a partially working query that manages to extract the mobile numbers and relates them to P1 or P2, however this only works if the Note field is not null.
select
cc.CustomerId,
cc.Person1Forename,
cc.Person1Surname,
cc.Person2Forename,
cc.Person2Surname,
max(case when cd.Type = 3 then cd.Detail end) as 'Home',
max(case when cd.Type = 4 and cd.Note = cc.P1Title then cd.Detail end) as 'Person1Mobile',
max(case when cd.Type = 4 and cd.Note = cc.P2Title then cd.Detail end) as 'Person2Mobile',
max(case when cd.Type = 5 then cd.Detail end) as 'Email'
from ClientContacts cc join
ContactDetails
cd on cc.ContactId = cd.ContactId
I'm unsure how to proceed from here. Any help would be appreciated.

select from table A when condition is met in table B

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

SQL Views - Modify Returned Result

I'm a little stuck here. I'm trying to modify a returned View based on a condition. I'm fairly green on SQL and am having a bit of difficultly with the returned result. Heres a partial component of the view I wrote:
WITH A AS (
SELECT
ROW_NUMBER() OVER (PARTITION BY fkidContract,fkidTemplateItem ORDER BY bStdActive DESC, dtdateplanned ASC) AS RANK,
tblWorkItems.fkidContract AS ContractNo,
....
FROM tblWorkItems
WHERE fkidTemplateItem IN
(2895,2905,2915,2907,2908,
2909,3047,2930,2923,2969,
2968,2919,2935,2936,2927,
2970,2979)
AND ...
)
SELECT * FROM A WHERE RANK = 1
The return result is similar to the following:
ContractNo| ItemNumber | Planned | Complete
001 | 100 | 01/01/1900 | 02/01/1900
001 | 101 | 03/04/1900 | 02/01/1901
001 | 102 | 03/06/1901 | 02/08/1900
002 | 100 | 01/03/1911 | 02/08/1913
This gives me the results I expect, but due a nightmare crystal report I need to alter this view slightly. I want to take this returned result set and modify an existing column with a value pulled from the same table and the same Contract relationship, something like the following:
UPDATE A
SET A.Completed = ( SELECT R.Completed
FROM myTable R
INNER JOIN A
ON A.ContractNo = R.ContractNo
WHERE A.ItemNumber = 100 AND R.ItemNumber = 101
)
What I'm trying to do is modify the "Completed Date" of one task and make it the complete date of another task if they both share the same ContractNo field value.
I'm not sure about the ItemNumber relationships between A and R (perhaps it was just for testing...), but it seems like you don't really want to UPDATE anything, but you want to use a different value under some circumstances. So, maybe you just want to change the non-cte part of your query to something like:
SELECT A.ContractNo, A.ItemNumber, A.Planned,
COALESCE(R.Completed,A.Completed) as Completed
FROM A
LEFT OUTER JOIN myTable R
ON A.ContractNo = R.ContractNo
AND A.ItemNumber = 100 AND R.ItemNumber = 101 -- I'm not sure about this part
WHERE A.Rank = 1
So it turns out that actually reading the vendor documentation helps :)
SELECT
column1,
column2 =
case
when date > 1999 then 'some value'
when date < 1999 then 'other value'
else 'back to the future'
end
FROM ....
For reference, the total query did a triple inner join over ~5 million records and this case statement was surprisingly performant.
I suggest that this gets closed as a duplicate.