Using union to do a crosstab query - database

I have a table which has the following structure :
id key data
1 A 10
1 B 20
1 C 30
I need to write a query so that i get these keys as columns and the value as rows.
Eg :
id A B C
1 10 20 30
I have tried using union and case but i get 3 rows for instead of one
Any suggestion?

The most straightforward way to do this is:
SELECT DISTINCT "id",
(SELECT "data" FROM Table1 WHERE "key" = 'A') AS "A",
(SELECT "data" FROM Table1 WHERE "key" = 'B') AS "B",
(SELECT "data" FROM Table1 WHERE "key" = 'C') AS "C"
FROM Table1
Or you can use a PIVOT:
SELECT * FROM
(SELECT "id", "key", "data" FROM Table1)
PIVOT (
MAX("data")
FOR ("key") IN ('A', 'B', 'C'));
sqlfiddle demo

Related

JSON_VALUE is not working in where clause in SQL Server

I have JSON data in a column in my table. I am trying to apply where condition on the JSON column and fetch records.
Employee table:
Here is my SQL query:
SELECT ID, EMP_NAME
FROM EMPLOYEE
WHERE JSON_VALUE(TEAM, '$') IN (2, 3, 4, 5, 7, 10)
I am getting an empty result when I use this query. Any help on how to do this?
You need to parse the JSON in the TEAM column with OPENJSON():
Table:
CREATE TABLE EMPLOYEE (
ID int,
EMP_NAME varchar(50),
TEAM varchar(1000)
)
INSERT INTO EMPLOYEE (ID, EMP_NAME, TEAM)
VALUES
(1, 'Name1', '[2,11]'),
(2, 'Name2', '[2,3,4,5,7,10]'),
(3, 'Name3', NULL)
Statement:
SELECT DISTINCT e.ID, e.EMP_NAME
FROM EMPLOYEE e
CROSS APPLY OPENJSON(e.TEAM) WITH (TEAM int '$') j
WHERE j.TEAM IN (2,3,4,5,7,10)
Result:
ID EMP_NAME
1 Name1
2 Name2
As an additional option, if you want to get the matches as an aggregated text, you may use the following statement (SQL Server 2017 is needed):
SELECT e.ID, e.EMP_NAME, a.TEAM
FROM EMPLOYEE e
CROSS APPLY (
SELECT STRING_AGG(TEAM, ',') AS TEAM
FROM OPENJSON(e.TEAM) WITH (TEAM int '$')
WHERE TEAM IN (2,3,4,5,7,10)
) a
WHERE a.TEAM IS NOT NULL
Result:
ID EMP_NAME TEAM
1 Name1 2
2 Name2 2,3,4,5,7,10
JSON_VALUE returns a scalar value, not a data set, which you appaer to think it would. If you run SELECT JSON_VALUE('[2,3,4,5,7,10]','$') you'll see that it returns NULL, so yes, no rows will be returned.
You need to treat the JSON like a data set, not a single value:
SELECT ID, EMP_NAME
FROM EMPLOYEE E
WHERE EXISTS (SELECT 1
FROM OPENJSON (E.TEAM) OJ
WHERE OJ.Value IN (2,3,4,5,7,10))

How can I apply a function to each element of a array column?

I have a dataset were a column in with an ARRAY of OBJECTs like this:
ID TAGS
1 {"tags": [{"tag": "a"}, {"tag": "b"}]}
2 {"tags": [{"tag": "c"}, {"tag": "d"}]}
I want to extract the tag field of each element of the array, so the end result would be:
ID TAGS
1 ["a","b"]
2 ["c","d"]
Assuming the following table t1:
CREATE OR REPLACE TEMPORARY TABLE t1 AS (
select 1 as ID , PARSE_JSON('{"tags": [{"tag":"a"}, {"tag":"b"}]}') AS PAYLOAD
UNION ALL
select 2, PARSE_JSON('{"tags": [{"tag":"c"}, {"tag":"d"}]}')
);
One possible solution is to create a javascript function and use the javascript .map() to apply a function to each element of the array:
create or replace function extract_tags(a array)
returns array
language javascript
strict
as '
return A.map(function(d) {return d.tag});
';
SELECT ID, EXTRACT_TAGS(PAYLOAD:tags) AS tags from t1;
this gives the desired result:
ID TAGS
1 [ "a", "b" ]
2 [ "c", "d" ]
A pure SQL approach would be to combine LATERAL FLATTEN and ARRAY_AGG like this:
with t2 as (
select ID, t2.value:tag as tag
from t1, LATERAL FLATTEN(input => payload:tags) t2
)
select t2.id, ARRAY_AGG(t2.tag) as tags from t2
group by ID
order by ID ASC;
t2 itself will become:
ID TAG
1 "a"
1 "b"
2 "c"
2 "d"
and after the GROUP BY ID it becomes:
ID TAGS
1 [ "a", "b" ]
2 [ "c", "d" ]

TSQL : Find PAIR Sequence in a table

I have following table in T-SQL(there are other columns too but no identity column or primary key column):
Oid Cid
1 a
1 b
2 f
3 c
4 f
5 a
5 b
6 f
6 g
7 f
So in above example I would like to highlight that following Oid are duplicate when looking at Cid column values as "PAIRS":
Oid:
1 (1 matches Oid: 5)
2 (2 matches Oid: 4 and 7)
Please NOTE that Oid 2 match did not include Oid 6, since the pair of 6 has letter 'G' as well.
Is it possible to create a query without using While loop to highlight the "Oid" like above? along with how many other matches count exist in database?
I am trying to find the patterns within the dataset relating to these two columns. Thank you in Advance.
Here is a worked example - see comments for explanation:
--First set up your data in a temp table
declare #oidcid table (Oid int, Cid char(1));
insert into #oidcid values
(1,'a'),
(1,'b'),
(2,'f'),
(3,'c'),
(4,'f'),
(5,'a'),
(5,'b'),
(6,'f'),
(6,'g'),
(7,'f');
--This cte gets a table with all of the cids in order, for each oid
with cte as (
select distinct Oid, (select Cid + ',' from #oidcid i2
where i2.Oid = i.Oid order by Cid
for xml path('')) Cids
from #oidcid i
)
select Oid, cte.Cids
from cte
inner join (
-- Here we get just the lists of cids that appear more than once
select Cids, Count(Oid) as OidCount
from cte group by Cids
having Count(Oid) > 1 ) as gcte on cte.Cids = gcte.Cids
-- And when we list them, we are showing the oids with duplicate cids next to each other
Order by cte.Cids
select o1.Cid, o1.Oid, o2.Oid
, count(*) + 1 over (partition by o1.Cid) as [cnt]
from table o1
join table o2
on o1.Cid = o2.Cid
and o1.Oid < o2.Oid
order by o1.Cid, o1.Oid, o2.Oid
Maybe Like this then:
WITH CTE AS
(
SELECT Cid, oid
,ROW_NUMBER() OVER (PARTITION BY cid ORDER BY cid) AS RN
,SUM(1) OVER (PARTITION BY oid) AS maxRow2
,SUM(1) OVER (PARTITION BY cid) AS maxRow
FROM oid
)
SELECT * FROM CTE WHERE maxRow != 1 AND maxRow2 = 1
ORDER BY oid

How to make a "two dependent parameters" SQL stored procedure

I just can't figure it out, what I mean by two dependents parameters is this :
Suppose I have records like these :
ID Letter Number
-----------------------
23 A 1
23 A 2
23 B 1
23 B 2
81 A 1
81 B 2
The user is to input this :
First parameter : A,B
Second parameter : 1,2
Then only ID 23 would be returned, because it's the only one that respect all these conditions :
A1, A2, B1, B2
Every time I tried some query, 81 was returned...
In the context of my question it would be quick enough to make 4 conditions like :
A and 1, A and 2, B and 1, B and 2
But imagine if i have 16 * 16 ...
It would be extremely long to write them all...
You can use COUNT DISTINCT in the HAVING clause:
SELECT Id
FROM #Tbl
WHERE
Letter IN('A', 'B')
AND Number IN(1, 2)
GROUP BY Id
HAVING
COUNT(DISTINCT Letter) = 2
AND COUNT(DISTINCT Number) = 2;
For more dynamic approach, you can put the criteria in table variables:
DECLARE #Letters TABLE(Letter CHAR(1));
DECLARE #Numbers TABLE(Number INT);
INSERT INTO #Letters VALUES ('A'), ('B');
INSERT INTO #Numbers VALUES (1), (2);
WITH CteCross(Letter, Number) AS(
SELECT Letter, Number
FROM #Letters
CROSS JOIN #Numbers
)
SELECT t.Id
FROM #Tbl t
INNER JOIN CteCross cc
ON cc.Letter = t.Letter
AND cc.Number = t.Number
GROUP BY t.Id
HAVING COUNT(*) = (SELECT COUNT(*) FROM CteCross);
ONLINE DEMO

SQL Server: Joining in rows via. comma separated field

I'm trying to extract some data from a third party system which uses an SQL Server database. The DB structure looks something like this:
Order
OrderID OrderNumber
1 OX101
2 OX102
OrderItem
OrderItemID OrderID OptionCodes
1 1 12,14,15
2 1 14
3 2 15
Option
OptionID Description
12 Batteries
14 Gift wrap
15 Case
[etc.]
What I want is one row per order item that includes a concatenated field with each option description. So something like this:
OrderItemID OrderNumber Options
1 OX101 Batteries\nGift Wrap\nCase
2 OX101 Gift Wrap
3 OX102 Case
Of course this is complicated by the fact that the options are a comma separated string field instead of a proper lookup table. So I need to split this up by comma in order to join in the options table, and then concat the result back into one field.
At first I tried creating a function which splits out the option data by comma and returns this as a table. Although I was able to join the result of this function with the options table, I wasn't able to pass the OptionCodes column to the function in the join, as it only seemed to work with declared variables or hard-coded values.
Can someone point me in the right direction?
I would use a splitting function (here's an example) to get individual values and keep them in a CTE. Then you can join the CTE to your table called "Option".
SELECT * INTO #Order
FROM (
SELECT 1 OrderID, 'OX101' OrderNumber UNION SELECT 2, 'OX102'
) X;
SELECT * INTO #OrderItem
FROM (
SELECT 1 OrderItemID, 1 OrderID, '12,14,15' OptionCodes
UNION
SELECT 2, 1, '14'
UNION
SELECT 3, 2, '15'
) X;
SELECT * INTO #Option
FROM (
SELECT 12 OptionID, 'Batteries' Description
UNION
SELECT 14, 'Gift Wrap'
UNION
SELECT 15, 'Case'
) X;
WITH N AS (
SELECT I.OrderID, I.OrderItemID, X.items OptionCode
FROM #OrderItem I CROSS APPLY dbo.Split(OptionCodes, ',') X
)
SELECT Q.OrderItemID, Q.OrderNumber,
CONVERT(NVarChar(1000), (
SELECT T.Description + ','
FROM N INNER JOIN #Option T ON N.OptionCode = T.OptionID
WHERE N.OrderItemID = Q.OrderItemID
FOR XML PATH(''))
) Options
FROM (
SELECT N.OrderItemID, O.OrderNumber
FROM #Order O INNER JOIN N ON O.OrderID = N.OrderID
GROUP BY N.OrderItemID, O.OrderNumber) Q
DROP TABLE #Order;
DROP TABLE #OrderItem;
DROP TABLE #Option;

Resources