How to append metadata to a grouped selection by primary key - sql-server

I have a list of favorites like:
Sample Data
| key | item_id | list_name | customer_id |meta |
|-----|---------|-----------|--------------|---------------|
| 1 | A-11 | aa11 | 001 | unique-data-1 |
| 2 | A-11 | bb22 | 001 | unique-data-2 |
| 3 | A-26 | cc33 | 001 | unique-data-3 |
| 4 | A-28 | aa11 | 002 | unique-data-4 |
| 5 | J-52 | aa11 | 001 | unique-data-5 |
| 6 | X-53 | aa11 | 001 | unique-data-6 |
Desired Output
for #item_id nvarchar(20) = 'A-11'
| key | isFavorited | list_name | meta |
|-----|-------------|-----------|---------------|
| 1 | Y | aa11 | unique-data-1 |
| 2 | Y | bb22 | unique-data-2 |
| 3 | N | cc33 | unique-data-3 |
And would like to return a selection of all available lists, as well as whether or not a particular item is part of that list, with its meta data.
declare #item_id nvarchar(20) = 'A-11'
declare #customer_id nvarchar(20) = 001
select
[key],
[isFavorited] = max(case when [item_id] = #item_id then 'Y' else 'N' end)
[list_name]
[meta]
from favorites
where customer_id = #customer_id
group by [list_name], [key], [meta]
Issues when trying various methods:
The issue I'm having is that since the meta is unique the group by destroys the uniqueness of the select
A cross apply like the following doesn't apply the correct meta based on a matching key.
cross apply (
select top 1
[meta]
from favorites
where customer_id = #customer_id
)
When selecting by row number, the actual key to join back to is lost, so I'm unable to join the meta.
"noRow" = row_number() over(order by h_po_no asc)
I'd like to
Pass in an item_id and customer_id
Return all lists for that customer
Get favorite status for each list of passed in item_id
An item is flagged favorite if it matches both list_name and item_id for a given customer_id
Get row primary key and meta data
How can I return a distinct selection of list_name, isFavorite status, key, and it's meta?

To return the desired output you don't need any aggregation at all. A simple case expression and a where clause will accomplish this.
declare #Favorites table
(
MyKey int
, item_id varchar(10)
, list_name varchar(10)
, customer_id varchar(10)
, meta varchar(20)
)
insert #Favorites values
(1, 'A-1', 'list-1', '001', 'unique-data-1')
, (2, 'A-1', 'list-2', '001', 'unique-data-2')
, (3, 'A-2', 'list-3', '001', 'unique-data-3')
, (4, 'A-2', 'list-1', '002', 'unique-data-1')
select *
from #Favorites
declare #item_id nvarchar(20) = 'A-1'
, #customer_id nvarchar(20) = '001'
select f.MyKey
, isFavorited = case when f.item_id = #item_id then 'Y' else 'N' end
, listName = f.list_name
, f.meta
from favorites f
where f.customer_id = #customer_id
order by f.MyKey

Test Parameters
declare #item_id nvarchar(20) = 'A-11'
declare #customer_id nvarchar(20) = 001
Solution
drop table if exists #tmp
;with cte as (
select
[key],
[list_name],
[rn] = row_number() over (partition by list_name order by list_name desc)
from favorites
where customer_id = #customer_id
group by [list_name], [key], [meta]
)
select *
into #tmp
from cte
where [rn] = 1
select
[i],
[json],
#t.[tmp]
from #tmp #t
inner join (
select
[isFavorited] = max(case when [item_id] = #item_id then 'Y' else 'N' end)
[list_name]
from favorites
where customer_id = #customer_id
group by [list_name]
) j
on j.list_name = #t.list_name

Related

Displaying data in a different way

I have a log table that looks like this:
ProductId | OldDescription | NewDescription | OldTagId | NewTagId |
-------------------------------------------------------------------
12345 | description1 | description2 | 1 | 5 |
and I want to display it this way:
ProductId | ChangeId | OldVal | NewVal |
----------------------------------------------------------
12345 | 1 | description1 | description2 |
12345 | 2 | 1 | 5 |
Where the data in the ChangeId corresponds to the type of the value changed (Description, TagId)
How could I approach this?
Thank you
Just another option via CROSS APPLY
Example
Declare #YourTable Table ([ProductId] varchar(50),[OldDescription] varchar(50),[NewDescription] varchar(50),[OldTagId] int,[NewTagId] int)
Insert Into #YourTable Values
(12345,'description1','description2',1,5)
Select ProductID
,B.*
From #YourTable A
Cross Apply ( values (1,[OldDescription],[NewDescription])
,(2,left([OldTagId],25),left([NewTagId],25))
) B(ChangeID,OldVal,NewVal)
Returns
ProductID ChangeID OldVal NewVal
12345 1 description1 description2
12345 2 1 5
Just for fun:
I saw the comment of 30 columns. If performance is NOT essential, here is option that will dynamically pivot your data without actually using dynamic SQL
Select *
From (
Select ProductID
,C.*
From #YourTable A
Cross Apply ( values (cast((Select A.* for XML RAW) as xml))) B(XMLData)
Cross Apply (
Select Item = left(xAttr.value('local-name(.)', 'varchar(100)'),3)+'Val'
,Value = xAttr.value('.','varchar(100)')
,ChangeID = ((row_number() over (order by (select null)) - 1 ) / 2)+1
From XMLData.nodes('//#*') xNode(xAttr)
Where xAttr.value('local-name(.)','varchar(100)') not in ('ProductID','Other','ColumnsToExclude')
) C
) src
Pivot ( max(Value) for Item in ([OldVal],[NewVal]) ) pvt
See if something like that works for you:
SELECT ProductId,
1 AS ChangeId,
OldDescription AS OldVal,
NewDescription AS NewVal
FROM log
UNION
SELECT ProductId,
2 AS ChangeId,
OldTagId AS OldVal,
NewTagId AS NewVal
FROM log
ORDER BY ProductId,
ChangeId

SQL Server split field and create addition rows with values from main row

I have a table with rows and in one field there are values like this A,B,C
Table 'Mytable':
|ID | Date | MyValue | SplitID |
|1 | 2019-12-17 | A | |
|2 | 2019-12-15 | A,B | |
|3 | 2019-12-16 | B,C | |
Result should be:
|1 | 2019-12-17 | A | 1 |
|2 | 2019-12-15 | A | 2 |
|4 | 2019-12-15 | B | 2 |
|3 | 2019-12-16 | B | 3 |
|5 | 2019-12-16 | C | 3 |
(Sorry, I could not find HOW to format a table in the Stackoverflow help)
I tried a inline table function which splits the Field Myvalue into more lines but could not pass my rows with
charindex(',',[MyValue])>0
from MyTable as input lines.
The code is this:
ALTER function [dbo].[fncSplitString](#input Varchar(max), #Splitter Varchar(99), #ID int)
returns table as
Return
with tmp (DataItem, ix, ID) as
( select LTRIM(#input) , CHARINDEX('',#Input), #ID --Recu. start, ignored val to get the types right
union all
select LTRIM(Substring(#input, ix+1,ix2-ix-1)), ix2, #ID
from (Select *, CHARINDEX(#Splitter,#Input+#Splitter,ix+1) ix2 from tmp) x where ix2<>0
) select DataItem,ID from tmp where ix<>0
Thanks for help
Michael
You can try the following query.
Create table #Temp(
Id int,
DateField Date,
MyValue Varchar(10),
SplitID int
)
CREATE FUNCTION [dbo].[SplitPra] (#Value VARCHAR(MAX), #delimiter CHAR)
RETURNS #DataResult TABLE([Position] TINYINT IDENTITY(1,1),[Value] NVARCHAR(128))
AS
BEGIN
DECLARE #XML xml = N'<r><![CDATA[' + REPLACE(#Value, #delimiter, ']]></r><r><![CDATA[') + ']]></r>'
INSERT INTO #DataResult ([Value])
SELECT RTRIM(LTRIM(T.c.value('.', 'NVARCHAR(128)')))
FROM #xml.nodes('//r') T(c)
RETURN
END
insert into #Temp Values(1, '2019-12-17', 'A', NULL),(2, '2019-12-15', 'A,B', NULL), (3, '2019-12-16', 'B,C', NULL)
Select
#Temp.Id, DateField, b.Value as MyValue, b.Id as SplitValue
from #Temp inner join (
select
Id, f.*
from
#Temp u
cross apply [dbo].[SplitPra](u.MyValue, ',') f
)b on #Temp.Id = b.Id
Drop table #Temp
This will give an output as shown below.
Id DateField MyValue SplitValue
---------------------------------
1 2019-12-17 A 1
2 2019-12-15 A 2
2 2019-12-15 B 2
3 2019-12-16 B 3
3 2019-12-16 C 3
You can find the live demo here.
I found this solution, i hope it will work for you. But i didn't use your function to solve this problem. Instead of that, i used cross apply function.You can find the query below:
-- Creating Test Table
CREATE TABLE #Test
(
ID int,
Date date,
MyValue nvarchar(max),
SplitID int
);
GO
-- Inserting data into test table
INSERT INTO #Test VALUES (1, '2019-12-17', 'A', NULL);
INSERT INTO #Test VALUES (2, '2019-12-15', 'A,B', NULL);
INSERT INTO #Test VALUES (3, '2019-12-16', 'B,C', NULL);
GO
-- Select query
SELECT
*,
(SELECT ID FROM test t1 WHERE t.Date = t1.date) AS SplitID
FROM
(
SELECT
ROW_NUMBER() OVER (ORDER BY ID) AS ID,
Date,
substring(A.value,1,
CASE WHEN charindex(',',rtrim(ltrim(A.value))) = 0 then LEN(A.value)
ELSE charindex(',',rtrim(ltrim(A.value))) -1 end) as MyValue
FROM Test
CROSS APPLY string_split (MyValue, ',') A) AS T
ORDER BY MyValue ASC;
And the result must be like that:
ID Date MyValue SplitID
1 2019-12-17 A 1
2 2019-12-15 A 2
3 2019-12-15 B 2
4 2019-12-16 B 3
5 2019-12-16 C 3

how to copy TreeView's rows from the same table and update with different ID column and copy the parent_id

I want copy the table and put different value on column Type= B and auto_increment id and copy the parent id
Table = Menu
Id | parent_id | order | section | name | url | type
100 | NULL | 7 | web | Tasks | ~/en/Tasks | A
102 | 100 | 1 | web | Pages | ~/en/Pages | A
103 | 100 | 4 | web | Category | ~/en/Category | A
104 | NULL | 3 | web | DLM | ~/en/DLM | A
105 | 104 | 6 | web | ONS | ~/en/ONS | A
106 | 104 | 2 | web | HBO | ~/en/HBO | A
107 | NULL | 7 | web | Tasks | ~/en/Tasks | B
108 | 107 | 1 | web | Pages | ~/en/Pages | B
109 | 107 | 4 | web | Category | ~/en/Category | B
110 | NULL | 3 | web | DLM | ~/en/DLM | B
111 | 110 | 6 | web | ONS | ~/en/ONS | B
112 | 110 | 2 | web | HBO | ~/en/HBO | B
This probably isn't the most efficient, but it gets the job done. It assumes that name is unique. I left out columns unnecessary to the example. Also, you can't put a variable into the identity clause, so that needs to be wrapped in an EXEC
IF OBJECT_ID (N'paths', N'U') IS NOT NULL
DROP TABLE paths
IF OBJECT_ID (N'new_paths', N'U') IS NOT NULL
DROP TABLE new_paths
CREATE TABLE paths (
id INT,
parent_id INT,
name NVARCHAR(20)
)
INSERT INTO dbo.paths
(id,parent_id,name)
VALUES
(100, NULL, 'Tasks'),
(102, 100, 'Pages'),
(103, 100, 'Category'),
(104, NULL, 'DLM'),
(105, 104, 'ONS'),
(106, 104, 'HBO')
DECLARE #start_value INT
SET #start_value = (SELECT MAX(id) FROM paths) + 1
DECLARE #sql nvarchar(1000)
SET #sql = N'
CREATE TABLE new_paths (
id INT IDENTITY(' + CAST(#start_value AS nvarchar) + ',1),
parent_id INT,
name NVARCHAR(20)
)
'
EXEC sp_executesql #stmt = #sql
INSERT INTO new_paths (parent_id,name)
SELECT Parent_id, name FROM dbo.paths
;WITH mappings AS (
SELECT n.*, p.id AS old_id
FROM new_paths n
INNER JOIN paths p
ON p.name = n.name
)
UPDATE n
SET n.parent_id = m.id
FROM new_paths n
INNER JOIN mappings m
ON m.old_id = n.parent_id
--SELECT * FROM new_paths
Please see below approach to resolve an issue, ask questions in the comments if something is unclear, I have added some explanation in code comments
EDITED, to manage GUID (as per comment)
-- declare table var
declare #table table ([Increment] int identity(1,1), Id uniqueidentifier, [parent_id] nvarchar(50), [order] int, [section] nvarchar(50), [name] nvarchar(50), [url] nvarchar(50), [type] nvarchar(50))
-- insert values into this table
insert into #table
select [Id],
[parent_id],
[order],
[section],
[name],
[url],
'B'
from your_table
where [type] = 'A'
-- loop your temp table
declare #max_temp int = (select max(Increment) from #table)
declare #curr int = 1
declare #parent_value uniqueidentifier = null
while (#curr <= #max_temp)
begin
-- do diffrent inserts depend on parent_id value
if (select parent_id from #table) = null
begin
-- set below var, it will be used in next insert where parent_id is not null
set #parent_value = (select Id from #table where Increment = #curr)
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
[parent_id],
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
else
begin
insert into your_table ([parent_id], [order], [section], [name], [url], [type])
select
isnull(#parent_value, [parent_id]),
[order],
[section],
[name],
[url],
[type]
from #table
where Id = #curr
end
-- update current
set #curr = #curr + 1
end

SQL recursion with full hierarchy

This is what my query looks like right now:
with allmembers (objectid, parentid, name, parentname, recursion) as
(
-- anchor elements: where parentid = 25
select objectid, parentid, name, name as parentname, 0 as recursion
from orgs as orgs1
where parentid = 25
-- recursion begins here
union all
select orgs2.objectid, orgs2.parentid, orgs2.name, orgs3.name as parentname, recursion + 1
from orgs as orgs2
join allmembers as orgs3 on orgs2.parentid = orgs3.objectid
)
-- we select all the results
select *
from allmembers
It selects the orgs (organizations) from a list, where the parentid is 25 (these are the "root organizations") and joins them with all their child organizations, recursively, until there are none left. So we get a list of organizations and their parents.
My problem is that I get only the direct child/parents relationsships:
name | parentname
Sales | All_Employees
Direct Sales | Sales
What is lost in this process is that "Direct Sales" is also a member of "All_Employees", indirectly, through "Sales".
So I would rather have the following result added:
name | parentname
Sales | All_Employees
Direct Sales | Sales
Direct Sales | All_Employees
How to achieve this?
Without going too far and getting functions involved, would using a materialized path suffice for your needs?
create table orgs (objectid int, name varchar(128), parentid int);
insert into orgs values
(26,'All Employees', 25)
,(27,'Sales', 26)
,(28,'Direct Sales',27);
with allmembers as (
-- anchor elements: where parentid = 25
select
objectid
, parentid
, name
, parentname = convert(varchar(128),'')
, rootname = name
, recursion = convert(int,0)
, name_path = convert(varchar(256),name)
from orgs
where parentid = 25
-- recursion begins here
union all
select
c.objectid
, c.parentid
, c.name
, parentname = p.name
, rootname = p.rootname
, recursion = p.recursion + 1
, name_path = convert(varchar(256),p.name_path + ' > ' + c.name)
from orgs as c
join allmembers as p on c.parentid = p.objectid
)
-- we select all the results
select *
from allmembers
returns:
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+
| objectid | parentid | name | parentname | rootname | recursion | name_path |
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+
| 26 | 25 | All Employees | | All Employees | 0 | All Employees |
| 27 | 26 | Sales | All Employees | All Employees | 1 | All Employees > Sales |
| 28 | 27 | Direct Sales | Sales | All Employees | 2 | All Employees > Sales > Direct Sales |
+----------+----------+---------------+---------------+---------------+-----------+--------------------------------------+

Update several columns with latest values from another table

Here's the data:
[ TABLE_1 ]
id | prod1 | date1 | prod2 | date2 | prod3 | date3 |
---|--------|--------|--------|--------|--------|-------|
1 | null | null | null | null | null | null |
2 | null | null | null | null | null | null |
3 | null | null | null | null | null | null |
[ TABLE_2 ]
id | date | product |
-----|-------------|-----------|
1 | 20140101 | X |
1 | 20140102 | Y |
1 | 20140103 | Z |
2 | 20141201 | data |
2 | 20141201 | Y |
2 | 20141201 | Z |
3 | 20150101 | data2 |
3 | 20150101 | data3 |
3 | 20160101 | X |
Both tables have other columns not listed here.
date is formatted: yyyymmdd and datatype is int.
[ TABLE_2 ] doesn't have empty rows, just tried to make sample above more readable.
Here's the Goal:
I need to update [ TABLE_1 ] prod1,date1,prod2,date2,prod3,date3
with product collected from [ TABLE_2 ] with corresponding date values.
Data must be sorted so that "latest" product becomes prod1,
2nd latest product will be prod2 and 3rd is prod3.
Latest product = biggest date (int).
If dates are equal, order doesn't matter. (see id=2 and id=3).
Updated [ TABLE_1 ] should be:
id | prod1 | date1 | prod2 | date2 | prod3 | date3 |
---|--------|----------|--------|----------|--------|----------|
1 | Z | 20140103 | Y | 20140102 | X | 20140101 |
2 | data | 20141201 | Y | 20141201 | Z | 20141201 |
3 | X | 20160101 | data2 | 20150101 | data3 | 20150101 |
Ultimate goal is to get the following :
[ TABLE_3 ]
id | order1 | order2 | order3 | + Columns from [ TABLE_1 ]
---|--------------------|----------------------|------------|--------------------------
1 | 20140103:Z | 20140102:Y | 20140103:Z |
2 | 20141201:data:Y:Z | NULL | NULL |
3 | 20160101:X | 20150101:data2:data3 | NULL |
I have to admit this exceeds my knowledge and I haven't tried anything.
Should I do it with JOIN or SELECT subquery?
Should I try to make it in one SQL -clause or perhaps in 3 steps,
each prod&date -pair at the time ?
What about creating [ TABLE_3 ] ?
It has to have columns from [ TABLE_1 ].
Is it easiest to create it from [ TABLE_2 ] -data or Updated [ TABLE_1 ] ?
Any help would be highly appreciated.
Thanks in advance.
I'll post some of my own shots on comments.
After looking into it (after my comment), a stored procedure would be best, that you can call to view the data as a pivot, and do away with TABLE_1. Obviously if you need to make this dynamic, you'll need to look into dynamic pivots, it's a bit of a hack with CTEs:
CREATE PROCEDURE DBO.VIEW_AS_PIVOTED_DATA
AS
;WITH CTE AS (
SELECT ID, [DATE], 'DATE' + CAST(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY [DATE] DESC) AS VARCHAR) AS [RN]
FROM TABLE_2)
, CTE2 AS (
SELECT ID, PRODUCT, 'PROD' + CAST(ROW_NUMBER() OVER(PARTITION BY ID ORDER BY [DATE] DESC) AS VARCHAR) AS [RN]
FROM TABLE_2)
, CTE3 AS (
SELECT ID, [DATE1], [DATE2], [DATE3]
FROM CTE
PIVOT(MAX([DATE]) FOR RN IN ([DATE1],[DATE2],[DATE3])) PIV)
, CTE4 AS (
SELECT ID, [PROD1], [PROD2], [PROD3]
FROM CTE2
PIVOT(MAX(PRODUCT) FOR RN IN ([PROD1],[PROD2],[PROD3])) PIV)
SELECT A.ID, [PROD1], [DATE1], [PROD2], [DATE2], [PROD3], [DATE3]
FROM CTE3 AS A
JOIN CTE4 AS B
ON A.ID=B.ID
Construction:
WITH ranked AS (
SELECT [id]
,[date]
,[product]
,row_number() over (partition by id order by date desc) rn
FROM [sistemy].[dbo].[TABLE_2]
)
SELECT id, [prod1],[date1],[prod2],[date2],[prod3],[date3]
FROM
(
SELECT id, type+cast(rn as varchar(1)) col, value
FROM ranked
CROSS APPLY
(
SELECT 'date', CAST([date] AS varchar(8))
UNION ALL
SELECT 'prod', product
) ca(type, value)
) unpivoted
PIVOT
(
max(value)
for col IN ([prod1],[date1],[prod2],[date2],[prod3],[date3])
) pivoted
You need to take a few steps to achive the aim.
Rank your products by date:
SELECT [id]
,[date]
,[product]
,row_number() over (partition by id order by date desc) rn
FROM [sistemy].[dbo].[TABLE_2]
Unpivot your date and product columns into one column. You can use UNPIVOT OR CROSS APPLY statements. I prefer CROSS APPLY
SELECT id, type+cast(rn as varchar(1)) col, value
FROM ranked
CROSS APPLY
(
SELECT 'date', CAST([date] AS varchar(8))
UNION ALL
SELECT 'prod', product
) ca(type, value)
or the same result using UNPIVOT
SELECT id, type+cast(rn as varchar(1)) col, value
FROM (
SELECT [id],
rn,
CAST([date] AS varchar(500)) date,
CAST([product] AS varchar(500)) prod
FROM ranked) t
UNPIVOT
(
value FOR type IN (date, product)
) unpvt
and at last you use PIVOTE and get a result.

Resources