Using COALESCE in SQL view - sql-server

I need to create a view from several tables. One of the columns in the view will have to be composed out of a number of rows from one of the table as a string with comma-separated values.
Here is a simplified example of what I want to do.
Customers:
CustomerId int
CustomerName VARCHAR(100)
Orders:
CustomerId int
OrderName VARCHAR(100)
There is a one-to-many relationship between Customer and Orders. So given this data
Customers
1 'John'
2 'Marry'
Orders
1 'New Hat'
1 'New Book'
1 'New Phone'
I want a view to be like this:
Name Orders
'John' New Hat, New Book, New Phone
'Marry' NULL
So that EVERYBODY shows up in the table, regardless of whether they have orders or not.
I have a stored procedure that i need to translate to this view, but it seems that you cant declare params and call stored procs within a view. Any suggestions on how to get this query into a view?
CREATE PROCEDURE getCustomerOrders(#customerId int)
AS
DECLARE #CustomerName varchar(100)
DECLARE #Orders varchar (5000)
SELECT #Orders=COALESCE(#Orders,'') + COALESCE(OrderName,'') + ','
FROM Orders WHERE CustomerId=#customerId
-- this has to be done separately in case orders returns NULL, so no customers are excluded
SELECT #CustomerName=CustomerName FROM Customers WHERE CustomerId=#customerId
SELECT #CustomerName as CustomerName, #Orders as Orders

EDIT: Modified answer to include creation of view.
/* Set up sample data */
create table Customers (
CustomerId int,
CustomerName VARCHAR(100)
)
create table Orders (
CustomerId int,
OrderName VARCHAR(100)
)
insert into Customers
(CustomerId, CustomerName)
select 1, 'John' union all
select 2, 'Marry'
insert into Orders
(CustomerId, OrderName)
select 1, 'New Hat' union all
select 1, 'New Book' union all
select 1, 'New Phone'
go
/* Create the view */
create view OrderView as
select c.CustomerName, x.OrderNames
from Customers c
cross apply (select stuff((select ',' + OrderName from Orders o where o.CustomerId = c.CustomerId for xml path('')),1,1,'') as OrderNames) x
go
/* Demo the view */
select * from OrderView
go
/* Clean up after demo */
drop view OrderView
drop table Customers
drop table Orders
go

In SQL Server 2008, you can take advantage of some of the features added for XML to do this all in one query without using a stored proc:
SELECT CustomerName,
STUFF( -- "STUFF" deletes the leading ', '
( SELECT ', ' + OrderName
FROM Orders
WHERE CustomerId = Customers.CutomerId
-- This causes the sub-select to be returned as a concatenated string
FOR XML PATH('')
),
1, 2, '' )
AS Orders
FROM Customers

Related

Insert multiple rows of data with out looping the table data

I have a table where it holds some duplicate entries, I would like to copy over the distinct entries to another table with out looping the data. I need to check if the distinct data exists in other table and insert what ever is missing. Here is the query I am writing, I feel like it can be implement better
CREATE TABLE ForgeRock
([productName] varchar(13));
INSERT INTO ForgeRock
([productName])
VALUES
('OpenIDM'), ('OpenAM'), ('OpenDJ'), ('OpenDJ'),('OpenDJ1');
CREATE TABLE ForgeRock1
([productName] varchar(13));
DECLARE #prodName NVARCHAR(MAX)
SELECT DISTINCT #prodName = STUFF((SELECT ',' + productName
FROM ForgeRock
FOR XML PATH('')) ,1,1,'')
set #prodName = ''''+replace(#prodName,',',''',''')+''''
INSERT INTO ForgeRock1 (productName)
SELECT DISTINCT productName FROM ForgeRock WHERE
productName NOT IN (SELECT productName FROM ForgeRock1
where productName NOT IN (#prodName))
Here is the sample fiddle I tried out http://sqlfiddle.com/#!18/9dbe8f/1/0, is this query efficient or can it be better
This query should do what you want :)
INSERT INTO ForgeRock1 (productName)
SELECT DISTINCT productName FROM ForgeRock fr
WHERE NOT EXISTS ( SELECT 1 FROM ForgeRock1 fr1 WHERE fr1.productName = fr.productName )

Query Large (in the millions) Data Faster

I have two tables:
Tbl1 has 2 columns: name and state
Tbl2 has name and state and additional columns about the fields
I am trying to match tbl1 name and state with tbl2 name and state. I have remove all exact matches, but I see that I could match more if I could account for misspelling and name variations by using a scalar function that compares the 2 names and returns an integer showing how close of a match they are (the lower the number returned the better the match).
The issue is that Tbl1 has over 2M records and Tbl2 has over 4M records – it takes about 30sec to just to search one record from Tbl1 in Tbl2.
Is there some way I could arrange the data or query so the search could be completed faster?
Here’s the table structure:
CREATE TABLE Tbl1
(
Id INT NOT NULL IDENTITY( 1, 1 ) PRIMARY KEY,
Name NVARCHAR(255),
[State] VARCHAR(50),
Phone VARCHAR(50),
DoB SMALLDATETIME
)
GO
CREATE INDEX tbl1_Name_indx ON dbo.Tbl1( Name )
GO
CREATE INDEX tbl1_State_indx ON dbo.Tbl1( [State] )
GO
CREATE TABLE Tbl2
(
Id INT NOT NULL IDENTITY( 1, 1 ) PRIMARY KEY,
Name NVARCHAR(255),
[State] VARCHAR(50)
)
GO
CREATE INDEX tbl2_Name_indx ON dbo.Tbl1( Name )
GO
CREATE INDEX tbl2_State_indx ON dbo.Tbl1( [State] )
GO
Here's a sample function that I tested with to try to rule out function complexity:
CREATE FUNCTION [dbo].ScoreHowCloseOfMatch
(
#SearchString VARCHAR(200) ,
#MatchString VARCHAR(200)
)
RETURNS INT
AS
BEGIN
DECLARE #Result INT;
SET #Result = 1;
RETURN #Result;
END;
Here's some sample data:
INSERT INTO Tbl1
SELECT 'Bob Jones', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Melcome T Homes', 'CA', '927-333-2222', 'June 10, 1971' UNION
SELECT 'Janet Rengal', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Matt Francis', 'TN', '234-333-2222', 'June 10, 1971' UNION
SELECT 'Same Bojen', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Frank Tonga', 'NY', '903-333-2222', 'June 10, 1971' UNION
SELECT 'Jill Rogers', 'WA', '555-333-2222', 'June 10, 1971' UNION
SELECT 'Tim Jackson', 'OR', '757-333-2222', 'June 10, 1971'
GO
INSERT INTO Tbl2
SELECT 'BobJonez', 'WA' UNION
SELECT 'Malcome X', 'CA' UNION
SELECT 'Jan Regal', 'WA'
GO
Here's the query:
WITH cte as (
SELECT t1Id = t1.Id ,
t1Name = t1.Name ,
t1State = t1.State,
t2Name = t2.Name ,
t2State = t2.State ,
t2.Phone ,
t2.DoB,
Score = dbo.ScoreHowCloseOfMatch(t1.Name, t2.Name)
FROM dbo.Tbl1 t2
JOIN dbo.Tbl2 t1
ON t1.State = t2.State
)
SELECT *
INTO CompareResult
FROM cte
ORDER BY cte.Score ASC
GO
One possibility would be to add a column with a normalized name used only for matching purposes. You would remove all the white spaces, remove accents, replace first names by abbreviated first names, replace known nicknames by real names etc.
You could even sort the first name and the last name of one person alphabetically in order to allow swapping both.
Then you can simply join the two tables by this normalized name column.
JOIN dbo.Tbl2 t1
ON t1.State = t2.State
You are joining 2Mx4M rows on a max 50 distinct values join criteria. No wonder this is slow. You need to go back to the drawing board and redefine your problem. If you really want to figure out the 'close match' of every body with everybody else in the same state, then be prepared to pay the price...

Create new table concatenating fields from many records

I have a large table that hold all our customer purchase.
Each record contains the purchased item, the date of purchase and a lot of other fields that are uselessly replicated for each purchase.
I wish to create a stored procedure to periodically export a new table with only one record per client with all the dates and the purchased items concatenated into one text field separated by a conventional character or crlf.
So now my table "Purchases" is something like this:
ID |Name|PurDate |PurItem |etc
0001|Jack|20100101|Art. 115|.......
0002|Jack|20100105|Art. 230|.......
0003|Jack|20120408|Art. 098|.......
0004|John|20150808|Art. 021|.......
0005|John|20160203|Art. 432|.......
The new table should look like:
ID |Name|Purchase|etc
0001|Jack|20100101 Art. 115;20100105 Art. 230;20120408 Art. 098|.......
0002|John|20150808 Art. 021;20160203 Art. 432|.......
Have you any suggestion?
try this code and customize to your requirement,
CREATE PROCEDURE UPDATERECORDS
BEGIN
-- Create temp table to get distinct names
CREATE TABLE #TEMP (NAME NVARCHAR(100))
-- insert names
INSERT INTO #TEMP
SELECT DISTINCT NAME FROM PURCHASES
-- insert to your table
INSERT INTO [YOUR NEW TABLE]
SELECT
ROW_NUMBER()OVER (ORDER BY NAME) ID,
NAME =
STUFF((SELECT ', ' + NAME
FROM PURCHASE A
WHERE A.NAME = B.NAME
FOR XML PATH('')), 1, 2, ''),
PURCHASE =
STUFF((SELECT ', ' + PURCHASE
FROM PURCHASE A
WHERE A.NAME = B.NAME
FOR XML PATH('')), 1, 2, ''), ETC.....
FROM #TEMP B
END

Unpivot dynamic table columns into key value rows

The problem that I need to resolve is data transfer from one table with many dynamic fields into other structured key value table.
The first table comes from a data export from another system, and has the following structure ( it can have any column name and data):
[UserID],[FirstName],[LastName],[Email],[How was your day],[Would you like to receive weekly newsletter],[Confirm that you are 18+] ...
The second table is where I want to put the data, and it has the following structure:
[UserID uniqueidentifier],[QuestionText nvarchar(500)],[Question Answer nvarchar(max)]
I saw many examples showing how to unpivot table, but my problem is that I dont know what columns the Table 1 will have. Can I somehow dynamically unpivot the first table,so no matter what columns it has, it is converted into a key-value structure and import the data into the second table.
I will really appreciate your help with this.
You can't pivot or unpivot in one query without knowing the columns.
What you can do, assuming you have privileges, is query sys.columns to get the field names of your source table then build an unpivot query dynamically.
--Source table
create table MyTable (
id int,
Field1 nvarchar(10),
Field2 nvarchar(10),
Field3 nvarchar(10)
);
insert into MyTable (id, Field1, Field2, Field3) values ( 1, 'aaa', 'bbb', 'ccc' );
insert into MyTable (id, Field1, Field2, Field3) values ( 2, 'eee', 'fff', 'ggg' );
insert into MyTable (id, Field1, Field2, Field3) values ( 3, 'hhh', 'iii', 'jjj' );
--key/value table
create table MyValuesTable (
id int,
[field] sysname,
[value] nvarchar(10)
);
declare #columnString nvarchar(max)
--This recursive CTE examines the source table's columns excluding
--the 'id' column explicitly and builds a string of column names
--like so: '[Field1], [Field2], [Field3]'.
;with columnNames as (
select column_id, name
from sys.columns
where object_id = object_id('MyTable','U')
and name <> 'id'
),
columnString (id, string) as (
select
2, cast('' as nvarchar(max))
union all
select
b.id + 1, b.string + case when b.string = '' then '' else ', ' end + '[' + a.name + ']'
from
columnNames a
join columnString b on b.id = a.column_id
)
select top 1 #columnString = string from columnString order by id desc
--Now I build a query around the column names which unpivots the source and inserts into the key/value table.
declare #sql nvarchar(max)
set #sql = '
insert MyValuestable
select id, field, value
from
(select * from MyTable) b
unpivot
(value for field in (' + #columnString + ')) as unpvt'
--Query's ready to run.
exec (#sql)
select * from MyValuesTable
In case you're getting your source data from a stored procedure, you can use OPENROWSET to get the data into a table, then examine that table's column names. This link shows how to do that part.
https://stackoverflow.com/a/1228165/300242
Final note: If you use a temporary table, remember that you get the column names from tempdb.sys.columns like so:
select column_id, name
from tempdb.sys.columns
where object_id = object_id('tempdb..#MyTable','U')

Select row data as ColumnName and Value

I have a history table and I need to select the values from this table in ColumnName, ColumnValue form. I am using SQL Server 2008 and I wasn’t sure if I could use the PIVOT function to accomplish this. Below is a simplified example of what I need to accomplish:
This is what I have:
The table’s schema is
CREATE TABLE TABLE1 (ID INT PRIMARY KEY, NAME VARCHAR(50))
The “history” table’s schema is
CREATE TABLE TABLE1_HISTORY(
ID INT,
NAME VARCHAR(50),
TYPE VARCHAR(50),
TRANSACTION_ID VARCHAR(50))
Here is the data from TABLE1_HISTORY
ID NAME TYPE TRANSACTION_ID
1 Joe INSERT a
1 Bill UPDATE b
1 Bill DELETE c
I need to extract the data from TABLE1_HISTORY into this format:
TransactionId Type ColumnName ColumnValue
a INSERT ID 1
a INSERT NAME Joe
b UPDATE ID 1
b UPDATE NAME Bill
c DELETE ID 1
c DELETE NAME Bill
Other than upgrading to Enterprise Edition and leveraging the built in change tracking functionality, what is your suggestion for accomplishing this task?
You could try using a UNION
Test Data
DECLARE #TABLE1_HISTORY TABLE (
ID INT,
NAME VARCHAR(50),
TYPE VARCHAR(50),
TRANSACTION_ID VARCHAR(50))
INSERT INTO #TABLE1_HISTORY
SELECT 1, 'Joe', 'INSERT', 'a'
UNION ALL SELECT 1, 'Bill', 'UPDATE', 'b'
UNION ALL SELECT 1, 'Bill', 'DELETE', 'c'
SQL Statement
SELECT [TransactionID] = Transaction_ID
, [Type] = [Type]
, [ColumnName] = 'ID'
, [ColumnValue] = CAST(ID AS VARCHAR(50))
FROM #Table1_History
UNION ALL
SELECT [TransactionID] = Transaction_ID
, [Type] = [Type]
, [ColumnName] = 'NAME'
, [ColumnValue] = [Name]
FROM #Table1_History
ORDER BY TransactionID
, ColumnName
This can be done with the UNPIVOT function in SQL Server:
select transaction_id,
type,
ColumnName,
ColumnValue
from
(
select transaction_id,
type,
cast(id as varchar(50)) id,
name
from TABLE1_HISTORY
) src
unpivot
(
ColumnValue
for ColumnName in (ID, Name)
) un

Resources