SQL Server - multiple rows into one - sql

Hope you can help...
I have data table in this format (Lets refer this table as 'Product')
productid property_name property_value last_updated
p0001 type p1 05-Oct-2010
p0001 name Premium 05-Oct-2010
p0001 cost 172.00 05-Oct-2010
p0002 type p3 06-Oct-2010
p0002 name standard 06-Oct-2010
p0002 cost 13.00 06-Oct-2010
*(there are like 50 more properties of which i would need 15 atleast in my query.
However, i just ignore them for this example)*
I would need the data in this format:
productid type name cost
p0001 p1 Premium 172.00
p0002 p3 standard 13.00
I tried with a function and a view to get this format but it takes good few mins to get some 1000 records. Wonder if anyone knows quicker way?
What I tried:
Create function fun1(#productid nvarchar(50)) returns #retdetails table
(
type nvarchar(50) null,
name nvarchar(50) null,
cost nvarchar(50) null,
)
begin
declare
#type nvarchar(50),
#name nvarchar(50),
#cost nvarchar(50),
select #type=property_value from product where productid=#productid and property_name='type';
select #name=property_value from product where productid=#productid and property_name='name';
select #cost=property_value from product where productid=#productid and property_name='cost';
if isnull(#productid,'')<>''
begin
insert #retdetails
select #type, #name, #cost;
end;
return;
end;
then a view
select p.productid, pd.type, pd.name, pd.cost
from (select distinct productid from product) p
cross apply dbo.fun1(p.productid) pd
The slower response might be down to 'distinct' but without that I get duplicate records. I would appreciate any suggestion to get a quickier sql response.
Many Thanks

You could try this PIVOT approach
SELECT productid,
MAX(CASE WHEN property_name = 'type' THEN property_value END) AS type,
MAX(CASE WHEN property_name = 'name' THEN property_value END) AS name,
MAX(CASE WHEN property_name = 'cost' THEN property_value END) AS cost
FROM Product
GROUP BY productid

You could probably do this by joining the table back onto itself a couple of times, might be a bit of a performance issue though.

Starting from SQL Server 2005, you can use PIVOT operator:
DECLARE #TestData TABLE
(
productid VARCHAR(5) NOT NULL
,property_name VARCHAR(5) NOT NULL
,property_value VARCHAR(10)NOT NULL
,last_updated DATETIME NOT NULL
);
INSERT #TestData
SELECT 'p0001','type','p1' ,'05-Oct-2010'
UNION ALL
SELECT 'p0001','name','Premium' ,'05-Oct-2010'
UNION ALL
SELECT 'p0001','cost','172.00' ,'05-Oct-2010'
UNION ALL
SELECT 'p0002','type','p3' ,'06-Oct-2010'
UNION ALL
SELECT 'p0002','name','standard','06-Oct-2010'
UNION ALL
SELECT 'p0002','cost','13.00' ,'06-Oct-2010';
;WITH PivotSource
AS
(
SELECT a.productid
,a.property_name
,a.property_value
FROM #TestData a
)
SELECT pvt.*
--,CONVERT(NUMERIC(8,2), pvt.cost) NumericCost
FROM PivotSource src
PIVOT ( MAX(src.property_value) FOR src.property_name IN ([type], [name], [cost]) ) pvt
Results:
productid type name cost NumericCost
p0001 p1 Premium 172.00 172.00
p0002 p3 standard13.00 13.00

Related

Transpose ID, Field 'Name' Field 'Value' records from rows to columns in SQL Server

Before this question gets closed as a duplicate, I have seen many, many examples of 'SQL Dynamic Pivot Tables' on SO, but nothing I have tried so far has worked. Many of the examples that use a PIVOT expect a SUM or MAX value. I don't have any of these.
I have a single table SQL Server database.
The table holds products and associated properties. You could argue the properties should be split into a separate table, but I am stuck with what I have.. 1 table.
Example table (tblObject):
prodId
fieldName
fieldValue
ABC123
name
widget1
ABC123
description
Great widget!
ABC123
status
reserved
XYZ999
name
widget9
XYZ999
description
Lovely widget!
XYZ999
status
active
Each ProdId can have many fieldNames. My example table just shows name, description and status, but it could for expand to include colour, style, serial number etc. etc.
I need to report each prodId with all of the fieldName and fieldValue transposed, as below.
Desired Output:
prodId
prodName
name
description
status
createdDate
ABC123
widget1
widget1
Great widget!
reserved
2022-10-27
XYZ999
widget9
widget9
Lovely widget!
active
2022-10-27
I thought this would be extremely simple, but it is causing me quite a headache!
My current approach is below, but sadly is not functioning:
SELECT * FROM (
SELECT prodId, fieldName, fieldValue
FROM tblObjects
) Results
PIVOT (
fieldValue
FOR fieldName
IN (SELECT fieldName from tblObjects GROUP BY fieldName)
)
) AS PivotTable
I think you're not quite understanding how the aggregate functions affect the pivot values. They should only be grouped by the other values (in your case prodID).
Let's mock the data up:
DROP TABLE IF EXISTS #table;
CREATE TABLE #table (prodID NVARCHAR(6), fieldName NVARCHAR(20), fieldValue NVARCHAR(20));
INSERT INTO #table (prodId, fieldName, fieldValue) VALUES
('ABC123', 'name' , 'widget1'),
('ABC123', 'description' , 'Great widget!'),
('ABC123', 'status' , 'reserved'),
('XYZ999', 'name' , 'widget9'),
('XYZ999', 'description' , 'Lovely widget!'),
('XYZ999', 'status' , 'active');
I used a temp table here because table variables are harder to work with when it comes to dSQL.
Before we can do the pivot we need two things, the list of values from the column, and the SQL itself:
DECLARE #cmd NVARCHAR(MAX), #dpvt NVARCHAR(MAX);
SELECT #dpvt = STRING_AGG(fieldName,',')
FROM (SELECT DISTINCT fieldName FROM #table) a;
SET #cmd = '
SELECT prodID, name AS prodName, '+#dpvt+', CURRENT_TIMESTAMP AS createdDate
FROM #table t
PIVOT (
MAX(fieldValue) FOR fieldName IN ('+#dpvt+')
) p;';
With those sorted out we can execute sp_executeSQL and perform our dynamic pivot:
EXEC sys.sp_executesql #cmd;
prodID prodName name description status createdDate
-------------------------------------------------------------------
ABC123 widget1 widget1 Great widget! reserved 2022-11-01 15:34:36.430
XYZ999 widget9 widget9 Lovely widget! active 2022-11-01 15:34:36.430

SQL consolidating data best Strategy

I am using SQL Server 2005.
I have 8 databases in the same SQL Server.
There is a table containing thousand of Customers in each database (Property).
To make it simple
CustomerID numeric(18,0)
PropertyID int
CustomerSurname varchar(100)
CustomerName varchar(50)
CustomerEmail varchar(100)
Until now each Property populated its Customers individually. Now there is a need to consolidate the Customers for reporting purposes.
I want to find all the common Customers in all databases
(Criteria= CustomerSurname + CustomerEmail + first letter of Customer
Name)
and populate a new table (Consolidation) that contains the PropertyID and the CustomerID of the Property databases for eachcommon Customer.
ConsolidationID numeric(18,0)
PropertyID int
CustomerID numeric(18,0)
Imagine:
Customers on Property 1
1000 1 Smith Adrian smith#jj.com
Customers on Property 2
9876 2 Smith A smith#jj.com
Consolidation Table
1 1 1000
1 2 9876
So in the Consolidation table we have ID=1 for Smith which in Database1 (property) has local ID 1000 and in Database2 (property) has localID 9876
I am puzzled as to how I can find the common Customers using the criteria between 8 databases.The strategy to achieve it.
Consolidating your data is a pretty straight-forward process in this scenario.
Here's an example you can run in SSMS to get you started. Note that I am using TABLE variables instead of separate databases, but the concept remains the same.
Declare the tables ( representing databases ):
DECLARE #database1 TABLE ( CustomerID NUMERIC(18,0), PropertyID INT, CustomerSurname VARCHAR(100), CustomerName VARCHAR(50), CustomerEmail VARCHAR(100) );
DECLARE #database2 TABLE ( CustomerID NUMERIC(18,0), PropertyID INT, CustomerSurname VARCHAR(100), CustomerName VARCHAR(50), CustomerEmail VARCHAR(100) );
Insert the sample data you provided:
INSERT INTO #database1 ( CustomerID, PropertyID, CustomerSurname, CustomerEmail, CustomerName )
VALUES ( 1, 1000, 'Smith', 'Adrian', 'smith#jj.com' );
INSERT INTO #database2 ( CustomerID, PropertyID, CustomerSurname, CustomerEmail, CustomerName )
VALUES ( 2, 9876, 'Smith', 'A', 'smith#jj.com' );
Sprinkle in some SQL Server magic:
SELECT
ROW_NUMBER() OVER ( PARTITION BY CustomerSurname, CustomerEmail, FirstInitial ORDER BY CustomerSurname, CustomerEmail, FirstInitial ) AS ConsolidationID
, Consolidated.CustomerID
, Consolidated.PropertyID
FROM (
SELECT CustomerID, PropertyID, CustomerSurname, CustomerName, CustomerEmail, LEFT( CustomerName, 1 ) AS FirstInitial FROM #database1
UNION
SELECT CustomerID, PropertyID, CustomerSurname, CustomerName, CustomerEmail, LEFT( CustomerName, 1 ) AS FirstInitial FROM #database2
) AS Consolidated
ORDER BY
CustomerID, CustomerSurname, CustomerEmail, FirstInitial;
Returns consolidated resultset:
+-----------------+------------+------------+
| ConsolidationID | CustomerID | PropertyID |
+-----------------+------------+------------+
| 1 | 1 | 1000 |
| 1 | 2 | 9876 |
+-----------------+------------+------------+
Putting it to use:
To use this with your eight databases, you would simply replace the table variables ( #database1, #database2, etc... ) with the fully-qualified name to the database and table to be referenced.
SELECT {column-list} FROM MyDatabase1.dbo.TableName...
UNION
SELECT {column-list} FROM MyDatabase2.dbo.TableName...
Etc...
ROW_NUMBER() is the real "magic" here. By using its PARTION BY and ORDER BY we can get a single "ConsolidationID" for each row matching the partition criteria, in this case CustomerSurname, CustomerEmail and FirstInitial. ORDER BY is required to ensure the data is ordered correctly so the partion works as expected.
A few important things to note:
The column names must be exact between all tables and in the same order. You can
alias column names if needed.
Using UNION will exclude exact duplicates when all compared columns
are the same. I expect this is the behavior you want in this case,
but if not, replace UNION with UNION ALL to return all rows,
including exact matches.
SQL Server's ROW_NUMBER() is a pretty slick feature. You can read
more about it here.
I hope this helps get you on your way.

INSERT INTO from SELECT: The select list for the INSERT statement contains more items than the insert list

I am still getting a weird error:
The select list for the INSERT statement contains more items than the insert list. The number of SELECT values must match the number of INSERT columns.
Code:
INSERT INTO #tab (Phone)
select t2.Phone
from
(
SELECT DISTINCT top 999 t3.Phone, MIN(t3.Ord)
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
) t2
The idea is to select all phone numbers from #tabTemp with their row order. Then I wanna distinct them and insert distincted numbers into table #tab. Top 999 is here only for order by purpose, because I use it into a function (UDF).
Structures are following:
declare #tabTemp TABLE
(
Phone1 varchar(128) NULL,
Phone2 varchar(128) NULL,
Ord int
);
declate #tab TABLE
(
Phone varchar(max) NULL
);
EDITED:
FULL CODE
CREATE FUNCTION dbo.myFnc(#PID int, #VID int, #JID int, #ColumnNo int)
RETURNS #tab TABLE
(
Phone varchar(max) NULL
)
AS
BEGIN
if #PID is null and #VID is null and #JID is null
return;
if #ColumnNo is null or (#ColumnNo<>2 and #ColumnNo<>3 and #ColumnNo<>6)
return;
declare #catH int;
set #catH = dbo.fncGetCategoryID('H','tt'); -- just returning int value
declare #kvalP int;
set #kvalP = dbo.fncGetCategoryID('P','te');
declare #kvalR int;
set #kvalR = dbo.fncGetCategoryID('R','te');
declare #tabTemp TABLE
(
Phone1 varchar(128) NULL,
Phone2 varchar(128) NULL,
Ord int
);
-- finding parent subject + current one
WITH subj AS(
SELECT *
FROM Subjekt
WHERE
(ID = #PID and #PID is not null)
or
(ID = #VID and #VID is not null)
or
(ID = #JID and #JID is not null)
UNION ALL
SELECT t.*
FROM Subjekt t
INNER JOIN subj r ON r.ID = t.ID
)
INSERT INTO #tabTemp (Phone1,Phone2)
(select
(case when o.TYP1=#catH then o.TEL1 else null end) Phone1
,(case when o.TYP2=#catH then o.TEL2 else null end) Phone2
,so.POR_C
from
subj s
,SubjektPerson so
,Persons o
,recSetup idS
,recSetup idSO
,recSetup idO
where 1=1
and idO.isValid=1
and idSO.isValid=1
and idS.isValid=1
and idSO.ID0=so.ID
and idS.ID0=s.ID
and idO.ID0=o.ID
and so.ID_PERSON=o.ID
and so.ID_SUBJECT=s.ID
and (o.TYP=#kvalP or o.TYP=#kvalR)
)
INSERT INTO #tab (Phone)
select t2.Phone
from
(
SELECT DISTINCT top 999 t3.Phone, MIN(t3.Ord)
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
) t2
RETURN
END
Not sure why you have distinct AND a group by on the same query. You could greatly simplify this.
INSERT INTO #tab (Phone)
SELECT top 999 t3.Phone
FROM
(
select Phone1 as Phone, Ord from #tabTemp
union all
select Phone2 as Phone, Ord from #tabTemp
) t3
GROUP BY t3.Phone
ORDER BY MIN(t3.Ord) asc, t3.Phone
Now for the error message you were receiving, it doesn't seem like it came from this block of code because the syntax is fine and the number of columns matches correctly. I suspect the error is somewhere earlier in your code.
Also, you might want to consider using temp tables instead of table variables since it seems like you have a lot of rows in these tables.
You've focussed on the wrong insert. This is the one with the mismatch:
INSERT INTO #tabTemp (Phone1,Phone2)
(select
(case when o.TYP1=#catH then o.TEL1 else null end) Phone1
,(case when o.TYP2=#catH then o.TEL2 else null end) Phone2
,so.POR_C
from
...
Two columns in the insert list, 3 columns in the subselect. I can't tell just from the naming whether POR_C was meant to end up in the Ord column or not.
On the surface, it appears you are maybe triggering a query planner bug or something. There are a number of iffy things going on:
The union all of the same table to itself
Using both group by and distinct
I'm not sure what you mean by
Top 999 is here only for order by purpose, because I use it into a function (UDF).
Do you mean this whole query is executed within a UDF? If so, are there other queries that might be giving that error?

SQL query Optimization help

I have the the following SQL query
Declare #tempcalctbl Table
(
ItemId varchar(50),
ItemLocation varchar(50),
ItemNo varchar(50),
Width real,
Unit varchar(50),
date datetime
)
Insert Into #tempcalctbl
Select distinct SubId,ItemLocation,ItemNo,
(ABS((Select width From #temptbl a Where ItemProcess ='P1'and a.ItemId = c.ItemId
and a.ItemNo = c.ItemNo and a.ItemLocation = c.ItemLocation)
-(Select width From #temptbl b Where ItemProcess ='P2' and b.ItemId = c.ItemId
and b.ItemNo = c.ItemNo and b.ItemLocation = c.ItemLocation))) * 1000,
Unit,date
From #temptbl c
Group by ItemId,ItemLocation,ItemNo,Unit,date
I was wondering how to optimize this query.
The idea is to find out the different in width (p1's item - p2's item) between ItemProcess 'P1' and 'P2' according to the same ItemID, same ItemNo and same ItemLocation.
I have around 75000 and it took more then 25 minute to get the width differences for all the ItemId.
I tried to use Group by for the width different calculation but it would return multiple row instead of just a value which then would return error. By the way I am use MS SQL server 2008 and #tempcalctbl is a table that I declared in a store procedure.
Does the following help?
INSERT INTO #tempcalctbl
SELECT P1.SubId ,
P1.ItemLocation ,
P1.ItemNo ,
ABS(P1.Width - P2.Width) * 1000 AS Width ,
P1.Unit ,
P1.date
FROM #temptbl AS P1
INNER JOIN #temptbl AS P2 ON P1.ItemId = P2.ItemId
AND P1.ItemNo = P2.ItemNo
AND P1.ItemLocation = P2.ItemLocation
WHERE P1.ItemProcess = 'P1'
AND P2.ItemProcess = 'P2'
EDIT
To make use of indexes, you will need to change your table variable to a temporary table
CREATE TABLE #temptbl
(
ItemId varchar(50),
ItemLocation varchar(50),
ItemNo varchar(50),
Width real,
Unit varchar(50),
date DATETIME,
ItemProcess INT,
SubId INT
)
CREATE NONCLUSTERED INDEX Index01 ON #temptbl
(
ItemProcess ASC,
ItemId ASC,
ItemLocation ASC,
ItemNo ASC
)
INCLUDE ( SubId,Width,Unit,date)
GO
That should speed you up a little.
John Petrak's answer is the best query for this case.
If the speed is still now acceptable, maybe you can store #temptbl at a temporary real table, and create the related index on those four columns.

Pivot String SQL

I am trying to Pivot this table whose name is #salida
IDJOB NAME DATE
1 Michael NULL
1 Aaron NULl
THe result which I want to obtain is
IDJOB DATE NAME1 NAME2
1 NULL Michael Aaron
My code is this
SELECT *
FROM #salida
PIVOT
(
MAX([Name]) FOR [Name] IN ([Name1],[Name2])
) PVT GROUP BY IdJob,Date,Name1,Name2 ;
SELECT * FROM #salida
The result which obtain is
IDJOB DATE NAME1 NAME2
1 NULL NULL NULL
#XabiIparra, see a mock up. you need to partition by the IdJob and then add the columns needed.
DECLARE #salida TABLE(idjob VARCHAR(100),[Name] VARCHAR(100),[DATE] DATE);
INSERT INTO #salida VALUES
(1,'Michael', NULL)
,(1,'Aaron', NULL)
,(2,'Banabas', NULL)
SELECT p.*
FROM
(
SELECT *
,'NAME'+CAST(ROW_NUMBER() OVER(PARTITION BY [idjob] ORDER BY NAME) AS varchar(100)) ColumnName
FROM #salida
)t
PIVOT
(
MAX([Name]) FOR ColumnName IN (NAME1,NAME2,NAME3,NAME4,NAME5 /*add as many as you need*/)
)p;
How about must using aggregation and min() and max()?
select idjob, date, min(name), max(name)
from #salida
group by idjob, date;
SQL tables represent unordered sets, so there is no ordering to the values (unless another column specifies the ordering). So, this is probably the simplest way to get two different values in the same row.