SQL Pivot with String - sql

I have two tables in SQL Server: Customer and Address
Customer Table:
CustomerID FirstName LastName
----------- ---------- ----------
1 Andrew Jackson
2 George Washington
Address Table:
AddressID CustomerID AddressType City
----------- ----------- ----------- ----------
1 1 Home Waxhaw
2 1 Office Nashville
3 2 Home Philadelphia
This is the output that I need:
CustomerID Firstname HomeCity OfficeCity
----------- ---------- ---------- ----------
1 Andrew Waxhaw Nashville
2 George Philadelphia Null
This is my query, but not getting the right result:
SELECT CustomerID, Firstname, HOme as HomeCity, Office as OfficeCity FROM
(SELECT C.CustomerID, C.FirstName, A.AddressID, A.AddressType, A.City
FROM Customer C, Address A
WHERE C.CustomerID = A.CustomerID)as P
PIVOT (MAX(city) FOR AddressType in ([Home],[Office])) as PVT
This is the result that I am getting:
CustomerID Firstname HomeCity OfficeCity
----------- ---------- ---------- ----------
1 Andrew Waxhaw NULL
1 Andrew NULL Nashville
2 George Philadelphia Null
As you can see Customer 1 is showing up twice in the final result. Is it possible to get only one row per customer?
I looked up this example, but didn't help:http://stackoverflow.com/questions/6267660/sql-query-to-convert-rows-into-columns
Thanks

It is giving this row because you have AddressID in the select list for you subquery "P". So even though you don't have AddressID in you top level select this, the PIVOT function is still grouping by it. You need to change this to:
SELECT CustomerID, Firstname, Home as HomeCity, Office as OfficeCity
FROM ( SELECT C.CustomerID, C.FirstName, A.AddressType, A.City
FROM #Customer C, #Address A
WHERE C.CustomerID = A.CustomerID
) AS P
PIVOT
( MAX(city)
FOR AddressType in ([Home],[Office])
) AS PVT
Although I would be inclined to use an explicit INNER JOIN rather than an implicit join between customer and Address.

I would write it like this instead:
SELECT C.CustomerID, C.Firstname,
Home.City as HomeCity,
Office.City as OfficeCity
FROM Customer C
LEFT JOIN Address Home
on Home.CustomerID = C.CustomerID and Home.AddressType = 'Home'
LEFT JOIN Address Office
on Office.CustomerID = C.CustomerID and Office.AddressType = 'Office'

Related

Display SQL results in rows instead of a single line

I have the following tables:
Table Offices:
OfficeID MainAddId SubAddId1 SubAddId2 PortAddId1
------- -------- -------- -------- --------
2 1 2 3 5
Table Address:
AddressID Street City ZipCode State
------- -------- -------- -------- --------
1 Forest Ave New York 10001 New York
2 Morris St Philadelphia 19019 Pennsylvania
3 David St Raleigh 27513 North Carolina
Table Port:
PortID PortName Street City ZipCode State
------- ------- -------- -------- -------- --------
5 New York Harbour Bay St New York 10001 New York
I want to write an SQL request such that if any of the addresses Id in the office table is not null,
it will return the list of addresses in a list like:
AddressID Street City ZipCode State
------- -------- -------- -------- --------
1 Forest Ave New York 10001 New York
2 Morris St Philadelphia 19019 Pennsylvania
3 David St Raleigh 27513 North Carolina
5 Bay St New York 10001 New York
Any help on how I can do this please? Thanks
Here is what I tried (partly cause it's not working):
select *
from Office offi
left join Address add1 on offi.MainAddId = add1.AddressID
left join Address add2 on offi.SubAddId1= add2.AddressID
where offi.OfficeID = 2;
However, this is returning the addresses on a single line.
Don't use multiple joins if you really need them. Instead of that, you can use OR after ON in join.
I solved using UNION:
select a.AddressID, a.street, a.city, a.zipcode, a.state
from offices o
inner join address a on (o.MainAddid = a.addressId or o.subaddid1 = a.addressid or o.subaddid2 = a.addressid)
union
select p.PortId, p.street, p.city, p.zipcode, p.state
from offices o
inner join port p on p.portId = o.PortAddId1
Check demo on DB<>FIDDLE
Try this one query :
SELECT *
FROM ( SELECT AddressID, Street, City, ZipCode, State
FROM address
UNION
SELECT PortID, Street, City, ZipCode, State
FROM Port) w
WHERE addressid IN ( SELECT Addresses
FROM ( SELECT OfficeID, MainAddId, SubAddId1, SubAddId2, PortAddId1
FROM Offices) p
UNPIVOT ( Addresses
FOR Offices IN (MainAddId, SubAddId1, SubAddId2, PortAddId1)) AS unpvt );

SQL Server: finding duplicates between two parameters in different tables

I am trying to find duplicates in SQL Server where customers with the same forename, surname, and mobile number match. The thing is they are in different tables.
custid forename surname dateofbirth
-----------------------------------
1 David John 16-09-1985
2 David Jon 16-09-1985
3 Sarah Smith 10-08-2015
4 Peter Proca 11-06-2011
5 Peter Broca 11-06-2011
addid custid line1
-------------------------
1 1 0504135846
2 2 0504135846
3 3 0506523145
4 4 0503698521
5 5 0503698521
I am currently able to find duplicates by forename and surname, but if I want to find based on mobile numbers how can I bring it in?
select c.*
from
(select
c.*,
count(*) over (partition by left(surname, 3)) as cnt
from
customers c) c
order by
surname;
Use join:
select c.*
from (select c.*, t2.line1
count(*) over (partition by surname, forename, line1) as cnt
from customers c join
table2 t2
on t2.custid = c.custid
) c
order by surname;
Here you go, just JOIN on the table. Using HAVING might simplify your query as well
SELECT COUNT(*), c.forename, c.surname, mn.line1
FROM customers c
INNER JOIN mobilenumber mn ON c.custid=mn.custid
GROUP BY c.forename, c.surname, mn.line1
HAVING COUNT(*)>1
Also, you might need to LEFT JOIN if there is a chance that some records wont be in the mobilenumbers table.

Concatenating multiple results of a query in one row in Oracle

I have 2 tables with one having a reference to the first by id
first table for example is customer having the fields
id firstname lastname
------ --------- ---------
1 john smith
2 jessica james
the second table for example is product having the fields
id customer_id product descr
------- ----------- --------- ------
1 1 ts Shirt
2 1 ti Tie
3 2 sk skrit
I need a query that will output the following
customer.firstname customer.lastname product_and_desc
------------------ ------------------ ---------------------
john smith ts-Shirt , ti-Tie
jessica james sk-skirt
with the product rows variable for each customer.
I appreciate you help :)
thanks,
You can use list_agg(). In your case:
select c.firstname, c.lastname,
list_agg(p.product||'-'||p.desc, ' , ') within group (order by p.id) as product_and_desc
from customer c join
product p
on c.id = p.customer_id
group by c.firstname, c.lastname;
I would suggest, though, that the second argument to list_agg() be ', ' rather than ' , '. The space before the comma looks a bit unusual.
select first_name,last_name,wm_concat(product||'-'||descr) as product_and_descr
from tbl1 ,tbl2 where tbl1.id=tbl2.customer_id
group by first_name,last_name;

Cross-multiplying table

I have this SQL code and I want to show the sum of each item on its charge slip and on their receipt:
select item_description, sum(receipt_qty) as Samp1, sum(chargeSlip_qty) as Samp2
from Items inner join Receipt_Detail on (Receipt_Detail.item_number =
Items.item_number)
inner join ChargeSlip_Detail on (ChargeSlip_Detail.item_number =
Items.item_number)
group by item_description
It produces this output:
Acetazolamide 2681 1730
Ascorbic Acid 1512 651
Paracetamol 1370 742
Silk 576 952
But it should be:
Acetazolamide 383 173
Ascorbic Acid 216 93
Paracetamol 274 106
Silk 96 238
What's wrong with my code?
Since you are joining tables, you might have a one-to-many relationship that is causing the problem when you then get the sum(). So you can use subqueries to get the result. This will get the sum() for the receipt and chargeslip for each item_number and then you join that back to your items table to get the final result:
select i.item_description,
r.Samp1,
c.Samp2
from Items i
inner join
(
select sum(receipt_qty) Samp1,
item_number
from Receipt_Detail
group by item_number
) r
on r.item_number = i.item_number
inner join
(
select sum(chargeSlip_qty) Samp2,
item_number
from ChargeSlip_Detail
group by item_number
) c
on c.item_number = i.item_number
Do the GROUP BYs first, per Item_Number, so you don't multiply out rows from Receipt_Detail and ChargeSlip_Detail. That is, you generate the SUM values per Item_Number before JOINing back to Items
select
I.item_description,
R.Samp1,
C.Samp2
from
Items I
inner join
(SELECT item_number, sum(receipt_qty) as Samp1
FROM Receipt_Detail
GROUP BY item_number
) R
on (R.item_number = I.item_number)
inner join
(SELECT item_number, sum(chargeSlip_qty) as Samp2
FROM ChargeSlip_Detail
GROUP BY item_number
) C
on (C.item_number = I.item_number)
A left join returns rows from the left table, and for each row in the left table, all matching rows in the right table.
So for example:
create table Customers (name varchar(50));
insert Customers values
('Tim'),
('John'),
('Spike');
create table Orders (customer_name varchar(50), product varchar(50));
insert Orders values (
('Tim', 'Guitar'),
('John', 'Drums'),
('John', 'Trumpet');
create table Addresses (customer_name varchar(50), address varchar(50));
insert Addresses values (
('Tim', 'Penny Lane 1'),
('John', 'Abbey Road 1'),
('John', 'Abbey Road 2');
Then if you run:
select c.name
, count(o.product) as Products
, count(a.address) as Addresses
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
group by name
You get:
name Products Addresses
Tim 1 1
John 4 4
Spike 0 0
But John doesn't have 4 products!
If you run without the group by, you can see why the counts are off:
select *
from Customers c
left join Orders o on o.customer_name = c.name
left join Addresses a on a.customer_name = c.name
You get:
name customer_name product customer_name address
Tim Tim Guitar Tim Penny Lane 1
John John Drums John Abbey Road 1
John John Drums John Abbey Road 2
John John Trumpet John Abbey Road 1
John John Trumpet John Abbey Road 2
Spike NULL NULL NULL NULL
As you can see, the joins end up repeating each other. For each product, the list of addresses is repeated. That gives you the wrong counts. To solve this problem, use one of the excellent other answers:
select c.name
, o.order_count
, a.address_count
from Customers c
left join
(
select customer_name
, count(*) as order_count
from Orders
group by
customer_name
) o
on o.customer_name = c.name
left join
(
select customer_name
, count(*) as address_count
from Addresses
group by
customer_name
) a
on a.customer_name = c.name
The subqueries ensure only one row is joined per customer. The result is much better:
name order_count address_count
Tim 1 1
John 2 2
Spike NULL NULL

How to Join three tables

I have 3 tables : Orders, Customers and Suppliers
Structure and Data:
Orders
================================
OrdNo, OrdDt, OrdType, CSID
--------------------------------
1 01/04/2011 C 2
2 01/04/2011 S 1
--------------------------------
Customers
----------
CID, CName
----------
1 John
2 Boby
----------
Suppliers
=========
SID, SName
----------
1 Tony
2 Mohan
----------
If OrdTYpe = "C" then pick data from Customers table for CSID = CID
If OrdTYpe = "S" then pick data from Suppliers table for CSID = SID
How to list the records like
--------------------------------------
OrdNo, OrdDt, OrdType, CSID CSName
--------------------------------------
1 01/04/2011 C 2 Boby
2 01/04/2011 S 1 Tony
--------------------------------------
Does this get what you want?
SELECT ordno, orddt, ordtype, csid,
COALESCE( c.name, s.name ) csname
FROM orders o
LEFT JOIN customer c ON o.ordtype='C' AND c.cid = o.csid
LEFT JOIN suppliers s ON o.ordtype='S' AND c.sid = o.csid
Another possibility, at least in Oracle, would be:
SELECT ordno, orddt, ordtype, csid,
CASE WHEN ordtype='C' THEN (SELECT name FROM customer WHERE cid = csid)
WHEN ordtype='S' THEN (SELECT name FROM suppliers WHERE sid = csid)
END csname
FROM orders;
Martin has a good point. I'm no pro, but I would suggest making an intermediate table that will allow you to keep customers and suppliers separate, yet still pull from a common set of id's
Orders
========================
OrdNo, OrdDt, CSID
------------------------
1 01/04/2011 2
2 01/04/2011 1
-------------------------
ClientIDs
---
ID
---
1
2
3
4
---
Customers
----------
CID, CName
----------
1 John
3 Boby
----------
Suppliers
=========
SID, SName
----------
2 Tony
4 Mohan
----------
That way you end up with something like this:
-------------------------------
OrdNo, OrdDt, CSID CSName
-------------------------------
1 01/04/2011 2 Tony
2 01/04/2011 1 John
-------------------------------
and by back tracking up through the (now) unique ID you will be able to tell if it is a customer, or supplier... also I'm pretty sure your SQL will run faster with this route (don't hold me to it though). If you like this idea, I could look into SQL to back it.
SELECT o.[OrdNo], o.[OrdDt], o.[OrdType], o.[CSID],
CASE WHEN o.[OrdType] = 'C'
THEN c.[CName]
ELSE s.[SName]
END as [CSName]
FROM Orders AS o
LEFT JOIN Customers AS c
ON o.[CSID] = c.[CID]
LEFT JOIN Suppliers AS s
ON o.[CSID] = s.[SID]