How to write the query? - sql

I have one table that contains customers (The goal of this table was to be able to add fields without DB-Update). The table looks like this:
CustId Property PropertyValue
1 Name Smith
1 Email smith#gmail.com
2 Name Donalds
2 Email donalds#gmail.com
3 Name john
(The customer 3 has no entry for "Email" in the table)
Expected result: I want to get one line per client (Mail) and if the customer has no email, display still one line with NULL.
CustId Property PropertyValue
1 Email smith#gmail.com
2 Email donalds#gmail.com
3 Email NULL
Has someone the solution ?

Query 1
Select t1.CustId
, ISNULL(t2.Property ,'Email') AS Property
, t2.PropertyValue
FROM TableName t1
LEFT JOIN TableName t2 ON t1.CustId = t2.CustId
AND t2.Property = 'Email'
WHERE t1.Property = 'Name'
Result Set 1
╔════════╦══════════╦═══════════════════╗
║ CustId ║ Property ║ PropertyValue ║
╠════════╬══════════╬═══════════════════╣
║ 1 ║ Email ║ smith#gmail.com ║
║ 2 ║ Email ║ donalds#gmail.com ║
║ 3 ║ Email ║ NULL ║
╚════════╩══════════╩═══════════════════╝
Query 2
Another query for a more readable result set should look something like....
Select t1.CustId
, t1.PropertyValue [CustomerName]
, t2.PropertyValue [CustomerEmail]
FROM TableName t1
LEFT JOIN TableName t2 ON t1.CustId = t2.CustId
AND t2.Property = 'Email'
WHERE t1.Property = 'Name'
Result Set 2
╔════════╦══════════════╦═══════════════════╗
║ CustId ║ CustomerName ║ CustomerEmail ║
╠════════╬══════════════╬═══════════════════╣
║ 1 ║ Smith ║ smith#gmail.com ║
║ 2 ║ Donalds ║ donalds#gmail.com ║
║ 3 ║ john ║ NULL ║
╚════════╩══════════════╩═══════════════════╝

DECLARE #t TABLE (
CustId INT,
Property VARCHAR(50),
PropertyValue VARCHAR(50)
)
INSERT INTO #t (CustId, Property, PropertyValue)
VALUES
(1, 'Name', 'Smith'),
(1, 'Email', 'smith#gmail.com'),
(2, 'Name', 'Donalds'),
(2, 'Email', 'donalds#gmail.com'),
(3, 'Name', 'john')
SELECT CustId
, Name = 'Email'
, Value = MAX(CASE WHEN Property = 'Email' THEN PropertyValue END)
FROM #t
GROUP BY CustId

You can do it using a derived table containing all possible ID's , and then left joining only to the Emails on the original table:
SELECT t.custID,'EMAIL',s.PropertyValue
FROM(SELECT DISTINCT custID
FROM YourTable) t
LEFT OUTER JOIN YourTable s
ON(t.custID = s.custID and s.property = 'Email')
Can also be done with a correlated query:
SELECT DISTINCT t.CustID,'EMAIL',
(SELECT s.PropertyValue
FROM YourTable s
WHERE s.custID = t.custID and s.Property = 'Email')
FROM YourTable t

Self join with same table, property passed via variable
DECLARE #prop nvarchar(max) = 'Email'
SELECT DISTINCT c.CustId, #prop as Property, c1.PropertyValue
FROM yourtable c
LEFT JOIN yourtable c1
ON c.CustId = c1.CustId and c1.Property = #prop
Output will be as you posted in your question.

SELECT CustId
, MIN(CASE WHEN Property IS NULL THEN 'Email' ELSE Property END) Property
, MIN(PropertyValue) PropertyValue
FROM TableName
GROUP BY CustId
HAVING Property = 'Email';

Related

Returning MIN and MAX values and ignoring nulls - populate null values with preceding non-null value

Using a table of events, I need to return the date and type for:
the first event
the most recent (non-null) event
The most recent event could have null values, which in that case needs to return the most recent non-null value
I found a few articles as well as posts here on SO that are similar (maybe even identical) but am not able to decode or understand the solution - i.e.
Fill null values with last non-null amount - Oracle SQL
https://www.itprotoday.com/sql-server/last-non-null-puzzle
https://koukia.ca/common-sql-problems-filling-null-values-with-preceding-non-null-values-ad538c9e62a6
Table is as follows - there are additional columns, but I am only including 3 for the sake of simplicity. Also note that the first Type and Date could be null. In this case returning null is desired.
╔═══════╦════════╦════════════╗
║ Email ║ Type ║ Date ║
╠═══════╬════════╬════════════╣
║ A ║ Create ║ 2019-04-01 ║
║ A ║ Update ║ 2019-04-02 ║
║ A ║ null ║ null ║
╚═══════╩════════╩════════════╝
The output should be:
╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate ║ LastType ║ LastDate ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A ║ Create ║ 2019-04-01 ║ Update ║ 2019-04-02 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝
The first method I tried was to join the table to itself using a subquery that finds the MIN and MAX dates using case statements:
select
Email,
max(case when T1.Date = T2.Min_Date then T1.Type end) as FirstType,
max(case when T1.Date = T2.Min_Date then T1.Date end) as FirstDate,
max(case when T1.Date = T2.Max_Date then T1.Type end) as LastType,
max(case when T1.Date = T2.Max_Date then T1.Date end) as LastDate,
from
T1
join
(select
EmailAddress,
max(Date) as Max_Date,
min(Date) as Min_Date
from
Table1
group by
Email
) T2
on
T1.Email = T2.Email
group by
T1.Email
This seemed to work for the MIN values, but the MAX values would return null.
To solve the problem of returning the last non-value I attempted this:
select
EmailAddress,
max(Date) over (partition by EmailAddress rows unbounded preceding) as LastDate,
max(Type) over (partition by EmailAddress rows unbounded preceding) as LastType
from
T1
group by
EmailAddress,
Date,
Type
However, this gives a result of 3 rows, instead of 1.
I'll admit I don't quite understand analytic functions since I have not had to deal with them at length. Any help would be greatly appreciated.
Edit:
The aforementioned example is an accurate representation of what the data could look like, however the below example is the exact sample data that I am using.
Sample:
╔═══════╦════════╦════════════╗
║ Email ║ Type ║ Date ║
╠═══════╬════════╬════════════╣
║ A ║ Create ║ 2019-04-01 ║
║ A ║ null ║ null ║
╚═══════╩════════╩════════════╝
Desired Outcome:
╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate ║ LastType ║ LastDate ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A ║ Create ║ 2019-04-01 ║ Create ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝
Additional Use-Case:
╔═══════╦════════╦════════════╗
║ Email ║ Type ║ Date ║
╠═══════╬════════╬════════════╣
║ A ║ null ║ null ║
║ A ║ Create ║ 2019-04-01 ║
╚═══════╩════════╩════════════╝
Desired Outcome:
╔═══════╦═══════════╦════════════╦══════════╦════════════╗
║ Email ║ FirstType ║ FirstDate ║ LastType ║ LastDate ║
╠═══════╬═══════════╬════════════╬══════════╬════════════╣
║ A ║ null ║ null ║ Create ║ 2019-04-01 ║
╚═══════╩═══════════╩════════════╩══════════╩════════════╝
Use window functions and conditional aggregation:
select t.email,
max(case when seqnum = 1 then type end) as first_type,
max(case when seqnum = 1 then date end) as first_date,
max(case when seqnum_nonull = 1 and type is not null then type end) as last_type,
max(case when seqnum_nonull = 1 and type is not null then date end) as last_date
from (select t.*,
row_number() over (partition by email order by date) as seqnum,
row_number() over (partition by email, (case when type is null then 1 else 2 end) order by date) as seqnum_nonull
from t
) t
group by t.email;
As Spark SQL window functions support NULLS LAST|FIRST syntax you could use that then specify a pivot with multiple aggregates for rn values 1 and 2. I could do with seeing some more sample data but this work for your dataset:
%sql
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp;
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date), MAX(type) FOR rn In ( 1, 2 ) )
Rename the columns by supplying your required parts in the query, eg
-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
)
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) )
Alternately supply a column list, eg
-- Pivot and rename columns
;WITH cte AS
(
SELECT *, ROW_NUMBER() OVER( PARTITION BY email ORDER BY date NULLS LAST ) rn
FROM tmp
), cte2 AS
(
SELECT *
FROM cte
PIVOT ( MAX(date) AS Date, MAX(type) AS Type FOR rn In ( 1 First, 2 Last ) )
)
SELECT *
FROM cte2 AS (Email, FirstDate, FirstType, LastDate, LastType)
This simple query uses ROW_NUMBER to assign a row number to the dataset ordered by the date column, but using the NULLS LAST syntax to ensure null rows appear last in the numbering. The PIVOT then converts the rows to columns.

Pivot Column of varchar

I´m trying to PIVOT some data in a table, but I cannot do it because I do not find the way to do it using varchar columns. I have this table:
declare #table table(name VARCHAR(50) not null, occupation VARCHAR(MAX))
insert into #table values ('A','Doctor')
insert into #table values ('B','Doctor')
insert into #table values ('A','Professor')
insert into #table values ('A','Singer')
insert into #table values ('A','Actor')
SELECT
CASE WHEN occupation = 'Doctor' THEN NAME END AS Doctor,
CASE WHEN occupation = 'Professor' THEN NAME END AS Professor,
CASE WHEN occupation = 'Singer' THEN NAME END AS Singer,
CASE WHEN occupation = 'Actor' THEN NAME END AS Actor
FROM #table
Output:
Doctor Professor Singer Actor
A NULL NULL NULL
B NULL NULL NULL
NULL A NULL NULL
NULL NULL A NULL
NULL NULL NULL A
And for Pivot i get this output:
select * from
(
select name, occupation from #table ) src
pivot (
min(name)
for occupation in ([Doctor],[Professor],[Singer],[Actor])) as pvt
Doctor Professor Singer Actor
A A A A
And for min / max / function the pivot function gives me only partial output, for the count function I get number of records for doctor, singer etc.. But I need actual rows, not the row count.
What I need is this:
Doctor Professor Singer Actor
A A A A
B NULL NULL NULL
i.e suppose if we have 5 name for doctors we need to show 5 entries for doctor column.
I find this easier to express as conditional aggregation using a sequential number generated using `row_number():
select max(case when occupation = 'Doctor' then name end) as Doctor,
max(case when occupation = 'Professor' then name end) as Professor,
max(case when occupation = 'Singer' then name end) as Singer,
max(case when occupation = 'Actor' then name end) as Actor
from (select t.*,
row_number() over (partition by occupation order by name) as seqnum
from #table t
) t
group by seqnum
order by seqnum;
You can use PIVOT as you proposed, just add column with ROW_NUMBER:
SELECT [Doctor],[Professor],[Singer],[Actor]
FROM (SELECT name, occupation,
rn = ROW_NUMBER() OVER (PARTITION BY occupation ORDER BY occupation)
FROM #table ) AS src
PIVOT (
MIN(name)
FOR occupation IN ([Doctor],[Professor],[Singer],[Actor])
) AS pvt
LiveDemo
Output:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ ║ ║
╚════════╩═══════════╩════════╩═══════╝
EDIT:
You did not write how to handle more rows so consider this case.
Above solution will return:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ C ║ ║
╚════════╩═══════════╩════════╩═══════╝
vs:
╔════════╦═══════════╦════════╦═══════╗
║ Doctor ║ Professor ║ Singer ║ Actor ║
╠════════╬═══════════╬════════╬═══════╣
║ A ║ A ║ A ║ A ║
║ B ║ ║ ║ ║
║ ║ ║ C ║ ║
╚════════╩═══════════╩════════╩═══════╝
If you want second case use:
SELECT [Doctor],[Professor],[Singer],[Actor]
FROM (SELECT name, occupation,
rn = DENSE_RANK() OVER (ORDER BY Name)
FROM #table ) AS src
PIVOT (
MIN(name)
FOR occupation IN ([Doctor],[Professor],[Singer],[Actor])
) AS pvt
LiveDemo2

Getting Results from inner join differences on same row

Currently have sql returning a result set as below
WORKFLOWID UNMATCHEDVALUE MATCHEDADDRESS EXCEPTIONREASON
1001 UNIQUE ADDRESS1 (null)
1001 UNIQUE ADDRESS2 Some Value
What I am looking for is a result like this
WORKFLOWID UNMATCHEDVALUE MATCHEDADDRESS EXCEPTIONREASON MATCHEDADDRESS2 EXCEPTIONREASON2
1001 UNIQUE ADDRESS1 (null) ADDRESS2 Some Value
So the "variant" columns are MatchedAddress and Exception Reason, the other columns will be the same for each record. Note that for each workflow_id, will always have 2 rows coming back.
I have also created a fiddle to show the schema.
http://sqlfiddle.com/#!6/f7cde/3
Try this:
;WITH CTE AS
(
SELECT ws.id as WorkflowStepId,
ws.workflow_id as WorkflowId,
sg.unmatchValue as UnmatchedValue,
geo_address as MatchedAddress,
ws.exception_Value as ExceptionReason,
ROW_NUMBER() OVER(PARTITION BY ws.workflow_id ORDER BY ws.id) as RN
FROM workflow_step as ws
INNER JOIN workflow as gw
ON ws.workflow_id = gw.id
INNER JOIN super_group as sg
ON gw.super_group_id = sg.id
INNER JOIN alias on
ws.id = alias.workflow_step_id
)
SELECT WorkflowId,
UnmatchedValue,
MIN(CASE WHEN RN = 1 THEN MatchedAddress END) MatchedAddress,
MIN(CASE WHEN RN = 1 THEN ExceptionReason END) ExceptionReason,
MIN(CASE WHEN RN = 2 THEN MatchedAddress END) MatchedAddress2,
MIN(CASE WHEN RN = 2 THEN ExceptionReason END) ExceptionReason2
FROM CTE
GROUP BY WorkflowId,
UnmatchedValue
ORDER BY workflowId
Here is the modified sqlfiddle.
The results are:
╔════════════╦════════════════╦════════════════╦═════════════════╦═════════════════╦══════════════════╗
║ WORKFLOWID ║ UNMATCHEDVALUE ║ MATCHEDADDRESS ║ EXCEPTIONREASON ║ MATCHEDADDRESS2 ║ EXCEPTIONREASON2 ║
╠════════════╬════════════════╬════════════════╬═════════════════╬═════════════════╬══════════════════╣
║ 1001 ║ UNIQUE ║ ADDRESS1 ║ (null) ║ ADDRESS2 ║ Some Value ║
╚════════════╩════════════════╩════════════════╩═════════════════╩═════════════════╩══════════════════╝
Try this:
SELECT ws.workflow_id as WorkflowId, sg.unmatchValue as UnmatchedValue,
MAX(CASE WHEN ws.id = 1 THEN geo_address END) as MatchedAddress1,
MAX(CASE WHEN ws.id = 2 THEN geo_address END) as MatchedAddress2,
MAX(CASE WHEN ws.id = 1 THEN ws.exception_Value END) as ExceptionReason1,
MAX(CASE WHEN ws.id = 2 THEN ws.exception_Value END) as ExceptionReason2
FROM workflow_step as ws
INNER JOIN workflow as gw on ws.workflow_id = gw.id
INNER JOIN super_group as sg on gw.super_group_id = sg.id
inner JOIN alias on ws.id = alias.workflow_step_id
GROUP BY ws.workflow_id, sg.unmatchValue
SQL FIDDLE DEMO
Since I can't comment, I just wanted to point out that the answer given by Lamak is using a Common Table Expression. These are generally your best option for solving a recursion problem in sql.
This assumes you only have 2 address types. If you have more I would recommend creating a pivot table.
select a.*, MATCHEDADDRESS2,EXCEPTIONREASON2
from
(Select WORKFLOWID,UNIQUEVALUE,MATCHEDADDRESS,EXCEPTIONREASON
from "Your Table"
where MATCHEDADDRESS='ADDRESS1') a
join
(Select WORKFLOWID,UNIQUEVALUE,MATCHEDADDRESS as MATCHEDADDRESS2,EXCEPTIONREASON as XCEPTIONREASON2
from "Your Table"
where MATCHEDADDRESS='ADDRESS2') b
on a.WORKFLOWID=b.WORKFLOWID
and a.UNMATCHEDVALUE = b.UNMATCHEDVALUE

Using a SQL Server table, I need to create a new table recursive?

I have a simple table of related items, like so (SQL Server db)
id Item Parent
1 2 5
2 4 5
3 5 12
4 6 2
5 10 6
I'd like to output a table that shows, for each Item a full path of all inter-related items (up to 4 "levels"), like so
id Item ParentL1 ParentL2 ParentL3 ParentL4
1 2 5 12
2 4 5 12
3 5 12
4 6 2 5 12
5 10 6 2 5 12
Thanks!
This is the simple approach.
SELECT id, t1.Item as Item,
t1.Parent as ParentL1,
t2.Parent as ParentL2,
t3.Parent as ParentL3,
t4.Parent as ParentL4
FROM Items t1
LEFT JOIN Items t2 ON t1.Parent = t2.Id
LEFT JOIN Items t3 ON t2.Parent = t3.Id
LEFT JOIN Items t4 ON t3.Parent = t4.Id
The follwoing query should do the trick
SELECT t1.id, t1.Item, t1.Parent [ParentL1], t2.Parent [ParentL2], t3.Parent [ParentL3], t4.Parent [ParentL4]
FROM MyTable t1
LEFT JOIN MyTable t2
ON t1.Parent = t2.Item
LEFT JOIN MyTable t3
ON t2.Parent = t3.Item
LEFT JOIN MyTable t4
ON t3.Parent = t4.Item
Used the following to create the test table, MyTable to confirm the resultset
CREATE TABLE MyTable
(
id Int IDENTITY,
Item Int,
Parent Int
)
INSERT MyTable
VALUES (2, 5),
(4, 5),
(5, 12),
(6, 2),
(10, 6)
Ok, even though the LEFT JOINs are the simplest way in this case (when only 4 levels of recursion are needed), this is another option using recursive CTEs (SQL Server 2005+):
;WITH CTE AS
(
SELECT *, 1 RecursionLevel
FROM YourTable
UNION ALL
SELECT B.id, A.Item, B.Parent, RecursionLevel + 1
FROM CTE A
INNER JOIN YourTable B
ON A.Parent = B.Item
)
SELECT Item,
MIN(CASE WHEN RecursionLevel = 1 THEN Parent END) ParentL1,
MIN(CASE WHEN RecursionLevel = 2 THEN Parent END) ParentL2,
MIN(CASE WHEN RecursionLevel = 3 THEN Parent END) ParentL3,
MIN(CASE WHEN RecursionLevel = 4 THEN Parent END) ParentL4
FROM CTE
WHERE RecursionLevel <= 4
GROUP BY Item
This is the result:
╔══════╦══════════╦══════════╦══════════╦══════════╗
║ Item ║ ParentL1 ║ ParentL2 ║ ParentL3 ║ ParentL4 ║
╠══════╬══════════╬══════════╬══════════╬══════════╣
║ 2 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 4 ║ 5 ║ 12 ║ NULL ║ NULL ║
║ 5 ║ 12 ║ NULL ║ NULL ║ NULL ║
║ 6 ║ 2 ║ 5 ║ 12 ║ NULL ║
║ 10 ║ 6 ║ 2 ║ 5 ║ 12 ║
╚══════╩══════════╩══════════╩══════════╩══════════╝
And here is a sqlfiddle with a demo of this.

Concatenate row record base on group by in SQL Server 2008

I have one table (tblproduct) with fields: dept, product, qty.
sample data below:
dept product qty
IT A 2
IT B 1
PU C 4
SAL D 1
SER D 2
SER A 4
I want to create stored pro in sql server with the result below:
product qty remark
A 6 IT=2,SER=4
B 1 IT=1
C 4 PU=4
D 3 SAL=1,SER=2
this is my stored pro
select product,
sum(qty)
from tblproduct
group by product
order by product
Pls. any help. thanks.
SELECT
[product], SUM(qty) Total_Qty,
STUFF(
(SELECT ',' + dept + '=' + CAST(qty AS VARCHAR(10))
FROM TableName
WHERE [product] = a.[product]
FOR XML PATH (''))
, 1, 1, '') AS Remark
FROM TableName AS a
GROUP BY [product]
SQLFiddle Demo
OUTPUT
╔═════════╦═══════════╦═════════════╗
║ PRODUCT ║ TOTAL_QTY ║ REMARK ║
╠═════════╬═══════════╬═════════════╣
║ A ║ 6 ║ IT=2,SER=4 ║
║ B ║ 1 ║ IT=1 ║
║ C ║ 4 ║ PU=4 ║
║ D ║ 3 ║ SAL=1,SER=2 ║
╚═════════╩═══════════╩═════════════╝
Please try:
SELECT product, SUM(qty) Qty,
STUFF(
(SELECT ','+b.dept+'='+CAST(qty as nvarchar(10))
FROM YourTable b where b.product=a.product
FOR XML PATH(''),type).value('.','nvarchar(max)'), 1, 1, '')
from YourTable a
group by product