Is there any way in T-SQL to use a dynamic operand when the operand is variable - sql-server-2016

I am trying to allow users to select the operand in a custom alert application. However, since many different versions of the alert can be setup and they will all run at the same time, I cannot create a variable to hold the operand and still run this as a set based operation (at least not that I can figure out, hence this post).
Thru a series of manipulations, I end up with a data set like this:
BusinessISN | InvoiceID | InvoiceTotal | Measure | ThresholdValue
-------------|-----------|--------------|---------|----------------
100002550 | 1165803 | 1171.8 | = | 1616.96
100002315 | 1165804 | 3190 | >= | 3000
100002550 | 1165806 | 473.68 | = | 1616.96
100003156 | 1165807 | 1612 | <= | 2000
100002550 | 1165809 | 1616.96 | = | 1616.96
100002550 | 1165810 | 1760.8 | = | 1616.96
| | | |
What I would like to do is be able to compare Invoicetotal to ThresholdValue using the operand called out in Measure.
I apologize ahead of time that I cannot figure out how I am supposed to format data. I tried creating an HTML table, but even that does not seem to have worked.

I'm not really sure what your desired result are, but this can be done using some dynamic SQL.
I'm assuming you want to get the BusinessISN and InvoiceID of the records where the condition expressed in the three other columns (InvoiceTotal, Measure and ThresholdValue ) evaluates to true - so this is what I wrote.
First, create and populate sample table (Please save us this step in your future questions):
DECLARE #T AS TABLE
(
BusinessISN int,
InvoiceID int,
InvoiceTotal float,
Measure varchar(2),
ThresholdValue float
)
INSERT INTO #T (BusinessISN, InvoiceID, InvoiceTotal, Measure, ThresholdValue) VALUES
(100002550, 1165803, 1171.8, '= ', 1616.96),
(100002315, 1165804, 3190, '>=', 3000),
(100002550, 1165806, 473.68, '= ', 1616.96),
(100003156, 1165807, 1612, '<=', 2000),
(100002550, 1165809, 1616.96, '= ', 1616.96),
(100002550, 1165810, 1760.8, '= ', 1616.96);
Then, use a for xml with stuff to dynamically generate a union all query:
DECLARE #Sql nvarchar(max);
SELECT #Sql = STUFF
(
(
SELECT N' UNION ALL SELECT '+ CAST(BusinessISN as nvarchar(11)) + N' AS BusinessISN, '+
CAST(InvoiceID as nvarchar(11)) + N' AS InvoiceID '+
N'WHERE '+ CAST(InvoiceTotal as varchar(100)) +' '+ Measure +' '+ CAST(ThresholdValue as varchar(100))
FROM #T
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
, 1, 10, '')
Next, manually check the SQL you've created using print. The print command is your best friend when dealing with dynamic SQL.
PRINT #Sql
The print should give you the following output, though a little less organized (based on the sample data):
SELECT 100002550 AS BusinessISN, 1165803 AS InvoiceID WHERE 1171.8 = 1616.96 UNION ALL
SELECT 100002315 AS BusinessISN, 1165804 AS InvoiceID WHERE 3190 >= 3000 UNION ALL
SELECT 100002550 AS BusinessISN, 1165806 AS InvoiceID WHERE 473.68 = 1616.96 UNION ALL
SELECT 100003156 AS BusinessISN, 1165807 AS InvoiceID WHERE 1612 <= 2000 UNION ALL
SELECT 100002550 AS BusinessISN, 1165809 AS InvoiceID WHERE 1616.96 = 1616.96 UNION ALL
SELECT 100002550 AS BusinessISN, 1165810 AS InvoiceID WHERE 1760.8 = 1616.96
Then, when you are sure you've got the correct query, all you have to do is execute it:
EXEC(#Sql)
Which should give you the following results:
BusinessISN InvoiceID
100002315 1165804
100003156 1165807
100002550 1165809
You can see a live demo on rextester.

Related

SQL Server Management Studio - UNPIVOT Query for n columns, n rows

I wanted to unpivot a dataset which looks like this:
To this:
+-------------+-----------------+---------+
| Scenario ID | Distribution ID | Value |
+-------------+-----------------+---------+
| 0 | Number1 | 10 |
| 0 | Number2 | 19 |
| 0 | Number3 | 34.3 |
| 0 | Number4 | 60.31 |
| 0 | Number5 | 104.527 |
+-------------+-----------------+---------+
Using SQL System Management Studio.
I think I should use a code which is based on something like this:
SELECT *
FROM
(
SELECT 1
FROM table_name
) AS cp
UNPIVOT
(
Scenario FOR Scenarios IN (*
) AS up;
Can anyone help me with this? I do not know how to code, just starting.
Thanks in advance!
In case you need a dynamic unpivot solution (that can handle any number of columns) try this:
create table [dbo].[Test] ([ScenarioID] int, [Number1] decimal(10,3),
[Number2] decimal(10,3), [Number3] decimal(10,3),
[Number4] decimal(10,3), [Number5] decimal(10,3))
insert into [dbo].[Test] select 0, 10, 19, 34.3, 60.31, 104.527
declare #sql nvarchar(max) = ''
declare #cols nvarchar(max) = ''
select #cols = #cols +','+ QUOTENAME(COLUMN_NAME)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA='dbo' and TABLE_NAME='test' and COLUMN_NAME like 'Number%'
order by ORDINAL_POSITION
set #cols = substring(#cols, 2, LEN(#cols))
set #sql = #sql + ' select u.[ScenarioID], u.[DistributionID], u.[Value]
from [dbo].[Test] s
unpivot
(
[Value]
for [DistributionID] in ('+ #cols + ')
) u;'
execute(#sql)
Result:
I would use apply :
select t.scenarioid, tt.distributionId, tt.value
from table t cross apply
( values (Number1, 'Number1'), (Number2, 'Number2'), . . .
) tt (value, distributionId);
Yes, you need to list out all possible Numbers first time only.
You could use VALUES:
SELECT T.scenarioId, s.*
FROM tab t
CROSS APPLY (VALUES ('Number1', t.Number1),
('Number2', t.Number2)) AS s(DistId, Val)
I use cross apply for this:
select t.scenarioid, v.*
from t cross apply
(values ('Number1', number1), ('Number2', number2), . . .
) v(distributionId, number);
You need to list out all the numbers.
Why do I prefer cross apply over unpivot? I find the unpivot syntax to be very specific. It pretty much does exactly one thing. On the other hand, apply introduces lateral joins. These are very powerful, and apply can be used in many different situations.

T-Sql Query with dynamic (unknown) number of columns

We have a project where we should provide the possible to the user to add own custom columns to various tables.
Edit: these are 2 tables, not one.
**Products**
ProductId
Name
Price
Date
UserId
**ProductsCustomColumns**
ProductId
ColumnName
ColumnValue
EDIT: Please note that the dynamic columns are recorded as values and we don't know the count of these...it can be 0 or 200 or any.
Here is an example:
Now when we query the products tables we want to show all the predefined columns and after them all custom columns.
Obviously each user can have own number of columns with values and names.
SELECT *, (and the custom columns) FROM Products WHERE UserId = 3 AND ProductId = 1
Here are 2 questions:
Would that be good solution from performance point of view or there is better approach for solving the dynamic columns requirement?
How can I create a query that could read all records from ProductsCustomColumns for given userId and productId and append the records as columns to the query?
Thanks.
You need to write dynamic Query
DECLARE #COLUMNS VARCHAR(MAX)='', #QRY VARCHAR(MAX);
SELECT #COLUMNS = #COLUMNS +COLUMN_NAME +',' FROM
INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME='Products'
SELECT #COLUMNS =SUBSTRING (#COLUMNS,1 ,LEN(#COLUMNS)-1)
SELECT #QRY ='SELECT '+#COLUMNS + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#QRY)
EDIT: From your Comments & Edited Question
Schema I assumed from your Question
CREATE TABLE Products (
ProductId INT,
Name VARCHAR(250),
Price DECIMAL(18,2),
DateS DATETIME,
UserId INT)
INSERT INTO Products
SELECT 1,'Oil Product', 2000, GETDATE(), 3
UNION ALL
SELECT 2,'Amway', 600, GETDATE(), 2
UNION ALL
SELECT 3,'Thermal', 5000, GETDATE(), 1
UNION ALL
SELECT 4,'Oil Product', 500, GETDATE(), 4
CREATE TABLE ProductsCustomColumns
(
ProductId INT ,
ColumnName VARCHAR(200),
ColumnValue VARCHAR(15))
INSERT INTO ProductsCustomColumns
SELECT 1, 'Licence_No', '1545'
UNION ALL
SELECT 1, 'Location ', 'Atlanta'
UNION ALL
SELECT 2, 'Qty ', '5'
UNION ALL
SELECT 3, 'Gross', '80000'
Now your Dynamic Code goes here
DECLARE #COLUMN_PCC VARCHAR(MAX)='', #PRODUCT_ID INT=1,#USER_ID INT=3, #QRY VARCHAR(MAX) ;
--preparing Custom Column Name List with comma ','
SELECT #COLUMN_PCC = #COLUMN_PCC+ [COLUMNNAME] +',' FROM ProductsCustomColumns
WHERE ProductId= #PRODUCT_ID
SELECT #COLUMN_PCC =SUBSTRING(#COLUMN_PCC,1,LEN(#COLUMN_PCC)-1)
--Preparing Dynamic Query
SELECT #QRY =' SELECT P.*, AV.* FROM Products P
INNER JOIN
(
SELECT * FROM (
SELECT * FROM ProductsCustomColumns WHERE ProductId= '+CAST(#PRODUCT_ID AS VARCHAR(50))+'
)
AS A
PIVOT
(
MAX (COLUMNVALUE)
FOR [COLUMNNAME] IN ('+#COLUMN_PCC +')
)AS PVT
)AS AV ON P.ProductId= AV.ProductId
AND P.UserId='++CAST(#USER_ID AS VARCHAR(50))+'
'
EXEC ( #QRY)
And the Result will be
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| ProductId | Name | Price | DateS | UserId | ProductId | Licence_No | Location |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
| 1 | Oil Product | 2000.00 | 2016-12-09 18:06:24.090 | 3 | 1 | 1545 | Atlanta |
+-----------+-------------+---------+-------------------------+--------+-----------+------------+----------+
You need dynamic sql no other way to do this
DECLARE #sql VARCHAR(max),
#cust_col VARCHAR(max)
SET #cust_col = (SELECT Quotename(CustomColumns) + ','
FROM ProductsCustomColumns
FOR xml path(''))
SELECT #cust_col = LEFT(#cust_col, Len(#cust_col) - 1)
SET #sql = 'SELECT *, ' + #cust_col + ' FROM Products WHERE UserId = 3 AND ProductId = 1'
EXEC (#sql)
In general it is a very bad idea to add custom data in additional columns of your main table. Just imagine 100 customers using this. All of them have differing table schemas and you wnat to write an update script for all of them?
It is a pain in the neck, if you have to deal with result sets where you don't know the structure in advance!
You have several choices:
Add one column of type XML. The advantage: The resultset is fix. You just need a customer specific rule, how to interpret the XML. You can solve this with an inline table valued function. Pass in the XML and get a derived table back. Call this with CROSS APPLY and you are out...
Add a new table with the customerID and Key-Value-Pairs
If the additional data is not completely different, add some of the columns to your main table as SPARSE columns

How Do I Use PIVOT On This Data:?

I have a SQL Server table that looks like this:
RESOURCE | DESCRIPTION | VALUE
Test A Name | Resource A-xyz
Test A | Height | 20
Test A | Unit | ft
Test A | Location | Site 1
Test B | Volume | 30
Test C | Width | 10
Test C | Unit | in
I would like to get it into this format:
RESOURCE | Name | Height | Unit | Location | Volume | Width
Test A | Resource A-xyz | 20 | ft | Site 1 | |
Test B | | | | | 30 |
Test C | | | in | | | 10
One of the issues that I have is that there is no set pattern for description; for example, resource "Test B" might have all of the same descriptions as "Test A", while "Test C", might be missing some, and "Test D" might have a totally different set.
So far Google is suggesting that I want to use a pivot table, but I am still not sure how to do that with the above data.
The resulting column list is based on the ordering you provide within the PIVOT:
SELECT *
FROM Table1
PIVOT(MAX(VALUE) FOR DESCRIPTION IN (Name,Height,Unit,Location,Volume,Width))p
Demo: SQL Fiddle
If you have changing values for DESCRIPTION it's worthwhile to build this query dynamically, there are plenty of good examples for 'dynamic pivot' to be found.
try this code:
SELECT resource,Name,Height,Unit,Location,Volume,Width
FROM
#T1 AS SourceTable
PIVOT
(
max(value)
FOR description IN ([Name],[Height],[Unit],[Location],[Volume],[Width])
) AS PivotTable
ORDER BY 1
If you don't want to hardcode the columns and want to generate same view on the fly, you can use this. This will generate dynamic pivot
CREATE TABLE demo
(
RESOURCE VARCHAR(100),
DESCRIPTION VARCHAR(100), VALUE VARCHAR(100)
)
INSERT INTO demo VALUES
('Test A' , 'Name' , 'Resource A-xyz')
,('Test A' , 'Height' , '20')
,('Test A' , 'Unit' , 'ft')
,('Test A' , 'Location' , 'Site 1')
,('Test B' , 'Volume' , '30')
,('Test C' , 'Width' , '10')
,('Test C' , 'Unit' , 'in')
SELECT DISTINCT DESCRIPTION INTO #tbl FROM demo
//Get list of values to be pivoted
DECLARE #var NVARCHAR(1000)=''
SELECT #var = #var +', ' + DESCRIPTION FROM #tbl
SELECT #var = SUBSTRING(#var, 2, LEN(#var))
SELECT #var
DECLARE #query NVARCHAR(2000) = 'SELECT * FROM demo PIVOT(MAX(VALUE) FOR DESCRIPTION IN ('+ #var + '))p'
EXEC sp_executesql #query
In the end, I did the following:
Selected all distinct descriptions (more than 70!).
Created a table that had resource and every single distinct description as fields
Populated resource column distinct resource names
Ran a series of
updates to populate remaining columns for each distinct resource
name.
For example
CREATE TABLE #tb1
(
[RESOURCE] varchar(100),
[FIELD1] varchar(100),
[FIELD2] varchar(50),
.
.
.
[LAST FIELD] varchar(50),
)
INSERT INTO #tb1 (RESOURCE)
SELECT DISTINCT RESOURCE FROM tb2
ORDER BY Resource ASC
UPDATE #tb1 SET [FIELD1] = (SELECT VALUE FROM tb2 WHERE Resource = #tb1.Resource and Property = [FIELD1])
.
.
.
UPDATE #tb1 SET [LAST FIELD] = (SELECT VALUE FROM tb2 WHERE Resource = #tb1.Resource and Property = [LAST FIELD])

How do i transform rows into columns in sql server 2005

There is a question here in stackoverflow with the same title but that is not what I am looking for.
I have a table like the one below
Name | Count
----------------
Chery | 257
Drew | 1500
Morgon | 13
Kath | 500
Kirk | 200
Matt | 76
I need to trasform this result set into something like this
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
How do i acheive this using sql server 2005?
There are similar questions here,here answered in stackoverflow.
You need to use the operator PIVOT in your query to acheive this.Here is the example and explanation on how you can do that.The example is referenced from this source.
---I assumed your tablename as TESTTABLE---
DECLARE #cols NVARCHAR(2000)
DECLARE #query NVARCHAR(4000)
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
SET #query = N'SELECT '+ #cols +' FROM
(SELECT t1.Name , t1.Count FROM TESTTABLE AS t1) p
PIVOT (MAX([Count]) FOR Name IN ( '+ #cols +' ))
AS pvt;'
EXECUTE(#query)
Explanation
1.The first part of the query
SELECT #cols = STUFF(( SELECT DISTINCT TOP 100 PERCENT
'],[' + t.Name
FROM TESTTABLE AS t
ORDER BY '],[' + t.Name
FOR XML PATH('')
), 1, 2, '') + ']'
gives you a nice flattened result of your Name column values in a single row as follow
[Cheryl],[Drew],[Karen],[Kath],[Kirk],[Matt]
You can learn more about the STUFF and XML PATH here and here.
2.SELECT + #cols + FROM will select all the rows as coloumn names for the final result set (pvt - step 3)
i.e
Select [Chery],[Drew],[Morgan],[Kath],[Kirk],[Matt]
3.This query pulls all the rows of data that we need to create the cross-tab results. The (p) after the query is creating a temporary table of the results that can then be used to satisfy the query for step 1.
(SELECT t1.Name, t1.Count FROM TESTTABLE AS t1) p
4.The PIVOT expression
PIVOT (MAX (Count) FOR Name IN ( #cols) AS pvt
does the actual summarization and puts the results into a temporary table called pvt as
Chery | Drew | Morgon | Kath | Kirk | Matt
-------------------------------------------
257 1500 13 500 200 76
See Using PIVOT and UNPIVOT.
You can use the PIVOT and UNPIVOT
relational operators to change a
table-valued expression into another
table. PIVOT rotates a table-valued
expression by turning the unique
values from one column in the
expression into multiple columns in
the output, and performs aggregations
where they are required on any
remaining column values that are
wanted in the final output. UNPIVOT
performs the opposite operation to
PIVOT by rotating columns of a
table-valued expression into column
values.
The quick answer is
SELECT Chery, Drew, Morgon, Kath, Kirk, Matt
FROM
(SELECT [Name], [Count] From Foo)
PIVOT
(
MIN([Count])
FOR [Name] IN (Chery, Drew, Morgon, Kath, Kirk, Matt)
) AS PivotTable
If you want to avoid anything complicated like a pivot or the accepted answer, you can do this! (most of the code is just setting up the test data just in case anyone wants to try it)
/* set up your test table */
declare #TestData table (Name Varchar(80),[Count] int)
insert into #TestData (Name, [count])
Select 'Chery' as name, 257 as [count]
union all select 'Drew', 1500
union all select 'Morgon',13
union all select 'Kath', 500
union all select 'Kirk', 200
union all select 'Matt', 76
/* the query */
Declare #Query Varchar(max)
Select #Query=Coalesce(#query+', ','SELECT ') +Convert(VarChar(5),[count]) +' as ['+name+']'
from #TestData
Execute (#Query)
/* result
Chery Drew Morgon Kath Kirk Matt
----------- ----------- ----------- ----------- ----------- -----------
257 1500 13 500 200 76
*/

Query Transposing certain rows into column names

I have a couple of tables which look like this
Table 1
user_id | name
-------------------------
x111 | Smith, James
x112 | Smith, Jane
etc..
Table 2
id | code | date | incident_code | user_id
-----------------------------------------------------------------
1 | 102008 | 10/20/2008 | 1 | x111
2 | 113008 | 11/30/2008 | 3 | x111
3 | 102008 | 10/20/2008 | 2 | x112
4 | 113008 | 11/30/2008 | 5 | x112
What i'd like to display is something like this
user_id | user_name | INCIDENT IN OCT 2008 | INCIDENT IN NOV 2008
------------------------------------------------------------------------------
x111 | Smith, John | 1 | 3
x112 | Smith, Jane | 2 | 5
etc..
The incident_code would be replaced by the actual description of the incident which is located in another table, but i thought i'd see how this would work first.
Some of the column headers would be static while others would be created based on the date.
Does anyone one know how i can do this using sql server 2005? Some examples would be very helpful.
Thanks in advance
Here's a solution which generates and runs the dynamic SQL with a PIVOT:
DECLARE #pivot_list AS VARCHAR(MAX)
--
;
WITH cols
AS ( SELECT DISTINCT
'INCIDENT IN ' + LEFT(UPPER(CONVERT(VARCHAR, [date], 107)),
3) + ' '
+ SUBSTRING(UPPER(CONVERT(VARCHAR, [date], 107)), 9, 4) AS col
FROM so926209_2
)
SELECT #pivot_list = COALESCE(#pivot_list + ', ', '') + '[' + col + ']'
FROM cols
--
DECLARE #template AS VARCHAR(MAX)
SET #template = 'WITH incidents AS (
SELECT [user_id],
incident_code,
''INCIDENT IN '' + LEFT(UPPER(CONVERT(VARCHAR, [date], 107)), 3)
+ '' '' + SUBSTRING(UPPER(CONVERT(VARCHAR, [date], 107)), 9, 4) AS col
FROM so926209_2
)
,results AS (
SELECT * FROM incidents PIVOT (MAX(incident_code) FOR col IN ({#pivot_list})) AS pvt
)
SELECT results.[user_id]
,so926209_1.[name]
,{#select_list}
FROM results INNER JOIN so926209_1 ON so926209_1.[user_id] = results.[user_id]
'
DECLARE #sql AS VARCHAR(MAX)
SET #sql = REPLACE(REPLACE(#template, '{#pivot_list}', #pivot_list), '{#select_list}', #pivot_list)
--PRINT #sql
EXEC (#sql)
Where so926209_1, so926209_2 are your table 1 and table 2
Note that if you have multiple incidents in a month for the same person, your example doesn't show how you want that handled. This example only takes the last incident in the month.
You want to Pivot
http://msdn.microsoft.com/en-us/library/ms177410.aspx
This sounds like a reporting task. Reporting, often referred to from a database perspective as OLAP, Online Aanalytical Processing, tends to differ quite frequently from "traditional" database access, OLTP (Online Transaction Processing) in that it is quite often made up of large aggregations of data spanning greater periods of time. Quite frequently, the kind of aggregation your looking for.
Use of a Pivot as Tetraneutron suggested will be sufficient for smaller data sets. However, as the volume of data you need to report on grows, you may need something more advanced. OLAP is provided for by SQL Server Analysis Services (SSAS), available in 2005 and 2008. Using SSAS you can create multidimensional data repositories that pre-aggregate data from either an OLTP database directly, or from an intermediary data warehouse database. Multidimensional data (usually referred to as cubes), provide a much faster way to access the kind of data you can get from a Pivot, without interfering with the performance of your standard transaction processing in your OLTP database.
If you have more than a small amount of data you need to report on, I recommend you check out SQL Server Analysis Services 2005, OLAP, Cubes, and MDX (Multidimensional Extensions for T-SQL.) There is a larger learnig curve to set up an OLAP Cube, but once it is set up, the benefits of having one can be huge if you have significant reporting needs.
A query like this would work:
select
u.User_id,
u.Name,
Okt2008Sum = sum(case when i.date between
'2008-10-01' and '2008-11-01' then 1 else 0 end),
Nov2008Sum = sum(case when i.date between
'2008-11-01' and '2008-12-01'then 1 else 0 end)
from #incidents i
inner join #users u on i.user_id = u.user_id
group by u.user_id, u.name
Depending on your client and how often you have to run it, you can generate this query. In SQL this would look like:
create table #months (
MonthName varchar(25),
StartDate datetime
)
insert into #months values ('Okt2008','2008-10-01')
insert into #months values ('Nov2008','2008-11-01')
declare #query varchar(8000)
select #query = 'select u.User_id, u.Name '
select #query = #query + ', ' + MonthName +
' = sum(case when i.date between ''' + cast(StartDate as varchar) +
''' and ''' + cast(dateadd(m,1,StartDate) as varchar) +
''' then 1 else 0 end) '
from #Months
select #query = #query + '
from #incidents i
inner join #users u on i.user_id = u.user_id
group by u.user_id, u.name'
exec (#query)