Transforming Data in SQL Server - sql-server

I am new here so please bear with me if this type of question has already been asked, and I am going to try and be as clear as possible. With that said, I am at whits end trying to transform the data table below into the format required. All ideas are helpful.
PhType PhNumber Sequence
-------------------------------------
Cell Phone 111-222-3333 2
Cell Phone 222-333-4444 5
Home Phone 999-222-1111 6
Home Phone 555-444-3333 8
And I am trying to transform the data into this table.
**CellPhone1#** **Sequence** **CellPhone2#** **Sequence** **HomePhone1#** **Sequence** **HomePhone2#** **Sequence**
111-222-3333 ---- 2 ------ 222-333-4444 ----- 5 --------- 999-222-1111 ----- 6 ------- 555-444-3333 ---------- 8
The idea is turning rows of data into columns, but because the sequences on the numbers aren't in sequential order I am having a hard time doing this without gaps in my data. Currently I am transforming the information into column on the join level
LEFT OUTER JOIN
Reports.dbo.BorrowerTelephones BT with(nolock) ON B1.HHNbr = BT.HHNbr
AND B1.NamSeq = BT.NamSeq
AND BT.Seq = 0
AND BT.PhType = 'Cell Phone'
LEFT OUTER JOIN
Reports.dbo.BorrowerTelephones BT1 with(nolock) ON B1.HHNbr = BT1.HHNbr
AND B1.NamSeq = BT1.NamSeq
AND BT1.Seq = 1
AND BT1.PhType = 'Cell Phone'
However if there is not a phone number in sequence 0 or 1 of the cell phone phtype it will leave those columns blank. I need the query to take the sequences out of the equation altogether without repeating the phone number in the next column over. Please help.
Thanks,
Mitch

So, as I understand it, you're just wanting to collapse the data in your table down to a single row, correct? I have written a SQL query that tests what I think you're wanting using ROW_NUMBER() to handle the gaps in your sequence numbers. I hope this gives you an idea of what you'll need.
CREATE TABLE #testPhones (
PhType VARCHAR(20),
PhNumber VARCHAR(12),
PhSequence INT)
INSERT INTO #testPhones(PhType, PhNumber, PhSequence)
VALUES('Cell Phone', '111-222-3333', 3),
('Cell Phone', '222-333-4444', 5),
('Home Phone', '999-222-1111', 6),
('Home Phone', '555-444-3333', 8)
SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS Row, *
FROM #testPhones
SELECT A.PhNumber AS CellPhone1#, A.PhSequence AS PhSequence,
B.PhNumber AS CellPhone2#, B.PhSequence AS PhSequence,
C.PhNumber AS HomePhone1#, C.PhSequence AS PhSequence,
D.PhNumber AS HomePhone2#, D.PhSequence AS PhSequence
FROM (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS A WHERE RowNo = 1) AS A
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS B WHERE RowNo = 2) AS B ON 1=1
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS C WHERE RowNo = 3) AS C ON 1=1
LEFT OUTER JOIN (SELECT PhNumber, PhSequence FROM (SELECT ROW_NUMBER() OVER(ORDER BY PhSequence) AS RowNo, * FROM #testPhones) AS D WHERE RowNo = 4) AS D ON 1=1
DROP TABLE #testPhones
This outputs the two following tables:
Output Tables

Related

MSSQL Union All two queries with if statement

I have a query the following works as expected
If((Select count(*) from table1 where product = 'carrot')< 5)
Begin
Select Top (5 - (Select count(*) from table1 where product = 'carrot'))
id, product From table2
WHere id NOT IN
(Select id from table1) AND product = 'carrot'
Order by newid()
END
What i want to do is Union or Union all say another product potatoes
If((Select count(*) from table1 where product = 'potato')< 5)
Begin
Select Top (5 - (Select count(*) from table1 where product = 'potato'))
id, product From table2
WHere id NOT IN
(Select id from table1) AND product = 'potato'
Order by newid()
END
I keep getting a syntax error, when i add UNION between IF or after END. Is this possible or another way is better....
What i am doing is trying to select a random sample of carrots, first i want to check if i have the 5 carrots in table1. if i do don't run sample.
If i do not have 5 total carrots run the sampler and return 5 carrots. I then filter out if they already exist in table 1 by the id. Then it subtracts the count from the new sample for a total of five.
It works well, now i want to run for other products eg lettuce, potatoes etc...
But i want an UNION or UNION All. hope makes sense.
I'd be interested to see whether this way works-
Select Top (5 - (Select count(*) from table1 where product = 'carrots')< 5)
id
, product
From table2
WHere id NOT IN (Select id from table2)
AND (Select count(*) from table1 where product = 'carrots')< 5)
UNION ALL
Select Top (5 - (Select count(*) from table1 where product = 'potatoes')< 5)
id
, product
From table2
WHere id NOT IN (Select id from table2)
AND (Select count(*) from table1 where product = 'potatoes')< 5)
Your style is interesting, feels procedural rather than set-based.
You can try it this way
If(((Select count(*) from table1 where product = 'carrot'< 5) and (Select count(*) from table1 where product ='potato' <5))
)
Begin
Select Top (5 - (Select count(*) from table1 where product = 'carrot')) id, product
From table2
WHere id NOT IN (Select id from table1) AND product = 'carrot' Order by newid()
Union all
Select Top (5 - (Select count(*) from table1 where product = 'potato')) id, product From table2
WHere id NOT IN (Select id from table1) AND product = 'potato' Order by newid()
END
IF statements in SQL do not behave as sub-queries or row-sets in SQL, as you've found out. They are for branching the flow of control only.
Here is a more set based approach you could take:
SELECT ProdSamples.*
FROM
(
SELECT Table2.*, ROW_NUMBER() OVER (PARTITION BY table2.Product ORDER BY NEWID()) RowNum
FROM Table2
LEFT JOIN Table1
ON Table1.id = Table2.id
WHERE Table1.id IS NULL
) ProdSamples
JOIN
(
SELECT Product, COUNT(*) ProdCount
FROM Table1
GROUP BY Product
) ProdCounts
ON ProdSamples.Product = ProdCounts.Product
AND ProdSamples.RowNum <= (5 - ProdCounts.ProdCount)
The first sub-query ProdSamples returns all the products from Table2 that do not have an id in Table1. The RowNum field ranks them in random order partitioned by Product.
The second sub-query ProdCounts is the count of records for each product in Table1. Then it joins these sub-queries together and only returns the records from ProdSamples where the RowNum is lower or equal to the number of samples you want to return.

SQL Simple Join with two tables, but one is random

I am stuck with this. I have a simple set-up with two tables. One table is holding emailaddresses one table is holding vouchercodes. I want to join them in a third table, so that each emailaddress has one random vouchercode.
Unfortunatly I am stuck with this as there are no identic Ids to match both values. What I have so far brings no result:
Select
A.Email
B.CouponCode
FROM Emailaddresses as A
JOIN CouponCodes as B
on A.Email = B.CouponCode
A hint would be great as search did not bring me any further yet.
Edit -
Table A (Addresses)
-------------------
Column A | Column B
-------------------------
email1#gmail.com True
email2#gmail.com
email3#gmail.com True
email4#gmail.com
Table B (Voucher)
-------------------
ABCD1234
ABCD5678
ABCD9876
ABCD5432
Table C
-------------------------
column A | column B
-------------------------
email1#gmail.com ABCD1234
email2#gmail.com ABCD5678
email3#gmail.com ABCD9876
email4#gmail.com ABCD5432
Sample Data:
While joining without proper keys is not a good solution, for your case you can try this. (note: not tested, just a quick suggestion)
;with cte_email as (
select row_number() over (order by Email) as rownum, Email
from Emailaddresses
)
;with cte_coupon as (
select row_number() over (order by CouponCode) as rownum, CouponCode
from CouponCodes
)
select a.Email,b.CouponCode
from cte_email a
join cte_coupon b
on a.rownum = b.rownum
You want to randomly join records, one email with one coupon each. So create random row numbers and join on these:
select
e.email,
c.couponcode
from (select t.*, row_number() over (order by newid()) as rn from emailaddresses t) e
join (select t.*, row_number() over (order by newid()) as rn from CouponCodes t) c
on c.rn = e.rn;
Give a row number for both the tables and join it with row number.
Query
;with cte as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_A]
),
cte2 as(
select [rn] = row_number() over(
order by [Column_A]
), *
from [Table_B]
)
select t1.[Column_A] as [Email_Id], t2.[Column_A] as [Coupon]
from cte t1
join cte2 t2
on t1.rn = t2.rn;
Find a demo here

Sql Filter table by two dates in order

I have been trying to filter one table by two dates with an order of importance (date2 > date1) as follows:
SELECT
t1.customer, t1.weights, t1.max(t1.date1) as date1, t1.date2
FROM
(SELECT *
FROM table
WHERE CAST(date2 AS smalldatetime) = '10/29/2017') t2
INNER JOIN
table t1 ON t1.customer = t2.customer
AND t1.date2 = t2.date2
GROUP BY
t1.customer, t1.date2
ORDER BY
t1.customer;
It filters the table correctly by date2 first, the max(t1.date1) doesn't what I want it to do though. I get duplicate customers, that share the same (and correct) date2, but show different date1's. These duplicate records have the following in common: The weight row is different. What would I need to do to output just the the customer records connected to the most current date1 without taking other columns into consideration?
I am still a noob, help would be greatly appreciated!
Solution for t-sql (all based on the accepted answer):
SELECT * FROM (
SELECT row_number() over(partition by t1.customer order by t1.date1 desc) as rownum, t1.customer, t1.weights, t1.date1 , t1.date2
FROM
(SELECT *
FROM table
WHERE CAST(date2 AS smalldatetime) = '10/29/2017') t2
INNER JOIN
table t1 ON t1.customer = t2.customer
AND t1.date2 = t2.date2
)t3
where rownum = 1;
If I understood correctly, then instead of a group by logic, I would just use a qualify row statement :)
Try the code below and tell me if it's what you needed - what I'm telling it to do is to bring back only one row per customer ID....but where we select the row based on the dates (by sorting them in ascending order) - however, I'm unclear of what you mean by importance of the 2 dates so I may be completely off base here...can you please give an example of input and desired output?
SELECT t1.customer, t1.weights, t1.date1, t1.date2
FROM
(
Select *
FROM table
WHERE Cast(date2 as smalldatetime)='10/29/2017'
) t2
Inner Join table t1
ON t1.customer = t2.customer
AND t1.date2 = t2.date2
Qualify row_number() over(partition by t1.customer order by date2 , date1)=1
Order By t1.customer;

Subtract top two rows from one column using one id

does anyone know how can I subtract top two rows from one column only using one id? Here's my sample query:
SELECT top 2 a.consumption,
coalesce(a.consumption -
(SELECT b.consumption
FROM tbl_t_billing b
WHERE b.id = a.id + 1), a.consumption) AS diff
FROM tbl_t_billing a
WHERE a.customerId = '5'
ORDER BY a.dateCreated DESC
I want to know how to get the difference between the top 2 rows using one id from the consumption column using the customerId #5. I've tried but I can't get the right query for that. Can somebody help me please? Thanks!
try this:
;with cte as
(
select consumption, customerId,
row_number() over (partiton by customerid order by datecreated desc) rn
from tbl_t_billing where customerId = '5'
)
select a.customerId, a.consumption,
coalesce((a.consumption - b.consumption), a.consumption) consumption_diff
from cte a left outer join cte b on a.rn + 1 = b.rn
where a.rn = 1
declare #tbl_t_billing table(consumption int, customerId int, datecreated datetime)
insert into #tbl_t_billing
values
(10,5,'20100101'),
(7,5,'20000101'),
(9,4,'20100101'),
(5,4,'20000101'),
(8,3,'20100101'),
(3,3,'20000101'),
(7,2,'20100101'),
(3,2,'20000101'),
(4,1,'20100101'),
(2,1,'20000101')
-- get the difference between the last two consumption values for each customerId
select
customerId,
sum(consumption) diff
from(
select
customerId,
consumption *
case row_number() over(partition by customerId order by datecreated desc)
when 1 then 1 when 2 then -1
end consumption
from #tbl_t_billing
) t
group by customerId

SQL Server Full Text Search - Weighting Certain Columns Over Others

If I have the following full text search query:
SELECT *
FROM dbo.Product
INNER JOIN CONTAINSTABLE(Product, (Name, Description, ProductType), 'model') ct
ON ct.[Key] = Product.ProductID
Is it possible to weigh the columns that are being searched?
For example, I care more about the word model appearing in the Name column than I do the
Description or ProductType columns.
Of course if the word is in all 3 columns then I would expect it to rank higher than if it was just in the name column. Is there any way to have a row rank higher if it just appears in Name vs just in Description/ProductType?
You can do something like the following query. Here, WeightedRank is computed by multiplying the rank of the individual matches. NOTE: unfortunately I don't have Northwind installed so I couldn't test this, so look at it more like pseudocode and let me know if it doesn't work.
declare #searchTerm varchar(50) = 'model';
SELECT 100 * coalesce(ct1.RANK, 0) +
10 * coalesce(ct2.RANK, 0) +
1 * coalesce(ct3.RANK, 0) as WeightedRank,
*
FROM dbo.Product
LEFT JOIN
CONTAINSTABLE(Product, Name, #searchTerm) ct1 ON ct1.[Key] = Product.ProductID
LEFT JOIN
CONTAINSTABLE(Product, Description, #searchTerm) ct2 ON ct2.[Key] = Product.ProductID
LEFT JOIN
CONTAINSTABLE(Product, ProductType, #searchTerm) ct3 ON ct3.[Key] = Product.ProductID
order by WeightedRank desc
Listing 3-25. Sample Column Rank-Multiplier Search of Pro Full-Text Search in SQL Server 2008
SELECT *
FROM (
SELECT Commentary_ID
,SUM([Rank]) AS Rank
FROM (
SELECT bc.Commentary_ID
,c.[RANK] * 10 AS [Rank]
FROM FREETEXTTABLE(dbo.Contributor_Birth_Place, *, N'England') c
INNER JOIN dbo.Contributor_Book cb ON c.[KEY] = cb.Contributor_ID
INNER JOIN dbo.Book_Commentary bc ON cb.Book_ID = bc.Book_ID
UNION ALL
SELECT c.[KEY]
,c.[RANK] * 5
FROM FREETEXTTABLE(dbo.Commentary, Commentary, N'England') c
UNION ALL
SELECT ac.[KEY]
,ac.[RANK]
FROM FREETEXTTABLE(dbo.Commentary, Article_Content, N'England') ac
) s
GROUP BY Commentary_ID
) s1
INNER JOIN dbo.Commentary c1 ON c1.Commentary_ID = s1.Commentary_ID
ORDER BY [Rank] DESC;
Similar to Henry's solution but simplified, tested and using the details the question provided.
NB: I ran performance tests on both the union and left join styles and found the below to require far less logical reads on the union style below with my datasets YMMV.
declare #searchTerm varchar(50) = 'model';
declare #nameWeight int = 100;
declare #descriptionWeight int = 10;
declare #productTypeWeight int = 1;
SELECT ranksGroupedByProductID.*, outerProduct.*
FROM (SELECT [key],
Sum([rank]) AS WeightedRank
FROM (
-- Each column that needs to be weighted separately
-- should be added here and unioned with the other queries
SELECT [key],
[rank] * #nameWeight as [rank]
FROM Containstable(dbo.Product, [Name], #searchTerm)
UNION ALL
SELECT [key],
[rank] * #descriptionWeight as [rank]
FROM Containstable(dbo.Product, [Description], #searchTerm)
UNION ALL
SELECT [key],
[rank] * #productTypeWeight as [rank]
FROM Containstable(dbo.Product, [ProductType], #searchTerm)
) innerSearch
-- Grouping by key allows us to sum each ProductID's ranks for all the columns
GROUP BY [key]) ranksGroupedByProductID
-- This join is just to get the full Product table columns
-- and is optional if you only need the ordered ProductIDs
INNER JOIN dbo.Product outerProduct
ON outerProduct.ProductID = ranksGroupedByProductID.[key]
ORDER BY WeightedRank DESC;

Resources