Related
I have sample data set as follows,
| Customer | |Detail | |DataValues |
|----------| |-------| |-----------|
| ID | |ID | |CustomerID |
| Name | |Name | |DetailID |
|Values |
| Customer | |Detail | |DataValues |
|----------| |---------| |-----------|
| 1 | Jack | | 1 | sex | | 1 | 1 | M |
| 2 | Anne | | 2 | age | | 1 | 2 | 30|
| 2 | 1 | F |
| 2 | 2 | 28|
and my desired outcome is below,
Name
Sex
Age
Jack
M
30
Anne
F
28
I have failed to come up with a correct SQL Query that returns anything.
Thanks in advance.
select Customers.Name, Details.Name, DataValues.Value from Customers
inner join DataValues on DataValues.CustomersID = Customers.ID
inner join Details on DataValues.DetailsID = Details.ID
The static way, assuming you know you want exactly Sex and Age:
WITH cte AS
(
SELECT c.Name, Type = d.Name, dv.[Values]
FROM dbo.DataValues AS dv
INNER JOIN dbo.Detail AS d
ON dv.DetailID = d.ID
INNER JOIN dbo.Customer AS c
ON dv.CustomerID = c.ID
WHERE d.Name IN (N'Sex',N'Age')
)
SELECT Name, Sex, Age
FROM cte
PIVOT (MAX([Values]) FOR [Type] IN ([Sex],[Age])) AS p;
If you need to derive the query based on all of the possible attributes, then you'll need to use dynamic SQL. Here's one way:
DECLARE #in nvarchar(max),
#piv nvarchar(max),
#sql nvarchar(max);
SELECT #in = STRING_AGG(N'N' + QUOTENAME(Name, char(39)), ','),
#piv = STRING_AGG(QUOTENAME(Name), ',')
FROM (SELECT Name FROM dbo.Detail GROUP BY Name) AS src;
SET #sql = N'WITH cte AS
(
SELECT c.Name, Type = d.Name, dv.[Values]
FROM dbo.DataValues AS dv
INNER JOIN dbo.Detail AS d
ON dv.DetailID = d.ID
INNER JOIN dbo.Customer AS c
ON dv.CustomerID = c.ID
WHERE d.Name IN (' + #in + N')
)
SELECT Name, ' + #piv + N'
FROM cte
PIVOT (MAX([Values]) FOR [Type] IN (' + #piv + N')) AS p;';
EXECUTE sys.sp_executesql #sql;
Working examples in this fiddle.
There's a lot to unpack here. Let's start with how to present demo data:
If you provide the DDL and DML for your data it makes it much easier for folks to work with:
DECLARE #Customer TABLE (ID INT, Name NVARCHAR(100))
DECLARE #Detail TABLE (ID INT, Name NVARCHAR(20))
DECLARE #DataValues TABLE (CustomerID INT, DetailID INT, [Values] NVARCHAR(20))
INSERT INTO #Customer (ID, Name) VALUES
(1, 'Jack'),(2, 'Anne')
INSERT INTO #Detail (ID, Name) VALUES
(1, 'Sex'),(2, 'Age')
INSERT INTO #DataValues (CustomerID, DetailID, [Values]) VALUES
(1, 1, 'M'),(1, 2, '30'),(2, 1, 'F'),(2, 2, '28')
This sets up your tables (as variables) and populates them with the demo data.
Next let's talk about the horrible schema here.
You should always to try avoid reserved words as column names too. Values is a keyword.
This should probably be a single customers table:
DECLARE #Genders TABLE (ID INT IDENTITY, Name NVARCHAR(20))
DECLARE #Customer1 TABLE (CustomerID INT IDENTITY, Name NVARCHAR(100), BirthDate DATETIME, GenderID INT NULL, Age AS (DATEDIFF(YEAR, BirthDate, CURRENT_TIMESTAMP)))
Notice I used BirthDate instead of Age. This is because a persons age will change over time, but their birth date will not. Attributes that are calculated based on another attribute shouldn't be stored (but if you want you can used a calculated column, as we are here). You'll also note that instead of explicitly defining gender in the customers table we instead will reference it by Gender ID. This is a lookup table.
If you had used a normalized schema your query would then look like:
/* Demo Data */
DECLARE #Genders TABLE (ID INT IDENTITY, Name NVARCHAR(20));
INSERT INTO #Genders (Name) VALUES
('Male'),('Female'),('Non-Binary');
DECLARE #Customer1 TABLE (CustomerID INT IDENTITY, Name NVARCHAR(100), BirthDate DATETIME, GenderID INT NULL, Age AS (DATEDIFF(YEAR, BirthDate, CURRENT_TIMESTAMP)));
INSERT INTO #Customer1 (Name, BirthDate, GenderID) VALUES
('Jack', '2000-11-03', 1),('Anne', '2000-11-01', 2),('Chris', '2001-05-13', NULL);
/* Query */
SELECT *
FROM #Customer1 c
LEFT OUTER JOIN #Genders g
ON c.GenderID = g.ID;
Now on to how to get the data you want from the structure you have. Anyway you do this is going to be acrobatic because we have to work against the schema.
/* Demo Data */
DECLARE #Customer TABLE (ID INT, Name NVARCHAR(100));
DECLARE #Detail TABLE (ID INT, Name NVARCHAR(20));
DECLARE #DataValues TABLE (CustomerID INT, DetailID INT, [Values] NVARCHAR(20));
INSERT INTO #Customer (ID, Name) VALUES
(1, 'Jack'),(2, 'Anne');
INSERT INTO #Detail (ID, Name) VALUES
(1, 'Sex'),(2, 'Age');
INSERT INTO #DataValues (CustomerID, DetailID, [Values]) VALUES
(1, 1, 'M'),(1, 2, '30'),(2, 1, 'F'),(2, 2, '28');
/* Query */
SELECT *
FROM (
SELECT d.Name AS DetailName, c.Name AS CustomerName, DV.[Values]
FROM #DataValues dv
INNER JOIN #Detail d
ON dv.DetailID = d.ID
INNER JOIN #Customer c
ON dv.CustomerID = c.ID
) a
PIVOT (
MAX([Values]) FOR DetailName IN (Sex,Age)
) p;
CustomerName Sex Age
-----------------------
Anne F 28
Jack M 30
This is the Scenario : I have a duplicate rows in my table with the same Id , Name and so on .
1) I have to find the duplicate row matching all the criteria ( this is done)
2) Delete them only if the criteria match
3) Use the id of the deleted record and update the existing row in the table
For this i have created a 2 temp table. Temp1 is the table with all the record. Temp2 consist of duplicated row.
IF OBJECT_ID('tempdb..#Temp1') IS NOT NULL
DROP TABLE #Temp1
IF OBJECT_ID('tempdb..#Temp2') IS NOT NULL
DROP TABLE #Temp2
IF OBJECT_ID('tempdb..#Temp3') IS NOT NULL
DROP TABLE #Temp3
CREATE Table #Temp1 (
Id int,
Name NVARCHAR(64),
StudentNo INT NULL,
ClassCode NVARCHAR(8) NULL,
Section NVARCHAR(8) NULL,
)
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(1,'Joe',123,'A1', 'I')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(1,'Joe',123,'A1', 'I')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(2,'Harry',113,'X2', 'H')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(2,'Harry',113,'X2', 'H')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(3,'Elle',121,'J1', 'E1')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(3,'Elle',121,'J1', 'E')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(8,'Jane',191,'A1', 'E')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(5,'Silva',811,'S1', 'SE')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(6,'Juan',411,'S2', 'SE')
INSERT INTO #Temp1 (Id, Name,StudentNo,ClassCode,Section) Values(7,'Carla',431,'S2', 'SE')
;WITH CTE AS (
select
ROW_NUMBER() over (partition by Id
, StudentNo
order by Id, StudentNo)as Duplicate_RowNumber
, * from #Temp1 )
select t1.Id,t1.Name,t1.StudentNo,t1.Section,t1.ClassCode
INTO #Temp2
from CTE as c INNER JOIN #Temp1 as t1 ON t1.Id = c.Id
and t1.StudentNo = t1.StudentNo
and c.Duplicate_RowNumber >1
-- this will have 6 rows all the duplicates are included
--select * from #Temp2
-- this is for output clause
DECLARE #inserted Table (Id int,
Name NVARCHAR(64),
StudentNo INT NULL,
ClassCode NVARCHAR(8) NULL,
Section NVARCHAR(8) NULL)
DELETE FROM #temp1
OUTPUT deleted.Id , deleted.Name ,deleted.StudentNo ,deleted.ClassCode ,deleted.Section into #inserted
WHERE EXISTS ( SELECT * FROM #Temp2 as t2
where #temp1.Id = t2.Id
and #temp1.Name = t2.Name
and #temp1.StudentNo = t2.StudentNo
and #temp1.ClassCode = t2.ClassCode
and #temp1.Section = t2.Section)
-- this is to check what is delete so that i can join it and update the table temp1
select * from #inserted
You can see below the query should not delete the last two highlighted column because the Section does not match. It should only delete matching criteria from Temp1 and Temp2.
Scenario 2 : Delete the duplicate record in Temp1 and use the key in order to update the data to NULL for Section and Classcode . This is what i expect with the highlighted to be NULLs .
You can run this query yourself - just copy and paste.
Yes, for scenario #1 it is going to delete the rows because the problem is in this section.
I added this table for references.
Added this #temp2 table to clarify for later use.
CREATE Table #Temp2 (
Id int,
Name Varchar(64),
StudentNo INT NULL,
ClassCode Varchar(8) NULL,
Section Varchar(8) NULL,
)
IF OBJECT_ID('tempdb..#tmp4') IS NOT NULL
DROP TABLE #tmp4
select t1.Id,t1.Name,t1.StudentNo,t1.Section,t1.ClassCode,
Duplicate_RowNumber
INTO #Duplicatedata
from CTE as c INNER JOIN #Temp1 as t1 ON t1.Id = c.Id
and t1.StudentNo = t1.StudentNo
and c.Duplicate_RowNumber >1
select * from #Duplicatedata
This is going to satisfy both condition as #temp 1 will have both rows for Elle as your join condition is only on ID and Student No.
I added row number column for clarity.
Id Name StudentNo Section ClassCode Duplicate_RowNumber
1 Joe 123 I A1 2
1 Joe 123 I A1 2
2 Harry 113 H X2 2
2 Harry 113 H X2 2
3 Elle 121 E1 J1 2
3 Elle 121 E J1 2
As your Partition is based by Student No and ID, every duplicate row will have 2 or more row numbers.
You can use this approach to delete.
select
ROW_NUMBER() over (partition by Id
, StudentNo
order by Id, StudentNo, section)as Duplicate_RowNumber
, * into #tmp4 from #Temp1
--You can add section in your order as well for consistency purpose.
delete
from #tmp4
output deleted.id, deleted.Name, deleted.StudentNo, deleted.ClassCode,
deleted.Section into #Temp2
where Duplicate_RowNumber > 1
After that it seems like you want to keep one row in your final table and put the other one in you deleted table. For Elle it will delete one of the rows from Final table and keep only one since your partition is not based on section.
To make sure that you delete 1 row from your final table you can use this.
DELETE t
OUTPUT deleted.Id , deleted.Name ,deleted.StudentNo ,deleted.ClassCode
,deleted.Section into #inserted FROM
(select *, row_number() over (Partition by tm.name, tm.studentNo Order by ID,
StudentNo, section ) rownum from #temp1 tm) t
join #Temp2 t2 on t.Id = t2.Id
and t.Name = t2.Name
and t.StudentNo = t2.StudentNo
and t.ClassCode = t2.ClassCode
and t.Section = t2.Section
where t.rownum > 1
If you notice I added this row number, so that it will not two delete the rows from final table, since Joe and Harry has all the matching attributes, and it will delete two rows.
select * from #inserted
Output you get:
Id Name StudentNo ClassCode Section
3 Elle 121 J1 E1
2 Harry 113 X2 H
1 Joe 123 A1 I
Finally you can update final table in this way. #Scenario 2
update TMP
SET ClassCode = NULL, SECTION = NULL
FROM
#Temp1 TMP
JOIN #INSERTED I ON TMP.Id = I.Id
AND TMP.StudentNo = I.StudentNo
SELECT * FROM #Temp1
Final Output:
Id Name StudentNo ClassCode Section
1 Joe 123 NULL NULL
2 Harry 113 NULL NULL
3 Elle 121 NULL NULL
8 Jane 191 A1 E
5 Silva 811 S1 SE
6 Juan 411 S2 SE
7 Carla 431 S2 SE
Please note that I have added scripts and output only for the parts where it required change, and rest part is same script provided by you.
I have the following animal table:
id | action
------------------
duck | cuack
duck | fly
duck | swim
pelican | fly
pelican | swim
I want to create a stored procedure and pass a group of values into a single parameter:
EXEC GuessAnimalName 'cuack,fly,swim'
Result:
duck
So, if it cuacks, it flyes and it swims then it's a duck. But also:
EXEC GuessAnimalName 'fly,swim'
Result:
pelican
---
EXEC GuessAnimalName 'fly'
Result:
No results
Parameter's number is dynamic.
In order to guess the animal's name all actions provided must match or be found in animal table.
This is what I have so far:
DECLARE #animal AS TABLE
(
[id] nvarchar(8),
[action] nvarchar(16)
)
INSERT INTO #animal VALUES('duck','cuack')
INSERT INTO #animal VALUES('duck','fly')
INSERT INTO #animal VALUES('duck','swim')
INSERT INTO #animal VALUES('pelican','fly')
INSERT INTO #animal VALUES('pelican','swim')
-- Parameter simulation
DECLARE #params AS TABLE
(
[action] nvarchar(16)
)
INSERT INTO #params VALUES('cuack')
INSERT INTO #params VALUES('fly')
INSERT INTO #params VALUES('swim')
SELECT
a.[id]
FROM
#animal a
INNER JOIN
#params p
ON
a.[action] = p.[action]
GROUP BY
a.[id]
HAVING COUNT(a.[action]) IN (SELECT COUNT([action]) FROM #animal GROUP BY [id])
Which gives the result:
Result:
--------
duck
--------
pelican
It should return just duck.
convert this to a stored proc using RANK
declare #lookfor varchar(100) = 'swim,fly'
select id from
(select
id, rank() over (order by cnt desc) rank_ -- get rank based on the number of match where should be the same number of rows
from
(
SELECT
a.id, count(1) cnt -- identify how many matches
FROM
#animal a
INNER JOIN
#params p
ON
a.[action] = p.[action]
where charindex(p.action,#lookfor) > 0
group by a.id
having count(1) = (select count(1) from #animal x where a.id = x.id)) -- only get the animal with the same number of matches and rows
y)
z where rank_ = 1 -- display only with the most matches which should be the same number of rows matched
you don't need #params here
select id from
(select
id, rank() over (order by cnt desc) rank_
from
(
SELECT
a.id, count(1) cnt
FROM
#animal a
where charindex(a.action,#lookfor) > 0
group by a.id
having count(1) = (select count(1) from #animal x where a.id = x.id))
y)
z where rank_ = 1
Let's assume my table is the following:
id | name | country
--------------------
| John | USA
| Mary | USA
| Mike | USA
Someone can help me with a script that can add id's to all names?
Thanks
-- Create a temporary table for the example.
CREATE TABLE #People(Id int, Name nvarchar(10), Country nvarchar(10))
-- Insert data, leaving the Id column NULL.
INSERT INTO #People(Name, Country) SELECT
'John', 'USA' UNION ALL SELECT
'Mary', 'USA' UNION ALL SELECT
'Mike', 'USA';
-- 1. Use Row_Number() to generate an Id.
-- 2. Wrap the query in a common table expression (CTE), which is like an inline view.
-- 3. If the CTE references a single table, we can update the CTE to affect the underlying table.
WITH PeopleCte AS (
SELECT
Id,
Row_Number() OVER (ORDER BY (SELECT NULL)) AS NewId
FROM
#People
)
UPDATE PeopleCte SET Id = NewId
SELECT * FROM #People
DROP TABLE #People
try this
update table set a.id=b.newid from table a,(select row_number() over (order by (select null)) newid,name from #temp) b
make changes like ordering as needed
I have a table like
TABLEX -
+------+------------+
| NAME | TABLE_NAME |
+------+------------+
| X1 | X001 |
| X2 | X002 |
+------+------------+
This table contains a name column which is nothing but description and a table_name column which is actually a table already present in the database.
X001 Table has columns like X1_A, X1_B
X002 Table has columns like X2_A, X2_B
Now I want to concatenate all columns in the actual table present in the TABLE_NAME column in a comma separated string and display that as a column.
+------+------------+------------+
| NAME | TABLE_NAME | COLUMNS |
+------+------------+------------+
| X1 | X001 | X1_A, X1_B |
| X2 | X002 | X2_A, X2_B |
+------+------------+------------+
Now can this be achieved using CTE. I've already successfully created the query using STUFF with XML PATH, but I'm having performance issues because there are like 200 odd rows in the table that I've show above and each subsequent tables linked have like 100 columns each.
EDIT -
SELECT
P.NAME,
P.TABLE_NAME,
[COLUMNS]=(SELECT STUFF((SELECT ',' + NAME FROM sys.syscolumns WHERE ID = OBJECT_ID(P.TABLE_NAME) ORDER BY colorder FOR XML PATH('') ), 1, 1,''))
FROM TABLEX P
Where TABLEX is the table posted above.
Try this one -
DDL:
IF OBJECT_ID (N'dbo.TABLEX') IS NOT NULL
DROP TABLE TABLEX
IF OBJECT_ID (N'dbo.X001') IS NOT NULL
DROP TABLE X001
IF OBJECT_ID (N'dbo.X002') IS NOT NULL
DROP TABLE X002
CREATE TABLE dbo.TABLEX (NAME VARCHAR(50), TABLE_NAME VARCHAR(50))
INSERT INTO dbo.TABLEX (NAME, TABLE_NAME)
VALUES ('X1', 'X001'), ('X2', 'X002')
CREATE TABLE dbo.X001 (X1_A VARCHAR(50), X1_B VARCHAR(50))
CREATE TABLE dbo.X002 (X2_A VARCHAR(50), X2_B VARCHAR(50))
Query:
;WITH cte AS
(
SELECT
NAME
, TABLE_NAME
, [COLUMN] = CAST('' AS VARCHAR(1024))
, POS = 1
FROM TABLEX t
UNION ALL
SELECT
t.NAME
, t.TABLE_NAME
, CAST([COLUMN] + ', ' + c.name AS VARCHAR(1024))
, POS + 1
FROM cte t
JOIN sys.columns c ON
OBJECT_ID('dbo.' + t.TABLE_NAME) = c.[object_id]
AND
t.POS = c.column_id
)
SELECT
NAME
, TABLE_NAME
, [COLUMNS] = STUFF([COLUMN], 1, 2, '')
FROM (
SELECT *, rn = ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY POS DESC)
FROM cte
) t
WHERE t.rn = 1
Results:
NAME TABLE_NAME COLUMNS
------ ------------- -------------
X1 X001 X1_A, X1_B
X2 X002 X2_A, X2_B
Query cost:
Statistic:
Query Presenter Scans Logical Reads
------------------- ----- -------------
XML 5 9
CTE 3 48