Table A
ID COLA
-----------------------
A value1
B value1
C value1
Table B
ID DETAIL_ID COL_X COL_Y
A 0 foo foo
A 1 bar bar
B 0 foo foo
My expected out is something like
ID COLA COL_X_0 COL_X_1 COL_Y_0 COL_Y_1
A value1 foo bar foo bar
B value1 foo NULL foo NULL
C value1 NULL NULL NULL NULL
It means the rows of table B will be column values based on DETAIL_ID column.
I tried to write queries for this , but can't succeed due to following.
Number of DetailID values will NOT be fixed-length.It means I can't hard-coded the name of the columns.
This will give the exact output you described and you can add more columns if needed
DECLARE #a table (id char, cola varchar(10))
DECLARE #b table (id char, detail_id int, colx char(3), coly char(3))
INSERT #a values('A', 'value1'),('B', 'value2'),('C','value3')
INSERT #b values('A', 0, 'foo', 'foo'),('A', 1, 'bar', 'bar'),
('B',0, 'foo','foo')--,('A', 2, 'bar', 'bar') -- add this for extra columns
CREATE TABLE ##t(id char, detail_id tinyint, colvalue char(3), col varchar(8), cola varchar(10))
DECLARE #columns varchar(max)=''
DECLARE #sqlstring varchar(1000)
;WITH a as (
SELECT a.id, a.cola, b.detail_id, colx, coly,
'col_x_' + cast(detail_id as varchar) col_a,
'col_y_' + cast(detail_id as varchar) col_b
FROM #a a LEFT JOIN #b b on a.id = b.id
)
INSERT ##t
SELECT id, detail_id, colx, col_a, cola FROM a
UNION
SELECT id, detail_id, coly, col_b, cola FROM a
ORDER BY 4,2
SELECT #columns = coalesce(#columns, '') +',[' + col + ']'
FROM (
SELECT DISTINCT col, detail_id FROM ##t where not col is null
) a
SET #columns = stuff(#columns, 1,1,'')
SET #sqlstring =
'SELECT * FROM (
SELECT id, cola, col, colvalue FROM ##t
) b
PIVOT(max(colvalue) FOR col
in(
'+#columns+'))AS p order by 1'
EXEC(#sqlstring)
DROP TABLE ##t
SQL queries must specify the columns of the result set. That's fundamental to SQL. Even PIVOT requires that your query specify the columns before you send it to the RDBMS.
For that reason, it's difficult and error-prone to create a query that returns rows as columns as you describe, and can adapt as needed to any number of columns.
Handling dynamic columns must be a two-stage procedure.
One option is to make the two stages be:
Write application code to build the SQL query dynamically, based on the distinct values found in the data. This requires an extra query to discover what values exist so you can build the query.
Execute the SQL query and retrieve the results.
The other option is to make the two stages be:
Run a more plain SQL query, that fetches rows as rows, as they are stored in the database.
Write application code to post-process the results, collecting individual values from rows into an expanding set of columns based on the values found. This does not require an extra query as the first design does.
Just join table A and B on B.DETAIL_ID == A.ID ?? Or is that too simple?
Related
I'm trying to create some reports for auditing, but I have a very specific question.
There's about 120 columns, each with a specific numeric answer. I'd like to return the column name and the value of the rows of that column. I'm aware I'll get a lot of results, but it's not a problem.
For example I have:
KEY |ColumnA | ColumnB
1 |Value A | ValueB
2 |ValueA2 | ValueB2
But want I want is:
1 |ColumnA | Value A
2 |ColumnA | Value A2
1 |ColumnB | Value B
2 |ColumnB | Value B2
I've tried returning all rows and then joining on itself, but it didn't provide me with the output I needed.
Simple unpivot will do the work :)
declare #tbl table ([Key] int, ColumnA varchar(15), ColumnB varchar(15));
insert into #tbl values
(1, 'Value A', 'ValueB'),
(2, 'ValueA2', 'ValueB2');
select [key], [column], [value] from
(select * from #tbl) p
unpivot
([value] for [column] in (ColumnA, ColumnB)) u
order by [column]
it's so simple...If you know the column names, you could use a simple UNION
SELECT * FROM tblAuditing
SELECT 'ColumnA' AS ColumnA,'ColumnB' AS ColumnA UNION
SELECT ColumnA AS ColumnA,ColumnB AS ColumnA FROM tblAuditing
The following query should do what you want - you need to do a customized sorting for the columns names:
CREATE TABLE #temp (ColumnA VARCHAR(20), ColumnB VARCHAR(20))
INSERT INTO #temp VALUES ('Value A','Value B'),('Value A2','Value B2')
SELECT t.Col, t.Val
FROM (SELECT *,ROW_NUMBER() OVER (ORDER BY (SELECT 1)) RNO FROM #temp t) tmp
CROSS APPLY (VALUES (tmp.ColumnA,'ColumnA',tmp.RNO),(tmp.ColumnB,'ColumnB',tmp.RNO)) AS T(Val,Col,sort)
ORDER BY T.Col, Sort
The result is as below,
Col Val
ColumnA Value A
ColumnA Value A2
ColumnB Value B
ColumnB Value B2
I want to achieve in MS SQL something like below, using 2 tables and through join instead of iteration.
From table A, I want each row to identify from table B which in the list is their nearest value, and when value has been selected, that value cannot re-used. Please help if you've done something like this before. Thank you in advance! #SOreadyToAsk
Below is a set-based solution using CTEs and windowing functions.
The ranked_matches CTE assigns a closest match rank for each row in TableA along with a closest match rank for each row in TableB, using the index value as a tie breaker.
The best_matches CTE returns rows from ranked_matches that have the best rank (rank value 1) for both rankings.
Finally, the outer query uses a LEFT JOIN from TableA to the to the best_matches CTE to include the TableA rows that were not assigned a best match due to the closes match being already assigned.
Note that this does not return a match for the index 3 TableA row indicated in your sample results. The closes match for this row is TableB index 3, a difference of 83. However, that TableB row is a closer match to the TableA index 2 row, a difference of 14 so it was already assigned. Please clarify you question if this isn't what you want. I think this technique can be tweaked accordingly.
CREATE TABLE dbo.TableA(
[index] int NOT NULL
CONSTRAINT PK_TableA PRIMARY KEY
, value int
);
CREATE TABLE dbo.TableB(
[index] int NOT NULL
CONSTRAINT PK_TableB PRIMARY KEY
, value int
);
INSERT INTO dbo.TableA
( [index], value )
VALUES ( 1, 123 ),
( 2, 245 ),
( 3, 342 ),
( 4, 456 ),
( 5, 608 );
INSERT INTO dbo.TableB
( [index], value )
VALUES ( 1, 152 ),
( 2, 159 ),
( 3, 259 );
WITH
ranked_matches AS (
SELECT
a.[index] AS a_index
, a.value AS a_value
, b.[index] b_index
, b.value AS b_value
, RANK() OVER(PARTITION BY a.[index] ORDER BY ABS(a.Value - b.value), b.[index]) AS a_match_rank
, RANK() OVER(PARTITION BY b.[index] ORDER BY ABS(a.Value - b.value), a.[index]) AS b_match_rank
FROM dbo.TableA AS a
CROSS JOIN dbo.TableB AS b
)
, best_matches AS (
SELECT
a_index
, a_value
, b_index
, b_value
FROM ranked_matches
WHERE
a_match_rank = 1
AND b_match_rank= 1
)
SELECT
TableA.[index] AS a_index
, TableA.value AS a_value
, best_matches.b_index
, best_matches.b_value
FROM dbo.TableA
LEFT JOIN best_matches ON
best_matches.a_index = TableA.[index]
ORDER BY
TableA.[index];
EDIT:
Although this method uses CTEs, recursion is not used and is therefore not limited to 32K recursions. There may be room for improvement here from a performance perspective, though.
I don't think it is possible without a cursor.
Even if it is possible to do it without a cursor, it would definitely require self-joins, maybe more than once. As a result performance is likely to be poor, likely worse than straight-forward cursor. And it is likely that it would be hard to understand the logic and later maintain this code. Sometimes cursors are useful.
The main difficulty is this part of the question:
when value has been selected, that value cannot re-used.
There was a similar question just few days ago.
The logic is straight-forward. Cursor loops through all rows of table A and with each iteration adds one row to the temporary destination table. To determine the value to add I use EXCEPT operator that takes all values from the table B and removes from them all values that have been used before. My solution assumes that there are no duplicates in value in table B. EXCEPT operator removes duplicates. If values in table B are not unique, then temporary table would hold unique indexB instead of valueB, but main logic remains the same.
Here is SQL Fiddle.
Sample data
DECLARE #TA TABLE (idx int, value int);
INSERT INTO #TA (idx, value) VALUES
(1, 123),
(2, 245),
(3, 342),
(4, 456),
(5, 608);
DECLARE #TB TABLE (idx int, value int);
INSERT INTO #TB (idx, value) VALUES
(1, 152),
(2, 159),
(3, 259);
Main query inserts result into temporary table #TDst. It is possible to write that INSERT without using explicit variable #CurrValueB, but it looks a bit cleaner with variable.
DECLARE #TDst TABLE (idx int, valueA int, valueB int);
DECLARE #CurrIdx int;
DECLARE #CurrValueA int;
DECLARE #CurrValueB int;
DECLARE #iFS int;
DECLARE #VarCursor CURSOR;
SET #VarCursor = CURSOR FAST_FORWARD
FOR
SELECT idx, value
FROM #TA
ORDER BY idx;
OPEN #VarCursor;
FETCH NEXT FROM #VarCursor INTO #CurrIdx, #CurrValueA;
SET #iFS = ##FETCH_STATUS;
WHILE #iFS = 0
BEGIN
SET #CurrValueB =
(
SELECT TOP(1) Diff.valueB
FROM
(
SELECT B.value AS valueB
FROM #TB AS B
EXCEPT -- remove values that have been selected before
SELECT Dst.valueB
FROM #TDst AS Dst
) AS Diff
ORDER BY ABS(Diff.valueB - #CurrValueA)
);
INSERT INTO #TDst (idx, valueA, valueB)
VALUES (#CurrIdx, #CurrValueA, #CurrValueB);
FETCH NEXT FROM #VarCursor INTO #CurrIdx, #CurrValueA;
SET #iFS = ##FETCH_STATUS;
END;
CLOSE #VarCursor;
DEALLOCATE #VarCursor;
SELECT * FROM #TDst ORDER BY idx;
Result
idx valueA valueB
1 123 152
2 245 259
3 342 159
4 456 NULL
5 608 NULL
It would help to have the following indexes:
TableA - (idx) include (value), because we SELECT idx, value ORDER BY idx;
TableB - (value) unique, Temp destination table - (valueB) unique filtered NOT NULL, to help EXCEPT. So, it may be better to have a temporary #table for result (or permanent table) instead of table variable, because table variables can't have indexes.
Another possible method would be to delete a row from table B (from original or from a copy) as its value is inserted into result. In this method we can avoid performing EXCEPT again and again and it could be faster overall, especially if it is OK to leave table B empty in the end. Still, I don't see how to avoid cursor and processing individual rows in sequence.
SQL Fiddle
DECLARE #TDst TABLE (idx int, valueA int, valueB int);
DECLARE #CurrIdx int;
DECLARE #CurrValueA int;
DECLARE #iFS int;
DECLARE #VarCursor CURSOR;
SET #VarCursor = CURSOR FAST_FORWARD
FOR
SELECT idx, value
FROM #TA
ORDER BY idx;
OPEN #VarCursor;
FETCH NEXT FROM #VarCursor INTO #CurrIdx, #CurrValueA;
SET #iFS = ##FETCH_STATUS;
WHILE #iFS = 0
BEGIN
WITH
CTE
AS
(
SELECT TOP(1) B.idx, B.value
FROM #TB AS B
ORDER BY ABS(B.value - #CurrValueA)
)
DELETE FROM CTE
OUTPUT #CurrIdx, #CurrValueA, deleted.value INTO #TDst;
FETCH NEXT FROM #VarCursor INTO #CurrIdx, #CurrValueA;
SET #iFS = ##FETCH_STATUS;
END;
CLOSE #VarCursor;
DEALLOCATE #VarCursor;
SELECT
A.idx
,A.value AS valueA
,Dst.valueB
FROM
#TA AS A
LEFT JOIN #TDst AS Dst ON Dst.idx = A.idx
ORDER BY idx;
I highly believe THIS IS NOT A GOOD PRACTICE because I am bypassing the policy SQL made for itself that functions with side-effects (INSERT,UPDATE,DELETE) is a NO, but due to the fact that I want solve this without resulting to iteration options, I came up with this and gave me better view of things now.
create table tablea
(
num INT,
val MONEY
)
create table tableb
(
num INT,
val MONEY
)
I created a hard-table temp which I shall drop from time-to-time.
if((select 1 from sys.tables where name = 'temp_tableb') is not null) begin drop table temp_tableb end
select * into temp_tableb from tableb
I created a function that executes xp_cmdshell (this is where the side-effect bypassing happens)
CREATE FUNCTION [dbo].[GetNearestMatch]
(
#ParamValue MONEY
)
RETURNS MONEY
AS
BEGIN
DECLARE #ReturnNum MONEY
, #ID INT
SELECT TOP 1
#ID = num
, #ReturnNum = val
FROM temp_tableb ORDER BY ABS(val - #ParamValue)
DECLARE #SQL varchar(500)
SELECT #SQL = 'osql -S' + ##servername + ' -E -q "delete from test..temp_tableb where num = ' + CONVERT(NVARCHAR(150),#ID) + ' "'
EXEC master..xp_cmdshell #SQL
RETURN #ReturnNum
END
and my usage in my query simply looks like this.
-- initialize temp
if((select 1 from sys.tables where name = 'temp_tableb') is not null) begin drop table temp_tableb end
select * into temp_tableb from tableb
-- query nearest match
select
*
, dbo.GetNearestMatch(a.val) AS [NearestValue]
from tablea a
and gave me this..
I've tried flexing my Google-fu to no avail so here I am! Unfortunately I cannot change anything about these tables as they are coming out of an application that I have to report out of.
In SQL Server 2008, I'm trying to replace multiple values in one text string column (Table 1) with the value from another table (Table 2).
Thanks in advance!!
Table 1
id value
-------------
1 a1, a2, a3
2 a2, a3
3 a4
Table 2
id value
---------
a1 Value1
a2 Value2
a3 Value3
a4 Value4
Desired Output
id value
-----------------------------
1 Value1, Value2, Value3
2 Value2, Value3
3 Value4
I'm sorry for this solution in advance :) It does what you need though:
create table TableA(
id int,
string varchar(255)
)
create table table2(
id varchar , text varchar(255)
)
insert into tableA values(1,'a,b,c,d')
insert into tableA values(2,'e,f')
insert into table2 values('a', 'value1')
insert into table2 values('b', 'value2')
insert into table2 values('c', 'value3')
insert into table2 values('d', 'value4')
insert into table2 values('e', 'value5')
insert into table2 values('f', 'value6')
select id, left(myConcat,len(myConcat)-1) from (
select c.id, replace(replace(CAST(CAST('<i'+stuff((select * from(
SELECT A.[id] ,
Split.a.value('.', 'VARCHAR(1000)') AS String
FROM (SELECT [id],
CAST ('<M>' + REPLACE([string], ',', '</M><M>') + '</M>' AS XML) AS String
FROM TableA) AS A CROSS APPLY String.nodes ('/M') AS Split(a)) a
inner join table2 b on a.String = b.id
where a.id = c.id
FOR XML PATH ('')
),1,2,'') AS XML).query('/text') AS VARCHAR(1000)),'<text>',''),'</text>',',') myConcat
from TableA c
group by c.id
) d
Using the DelimitedSplit8K found at http://www.sqlservercentral.com/articles/Tally+Table/72993/ as suggested by #user1221684 you might come up with something like this. Working with delimited data like this is a pain. First you have to parse the string so you can join it to the other table and then ruin by stuffing it back into a denormalized form.
Make sure that if you use this that you understand that function and what this code is doing. This is not entry level t-sql and it will be YOU supporting this at 3am when it breaks in production, not me.
if OBJECT_ID('tempdb..#table1') is not null
drop table #table1;
create table #table1
(
id int,
value varchar(50)
);
insert #table1
select 1, 'a1, a2, a3' union all
select 2, 'a2, a3' union all
select 3, 'a4';
if OBJECT_ID('tempdb..#table2') is not null
drop table #table2;
create table #table2
(
id varchar(50),
value varchar(50)
);
insert #table2
select 'a1', 'Value1' union all
select 'a2', 'Value2' union all
select 'a3', 'Value3' union all
select 'a4', 'Value4';
with parsedValues as
(
select t1.id
, t1.value
, LTRIM(x.item) as item
from #table1 t1
cross apply dbo.DelimitedSplit8K(t1.value, ',') x
)
, swappedVals as
(
select pv.id
, t2.value
from parsedValues pv
join #table2 t2 on t2.id = pv.item
)
select id
, STUFF((select ',' + value
from swappedVals sv2
where sv2.id = sv.id
order by sv2.value --need to make sure to order here so the results are in the right order
for XML path('')), 1, 1, '') as MyValues
from swappedVals sv
group by id
;
This site has a delimited text split function http://www.sqlservercentral.com/articles/Tally+Table/72993/
Use that function to split your values out into a temp table. Replace the values in your temp table with the new values. Then use STUFF..FOR XML to combine the records back together and update your table.
One query with a few cte's should be able to handle all of this after you add the function to your database.
Example using Sql Fiddle
Use this:
DECLARE #t TABLE(id int,value varchar(255))
INSERT INTO #t (id,value)
VALUES(1,'a1'),(2,'a2'),(3,'a3')....
SELECT *,STUFF((SELECT DISTINCT ','+value FROM #t WHERE id=t.id)
FOR XML PATH('')),1,2,' ')
FROM (SELECT DISTINCT ID FROM #t) t
DISTINCT in the case of same id, otherwise let it go
Is it possible to compare a vector of values with all columns of a table in SQL, more specifically in MS SQL Server?
For example, I have a table, EXAMPLE, with 3 columns:
EXAMPLE: ColA, ColB, ColC
And I want to check if its columns match a specific vector: ('val0', 'val1', 'val2')
I know I can do that with a sentence like this:
SELECT * FROM EXAMPLE WHERE ColA='val0' AND ColB = 'val1' AND ColC = 'val2'
But I'd like to know if there is some function, ALLEQUAL which could allow me to do something like:
SELECT * FROM EXAMPLE WHERE ALLEQUAL('val0', 'val1', 'val2');
I understand that if that function exists its syntax may be quite different between different RDBMSs and I now focused on Ms SQL Server. Nevertheless I will be more than happy if you can give me examples in other databases managers.
declare #Foo as Table ( ColA Int, ColB Int );
insert into #Foo ( ColA, ColB ) values ( 1, 1 ), ( 1, 2 ), ( 2, 1 );
select * from #Foo;
select *
from #Foo
intersect
select *
from ( values ( 2, 1 ) ) as Bar( ColA, ColB );
Maybe this will help you
SELECT *
FROM EXAMPLE
WHERE ColA+ColB+ColC = 'val0'+'val1'+'val2'
In a well designed database the tables will have a PRIMARY KEY, the key will be a unique identifier for the row, because of this there is no need to match the rest of the 'vector' in your scenario.
Now if you have some flat de-normalized data you could create an sproc that generates dynamic sql to do what you want. You'd have to query INFORMATION_SCHEMA.columns or sys.columns and generate something similar to the first SQL statement you wrote ~"WHERE ColA='val0' AND ColB = 'val1' AND ColC = 'val2' "
Here's the varchar column only approach using FOR XML PATH('')
BEGIN
DECLARE #tbl TABLE(c1 VARCHAR(max), c2 VARCHAR(max), c3 VARCHAR(max))
DECLARE #vec TABLE(data VARCHAR(max))
INSERT INTO #tbl VALUES ('abc', '123', 'xyz'), ('cba', '321', 'zyx')
INSERT INTO #vec VALUES ('abc'), ('123'), ('xyz')
SELECT * FROM #tbl
WHERE c1+c2+c3 = (SELECT '' + data FROM #vec FOR XML PATH(''))
END
I have like 10 diff temporary tables created in SQL server, what I am looking to do is union them all to a final temporary table holding them all on one table. All the tables have only one row and look pretty much exactly like the two temp tables below.
Here is what I have so far this is an example of just two of the temp tables as their all exactly like this one then #final is the table I want to union the all to:
create table #lo
(
mnbr bigint
)
insert into #login (mnbr)
select distinct (_ID)
FROM [KDB].[am_LOGS].[dbo].[_LOG]
WHERE time >= '2012-7-26 9:00:00
Select count(*) as countreject
from #lo
create table #pffblo
(
mber_br
)
insert into #pffblo (mber_br)
select distinct (mber_br)
from individ ip with (nolock)
join memb mp with (nolock)
on( ip.i_id=mp.i_id and mp.p_type=101)
where ip.times >= '2012-9-26 11:00:00.000'
select count(*) as countaccept
create table #final
(
countreject bigint
, Countacceptbigint
.....
)
insert into #final (Countreject, Countaccept....more rows here...)
select Countreject, Countaccept, ...more rows selected from temp tables.
from #final
union
(select * from #lo)
union
(select * from #pffblo)
select *
from #final
drop table #lo
drop table #pffblo
drop table #final
if this the form to union the rows form those temp tables to this final one. Then is this correct way to show all those rows that were thus unioned. When I do this union I get message number of columns in union need to match number of columns selected in union
I think you're using a union the wrong way. A union is used when you have to datasets that are the same structure and you want to put them into one dataset.
e.g.:
CREATE TABLE #Table1
(
col1 BIGINT
)
CREATE TABLE #Table2
(
col1 BIGINT
)
--populate the temporary tables
CREATE TABLE #Final
(
col1 BIGINT
)
INSERT INTO #Final (col1)
SELECT *
FROM #Table1
UNION
SELECT *
FROM #Table2
drop table #table1
drop table #table2
drop table #Final
I think what you're trying to do is get 1 data set with the count of all your tables in it. Union won't do this.
The easiest way (although not very performant) would be to do select statements like the following:
CREATE TABLE #Table1
(
col1 BIGINT
)
CREATE TABLE #Table2
(
col1 BIGINT
)
--populate the temporary tables
CREATE TABLE #Final
(
col1 BIGINT,
col2 BIGINT
)
INSERT INTO #Final (col1, col2)
select (SELECT Count(*) FROM #Table1) as a, (SELECT Count(*) FROM #Table2) as b
select * From #Final
drop table #table1
drop table #table2
drop table #Final
It appears that you want to take the values from each of temp tables and then place then into a single row of data. This is basically a PIVOT, you can use something like this:
create table #final
(
countreject bigint
, Countaccept bigint
.....
)
insert into #final (Countreject, Countaccept....more rows here...)
select
from
(
select count(*) value, 'Countreject' col -- your UNION ALL's here
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
) x
pivot
(
max(value)
for col in ([Countreject], [countaccept])
) p
Explanation:
You will create a subquery similar to this that will contain the COUNT for each of your individual temp table. There are two columns in the subquery, one column contains the count(*) from the table and the other column is the name of the alias:
select count(*) value, 'Countreject' col
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
You then PIVOT these values to insert into your final temp table.
If you do not want to use PIVOT, then you can use a CASE statement with an aggregate function:
insert into #final (Countreject, Countaccept....more rows here...)
select max(case when col = 'Countreject' then value end) Countreject,
max(case when col = 'countaccept' then value end) countaccept
from
(
select count(*) value, 'Countreject' col -- your UNION ALL's here
from #lo
union all
select count(*) value, 'countaccept' col
from #pffblo
) x
Or you might be able to JOIN all of the temp tables similar to this, where you create a row_number() for the one record in the table and then you join the tables with the row_number():
insert into #final (Countreject, Countaccept....more rows here...)
select isnull(lo.Countreject, 0) Countreject,
isnull(pffblo.Countaccept, 0) Countaccept
from
(
select count(*) Countreject,
row_number() over(order by (SELECT 0)) rn
from #lo
) lo
left join
(
select count(*) Countaccept,
row_number() over(order by (SELECT 0)) rn
from #pffblo
) pffblo
on lo.rn = pffblo.rn
SELECT *
INTO #1
FROM TABLE2
UNION
SELECT *
FROM TABLE3
UNION
SELECT *
FROM TABLE4
If you would like to get count for each temporary table in the resulting table, you will need just to calculate it for each column in subquery:
INSERT INTO result (col1, col2,...
SELECT
(SELECT COUNT() FROM tbl1) col1
,(SELECT COUNT() FROM tbl2) col2
..