Compare two values from one column based on values in another - sql

I have table where I need to compare two values based on their names in another column to see which one is bigger. Basically:
If **name1** -> value1
If **name2** -> value2
Compare **value1 > value2**
I have done summations but I cannot find a way to use the CASE WHEN approach to this situation.

You can PIVOT the data and have all values in current row. Then, compare them as you like:
SELECT [Id]
,[name1], [name2], [name3], [name4]
FROM [my_table]
PIVOT
(
MAX([value]) FOR [column] IN ([name1], [name2], [name3], [name4])
) PVT
or
SELECT [Id]
,MAX(CASE WHEN [column] = 'name1' THEN [value] END) AS [name1]
,MAX(CASE WHEN [column] = 'name2' THEN [value] END) AS [name2]
,MAX(CASE WHEN [column] = 'name3' THEN [value] END) AS [name3]
,MAX(CASE WHEN [column] = 'name4' THEN [value] END) AS [name4]
FROM [my_table]
GROUP [Id];

Related

PIVOT without aggregate function ID as column names

I have a query
SELECT a.Name, b.Date FROM
Table1 a
JOIN Table2 b on a.deviceID=b.id
where a.date > '2022-10-01'
Which gives me result
Name Date
A1 '2022-10-01 12:13'
A2 '2022-10-02 14:15'
A2 '2022-10-02 15:16'
A5 '2022-10-03 16:19'
etc.
The result I want to achieve is
A1 A2 A5
'2022-10-01 12:13' '2022-10-02 14:15''2022-10-03 16:19'
'2022-10-02 15:16'
The perfect result would be to receive only one date of each day. Can I do it with pivot?
Pivoting cannot be done (in T-SQL) without aggregation. As for what you want to achieve, seems you need to PIVOT/conditionally aggregate on the value of Name and group on the value of a ROW_NUMBER.
I, personally, prefer using conditional aggregation over the restrictive PIVOT operator, but I have included examples of both:
USE Sandbox;
GO
CREATE TABLE #YourData (Name char(2),
Date datetime2(0));
GO
INSERT INTO #YourData (Name,
Date)
VALUES('A1','2022-10-01T12:13:00'),
('A2','2022-10-02T14:15:00'),
('A2','2022-10-02T15:16:00'),
('A5','2022-10-03T16:19:00');
GO
WITH RNs AS(
SELECT [Name],
[Date],
ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Date]) AS RN
FROM #YourData)
SELECT MAX(CASE [Name] WHEN 'A1' THEN [Date] END) AS A1,
MAX(CASE [Name] WHEN 'A2' THEN [Date] END) AS A2,
MAX(CASE [Name] WHEN 'A5' THEN [Date] END) AS A5
FROM RNs
GROUP BY RN;
GO
SELECT P.A1,
P.A2,
P.A5
FROM (SELECT [Name],
[Date],
ROW_NUMBER() OVER (PARTITION BY [Name] ORDER BY [Date]) AS RN
FROM #YourData)YD
PIVOT(MAX([date])
FOR [Name] IN (A1,A2,A5))P;
GO
DROP TABLE #YourData;
If you want to pivot but keep all your related values in the same field rather than creating fake rows, you can use STRING_AGG. You can replace the "," separator with a CHAR(10), or perhaps an HTML to add a line break. You won't see it in SSMS but depending on your front end, that would render the two items, one under each other.
DROP TABLE IF EXISTS #YourData
CREATE TABLE #YourData (Name char(2),
Date datetime2(0));
GO
INSERT INTO #YourData (Name,
Date)
VALUES('A1','2022-10-01T12:13:00'),
('A2','2022-10-02T14:15:00'),
('A2','2022-10-02T15:16:00'),
('A5','2022-10-03T16:19:00');
GO
DECLARE #Seperator VARCHAR(10) = ' , '
SELECT
[A1] , [A2], [A3], [A4], [A5]
FROM
(
SELECT name, STRING_AGG(date,#Seperator) AS Dates
FROM #YourData
GROUP BY Name
) AS SourceTable
PIVOT
(
MAX(Dates)
FOR Name IN ([A1], [A2], [A3], [A4], [A5])
) AS PivotTable;

Pivot a table in SQL without an aggregate

I have a table with the following format:
ID CODE NAME VALUE
p1 p deflect Yes
a1 d source Prim
p1 p source Dim
I want to pivot to have the following:
ID CODE deflect source
p1 p Yes DIM
a1 d NULL Prim
This is my current code:
SELECT *
from
(
select [ID], [CODE], [NAME], [VALUE]
FROM [DATABASE].[dbo].[TABLE]
) SOURCE_TABLE
pivot
(
max(VALUE)
for [NAME] in ('deflect', 'source')
) PIVOT_TABLE;
But I'm getting:
Incorrect syntax near 'deflect'.
How would you write the pivot code for this?
Why don't you use the conditional aggregation as follows:
select [ID], [CODE],
max(case when [NAME] = 'deflect' then [VALUE] end) as deflect,
max(case when [NAME] = 'source' then [VALUE] end) as source_
FROM [DATABASE].[dbo].[TABLE]
group by [ID], [CODE]

SQL Server pivot values with different data types

i am trying to pivot all values in different type in MSSQL 2016. I could not find a way how i can pivot different data types..
The first table are initial form / structure. The second table is the desired shape.
I was trying the following SQL code to pivot my values
SELECT
[id] AS [id],
FIRSTNAME,
LASTNAME,
BIRTHDATE,
ADDRESS,
FLAG,
NUMBER
FROM (
SELECT
[cm].[key] AS [id],
[cm].[column] AS [column],
[cm].[string] AS [string],
[cm].[bit] AS [bit],
[cm].[xml] AS [xml],
[cm].[number] AS [number],
[cm].[date] AS [date]
FROM [cmaster] AS [cm]
) AS [t]
PIVOT (
MAX([string]) --!?!?
FOR [column] IN (
FIRSTNAME,
LASTNAME,
BIRTHDATE,
ADDRESS,
FLAG,
NUMBER
)
) AS [p]
I think your best bet is to use conditional aggregation, e.g.
SELECT cm.id,
FIRSTNAME = MAX(CASE WHEN cm.[property] = 'firstname' THEN cm.[string] END),
LASTNAME = MAX(CASE WHEN cm.[property] = 'lastname' THEN cm.[string] END),
BIRTHDATE = MAX(CASE WHEN cm.[property] = 'birthddate' THEN cm.[date] END),
FLAG = CONVERT(BIT, MAX(CASE WHEN cm.[bit] = 'flag' THEN CONVERT(TINYINT, cm.[boolean]) END)),
NUMBER = MAX(CASE WHEN cm.[property] = 'number' THEN cm.[integer] END)
FROM cmaster AS cm
GROUP BY cm.id;
Although, as you can see, your query becomes very tightly coupled to your EAV model, and why EAV is considered an SQL antipattern. Your alternative is to create a single column in your subquery and pivot on that, but you have to convert to a single data type, and lose a bit of type safety:
SELECT id, FIRSTNAME, LASTNAME, BIRTHDATE, ADDRESS, FLAG, NUMBER
FROM (
SELECT id = cm.[key],
[column] = cm.[column],
Value = CASE cm.type
WHEN 'NVARCHAR' THEN cm.string
WHEN 'DATETIME' THEN CONVERT(NVARCHAR(MAX), cm.date, 112)
WHEN 'XML' THEN CONVERT(NVARCHAR(MAX), cm.xml)
WHEN 'BIT' THEN CONVERT(NVARCHAR(MAX), cm.boolean)
WHEN 'INT' THEN CONVERT(NVARCHAR(MAX), cm.integer)
END
FROM cmaster AS cm
) AS t
PIVOT
(
MAX(Value)
FOR [column] IN (FIRSTNAME, LASTNAME, BIRTHDATE, ADDRESS, FLAG, NUMBER)
) AS p;
In order to make the result as per your request, first thing is we need to bring the data in to one format which is compatible with all data types. VARCHAR is ideal for that. Then prepare the base table using a simple select query, then PIVOT the result.
In the last projection, if you want, you can convert the data back in to the original format.
This query can be written dynamically as well to obtain the result as records are added. Here I provide the static answer according to your data. If you need a more generic dynamic answer, let me know. So I can post here.
--data insert scripts I used:
CREATE TABLE First_Table
(
[id] int,
[column] VARCHAR(10),
[string] VARCHAR(20),
[bit] BIT,
[xml] [xml],
[number] INT,
[date] DATE
)
SELECT GETDATE()
INSERT INTO First_Table VALUES(1, 'FIRST NAME', 'JOHN' , NULL, NULL, NULL, NULL)
INSERT INTO First_Table VALUES(1, 'LAST NAME', 'DOE' , NULL, NULL, NULL, NULL)
INSERT INTO First_Table VALUES(1, 'BIRTH DATE', NULL , NULL, NULL, NULL, '1985-02-25')
INSERT INTO First_Table VALUES(1, 'ADDRESS', NULL , NULL, 'SDFJDGJOKGDGKPDGKPDKGPDKGGKGKG', NULL, NULL)
INSERT INTO First_Table VALUES(1, 'FLAG', NULL , 1, NULL, NULL, NULL)
INSERT INTO First_Table VALUES(1, 'NUMBER', NULL , NULL, NULL, 20, NULL)
SELECT
PIVOTED.* FROM
(
--MAKING THE BASE TABLE FOR PIVOT
SELECT
[id]
,[column] AS [COLUMN]
, CASE WHEN [column] = 'FIRST NAME' then [string]
WHEN [column] = 'LAST NAME' then [string]
WHEN [column] = 'BIRTH DATE' then CAST([date] AS VARCHAR(100))
WHEN [column] = 'ADDRESS' then CAst([xml] as VARCHAR(100))
WHEN [column] = 'FLAG' then CAST([bit] AS VARCHAR(100))
else CAST([number] AS VARCHAR(100)) END AS [VALUE]
FROM First_Table
) AS [P]
PIVOT
(
MIN ([P].[VALUE])
FOR [column] in ([FIRST NAME],[LAST NAME],[BIRTH DATE],[ADDRESS],[FLAG],[NUMBER])
) AS PIVOTED
RESULT:
SQL:
SELECT
            ID,
            FIRSTNAME,
            ...,
            FLAG = CAST (FLAG AS INT),
            ...
FROM
            (
            SELECT
                        *
            FROM
                        (
                        SELECT
                                    f.ID,
                                    f.PROPERTY,
                                    f.STRING + f."INTEGER" + f.DATETIME + f.BOLLEAN + f.XML AS COLS
                        FROM
                                    FIRSTTBL f)
            PIVOT(
                        min(COLS) FOR PROPERTY IN
                                    (
                                    'firstname' AS firstname,
                                    'lastname' AS lastname,
                                    'birthdate' AS birthdate,
                                    'address' AS address,
                                    'flag' AS flag,
                                    'number' AS "NUMBER"
                                    )
                        )
            )
According to the original table, there is one and only one non-null value among STRING, INTEGER, DATETIME, BOLLEAN and XML columns for any row, so we just need to get the first non-null value and assign it to the corresponding new column. It is not difficult to perform the transposition using PIVOT function, except that we need to handle different data types according to the SQL rule, which requires that each column have a consistent type. For this task, first we need to convert the combined column values into a string, perform row-to-column transposition, and then convert string back to the proper types. When there are a lot of columns, the SQL statement can be tricky, and dynamic requirements are even hard to achieve.
Yet it is easy to write the code using the open-source esProc SPL:
 
A
1
=connect("MSSQL")
2
=A1.query#x("SELECT * FROM FIRSTTBL")
3
=A2.pivot(ID;PROPERTY,~.array().m(4:).ifn();"firstname":"FIRSTNAME", "lastname":"LASTANME","birthdate":"BIRTHDAY","address":"ADDRESS","flag":"FLAG","number":"NUMBER")
SPL does not require that data in the same column have consistent type. It is easy for it to maintain the original data types while performing the transposition.

PIVOT over two columns

is there a more elegant way to implement a PIVOT on the following table - basically I am trying to count the typeid's for ever userid, as well as get the first value for each type and pivot the types and the values (using sql server 2012):
This is the expected result:
And here is what I came up with:
SELECT userid ,
MAX([1]) [1] ,
MAX([2]) [2] ,
MAX(value1) [value1] ,
MAX(value2) [value2]
FROM ( SELECT userid ,
typeid ,
FIRST_VALUE(value) OVER ( PARTITION BY userid, typeid ORDER BY testid ) [value] ,
'value' + CONVERT(VARCHAR(20), typeid) [valuepivot]
FROM dbo.test
) t PIVOT( COUNT(typeid) FOR typeid IN ( [1], [2] ) ) as pivottype
PIVOT( MAX(value) FOR [valuepivot] IN ( [value1], [value2] ) ) as pivottype2
GROUP BY userid
Table Schema:
CREATE TABLE [dbo].[Test](
PIVOT( MAX(value) FOR [valuepivot] IN ( [value1], [value2] ) ) as pivottype2
[TestID] [int] PRIMARY KEY IDENTITY(1,1) ,
[UserID] [int] NOT NULL,
[TypeID] [int] NOT NULL,
[Value] [varchar](100) NOT NULL
)
GROUP BY userid
I would just do this using conditional aggregation:
select userid,
sum(case when typeid = 1 then 1 else 0 end) as test1,
sum(case when typeid = 2 then 1 else 0 end) as test2,
max(case when typeid = 1 then value end) as test1,
max(case when typeid = 2 then value end) as test2
from (select t.*,
row_number() over (partition by userid, typeid order by testid) as seqnum
from test
) t
group by userid;
Whether it is simpler or not is a matter of opinion.

Create Distinct Column Values as Extra Rows

Any tricks anyone can share on how to manipulate the following table
ID TYPE Name Description
1 X A DESC_A
2 X B DESC_B
3 Z C DESC_C
to this view?
NAME_X DESCRIPTION_X
A DESC_A
B DESC_B
NAME_Z DESCRIPTION_Z
C DESC_C
For every distinct column, I would like to create a custom row for every distinct value in the 'TYPE' column. In this example, the custom row is created by appending the TYPE value to 'NAME_' and 'DESCRIPTION_'.
Thanks!
Try this:
create view vwTestDistinctData
as
select [type], [Description]
from testdistinctdata
union all
select
'NAME_' + [type] as [Type],
'DESCRIPTION_' + [type] as [Description]
from testdistinctdata
group by [type]
go
Edit: Return some meta data from the view:
alter view dbo.yourView
as
with c_Distinct([type])
as ( select distinct [Type]
from dbo.yourTable
)
select [Sort] = 0,
[Type],
Name,
[Description]
from dbo.yourTable
union all
select [Sort] = 1,
[Type],
'NAME_'+[Type],
'DESCRIPTION_'+[Type]
from c_Distinct
And then perform the ordering when selecting from the View:
select *
from yourView
order by [Type] asc, [Sort] desc