SQL: Split a column into multiple - sql

I have the following table:
CREATE TABLE yow(
userid INT,
itemid INT,
feedback INT,
value INT)
(userid,itemid,feedback) can be considered a primary key, where each of these tuples contains a value.
I want a query which returns a table with the following columns:
userid | itemid | col0 | col1 | col2
Where col0 contains value for all rows in yow where feedback = 0, and col1 contains value where feedback = 1 and so on.
I have a somewhat working query:
SELECT
yow.userid AS uid,
yow.itemid AS iid,
isNull(col0.value, 0) AS col0,
IsNull(col1.value, 0) AS col1,
IsNull(col2.value, 0) AS col2
FROM yow
LEFT JOIN yow AS col0 ON col0.userid=yow.userid AND col0.itemid=yow.itemid
LEFT JOIN yow AS col1 ON col1.userid=yow.userid AND col1.itemid=yow.itemid
LEFT JOIN yow AS col2 ON col2.userid=yow.userid AND col2.itemid=yow.itemid
WHERE col0.feedback = 0
AND col1.feedback = 1
AND col2.feedback = 2
GROUP BY uid, iid
The problem is that I can have a value for (userid,itemid) in col1 or col2 but not the others. With this query, those rows are filtered out instead of the missing cells defaulting to 0.
As an example, I am getting something like this:
+-------+-------+--------+--------+--------+
| UID | IID | COL0 | COL1 | COL2 |
+-------+-------+--------+--------+--------+
| 1 | 101 | 23 | 22 | 241 |
| 1 | 101 | 51 | 13 | 159 |
| 2 | 102 | 22 | 55 | 152 |
| 3 | 103 | 14 | 41 | 231 |
+-------+-------+--------+--------+--------+
But instead I want something like this, where the missing values of col0 are defaulted to 0.
+-------+-------+--------+--------+--------+
| UID | IID | COL0 | COL1 | COL2 |
+-------+-------+--------+--------+--------+
| 1 | 101 | 23 | 22 | 241 |
| 1 | 101 | 51 | 13 | 159 |
| 1 | 102 | 0 | 15 | 142 |
| 2 | 102 | 22 | 55 | 152 |
| 2 | 103 | 0 | 45 | 92 |
| 3 | 103 | 14 | 41 | 231 |
+-------+-------+--------+--------+--------+
Can anyone suggest a fix to my query or perhaps propose a better one? I'm running this on H2, so I reckon the query should be somewhat standard. Thanks:)

The where condition will filter out all the null (missing) entries. To avoid that, you need to move the feedback = x to the join condition. Try the following instead:
SELECT
yow.userid AS uid,
yow.itemid AS iid,
isNull(col0.value, 0) AS col0,
IsNull(col1.value, 0) AS col1,
IsNull(col2.value, 0) AS col2
FROM yow
LEFT JOIN yow AS col0 ON col0.feedback = 0
AND col0.userid=yow.userid AND col0.itemid=yow.itemid
LEFT JOIN yow AS col1 ON col1.feedback = 1
AND col1.userid=yow.userid AND col1.itemid=yow.itemid
LEFT JOIN yow AS col2 ON col2.feedback = 2
AND col2.userid=yow.userid AND col2.itemid=yow.itemid
GROUP BY uid, iid

Related

sql group where clause

I have a table like this:
| grpType | grpId | paramId | val |
|---------|--------|:-------:|------:|
| 0 | 81452 | 123 | 1,293 |
| 0 | 81452 | 127 | 46 |
| 2 | 19873 | 282 | 3 |
| 2 | 19873 | 283 | -10,3 |
| 3 | 81455 | 123 | 1,144 |
| 3 | 100379 | 178 | 40 |
| 3 | 100379 | 188 | 269 |
| 3 | 100379 | 189 | 298 |
| 3 | 100379 | 190 | 267 |
| 3 | 100379 | 191 | 278 |
| 1 | 256 | 188 | 419 |
| 1 | 256 | 189 | 433 |
| 1 | 256 | 190 | 434 |
| 1 | 256 | 191 | 429 |
I want to get data from this table with conditions such as "paramId = 123 and val> = 1.2", "paramId=188 and val<=269", "paramId=189 and val>=298".
Here, since the conditions of "paramId = 188 and val <=269" and "paramId = 189 and val >298" have the same "grpId" in the table, both conditions specified for the "Val" column should provide. In the table above, There are 2 groups that meet the "paramId = 188, paramId = 189" condititon. I have to get the group that provides the requirement of "paramId = 188 and val <=269" and "paramId = 189 and val >298".(so the group of 100379 id)
However, the rows that provide the third condition (paramId = 123 and val >= 1.2) should be added to the data. In the table above, there are two "paramId = 123" row. I have to get the row that provides the requirement of "val >= 1.2".
The "grpType" column is not an important column. You can ignore.
I just need a distinct "grpID" list.
How should the query be I will write?
You can write combination of these conditions use OR in where condition.
Select *
from <TableName>
where ((paramId = 123 and Val >= 1.2) or (paramid=188 and Val <= 269) or (paramid=189 and Val <= 298))
It's like a query will work. If I use intersect, I can't get the rows itself. I can only get common data. It's enough for me now. The number of rows in this table is over 3 million and is constantly increasing. I'm a little worried about performance. I will continue the research.
(select grpId from tableX where paramId=51 and val>=600
intersect
select grpId from tableX where paramId=52 and val<15)
union
(select grpId from tableX where paramId=188 and val<= 269
intersect
select grpId from tableX where paramId=189 and val<= 298)
union
select grpId from tableX where paramId=123 and val<1.29

Change value in row based on a lookup from other rows

I have this data in a table(actually output of a query):
+--------------+------+---------+
| Connection | Pin | Circuit |
+--------------+------+---------+
| Value 1 | 1 | 33 |
| Value 1 | 2 | 1004 |
| Value 1 | 3 | 1015 |
| Value 1 | 4 | |
| Value 2 | SP-A | 1003 |
| Value 2 | SP-A | 1004 |
| Value 2 | SP-A | 1005 |
| Value 2 | SP-B | 1014 |
| Value 2 | SP-B | 1015 |
| Value 2 | SP-B | 1016 |
+--------------+------+---------+
I would like to use an SQL query to change it to this:
(changing the Pin based on a matching Circuit)
e.g.:
For each "SP-A", get the list of possible Circuits (1003, 1004, 1005)
Then look for a matching Circuit in another Connection (here this matches 1004, so we get Pin = 2)
Then replace the original value "SP-A" here with the match "2"
+------------+-------+---------+
| Connection | Pin | Circuit |
+------------+-------+---------+
| Value 1 | 1 | 33 |
| Value 1 | 2 | 1004 |
| Value 1 | 3 | 1015 |
| Value 1 | 4 | |
| Value 2 | *2* | 1003 |
| Value 2 | *2* | *1004*|
| Value 2 | *2* | 1005 |
| Value 2 | *3* | 1014 |
| Value 2 | *3* | *1015*|
| Value 2 | *3* | 1016 |
+------------+-------+---------+
My SQL skills are lacking.
I'm doing this in MS-Access.
First of all, try this, if it works, apply it on your main data.
CREATE TABLE #TEMP
(Connection nvarchar(50),
Pin nvarchar(50),
Circuit nvarchar(50))
INSERT INTO #TEMP
SELECT Connection, Pin, Circuit FROM Table_1
UPDATE TU
SET Pin = (
SELECT '*' + T1.Pin + '*' FROM Table_1 T1
INNER JOIN Table_1 T2 ON T1.Circuit = T2.Circuit AND T1.Connection <> T2.Connection AND T2.Pin = TU.Pin
)
FROM #TEMP TU
WHERE Connection = 'Value 2'
UPDATE TU
SET Circuit = '*' + T2.Circuit + '*'
FROM #TEMP TU
INNER JOIN #TEMP T2 ON TU.Circuit = T2.Circuit AND TU.Connection <> T2.Connection
WHERE TU.Connection = 'Value 2'
SELECT * FROM #TEMP
DROP TABLE #TEMP
You can express the logic using a correlated subquery:
update t
set pin = (select top (1) t2.pin
from t as t2
where t2.circuit = t.circuit and
t2.connection <> t.connection
)
where pin in ('SP-A', 'SP-B');

Pivoting a table in sql

I want same output shown in Output table. I have TableA and I want to pivote it and want output table as shown in image.
Thanks
I have a table #tableA
+-----+-------------+-------+------------+
| A | Allocations | Seats | EndDate |
+-----+-------------+-------+------------+
| ABC | 450 | 23 | 2017-10-05 |
| ABC | 23 | 765 | 2017-05-01 |
| PQR | 54 | 34 | 2017-07-04 |
| ABC | 234 | 45 | 2017-11-27 |
| PQR | 987 | 76 | 2017-03-05 |
| ABC | 76 | 65 | 2017-02-23 |
| PQR | 89 | 324 | 2017-08-14 |
| ABC | 45 | 34 | 2017-07-13 |
+-----+-------------+-------+------------+
Which can be created and populated as below.
CREATE TABLE #TableA
(
A VARCHAR(50),
Allocations INT,
Seats INT,
EndDate DATETIME
);
INSERT INTO #TableA
VALUES ('ABC',450,23,'2017-10-05'),
('ABC',23,765,'2017-05-01'),
('PQR',54,34,'2017-07-04'),
('ABC',234,45,'2017-11-27'),
('PQR',987,76,'2017-03-05'),
('ABC',76,65,'2017-02-23'),
('PQR',89,324,'2017-08-14'),
('ABC',45,34,'2017-07-13');
A column has ABC and PQR unique values.
Datetime column has multiple values.
How can I get the following output?
Datetime all Datetime values from TableA in column.
Output :-
date | 2017-12-13 | 2017-12-20 | 2017-12-27 | -|-|-|-|-|-|-|-|-|
-------------------------------------------------------------------------------
A | ABC | ABC | ABC |
Allocations | 50 | 50 | 50 |
Seats | 27 | 27 | 27 |
A | PQR | PQR | PQR |
Alloc | 50 | 50 | 50 |
Seats | 12 | 12 | 12 |
This is something that you should do in the reporting layer not SQL.
It is possible in SQL (demo) but not something that SQL is designed to do.
WITH T
AS (SELECT A,
thing,
priority,
value,
d
FROM #TableA
CROSS APPLY (VALUES(CAST(EndDate AS DATE)))D(d)
CROSS APPLY (VALUES(1, 'A', NULL),
(2, 'Allocations', Allocations),
(3, 'Seats', Seats)) V(priority, thing, value))
SELECT thing,
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-02-23],0) AS VARCHAR(50)) END AS [2017-02-23],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-03-05],0) AS VARCHAR(50)) END AS [2017-03-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-05-01],0) AS VARCHAR(50)) END AS [2017-05-01],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-04],0) AS VARCHAR(50)) END AS [2017-07-04],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-07-13],0) AS VARCHAR(50)) END AS [2017-07-13],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-08-14],0) AS VARCHAR(50)) END AS [2017-08-14],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-10-05],0) AS VARCHAR(50)) END AS [2017-10-05],
case when thing = 'A' THEN A ELSE CAST(ISNULL([2017-11-27],0) AS VARCHAR(50)) END AS [2017-11-27]
FROM T
PIVOT (SUM(value) FOR d in (
[2017-02-23],
[2017-03-05],
[2017-05-01],
[2017-07-04],
[2017-07-13],
[2017-08-14],
[2017-10-05],
[2017-11-27])) P
ORDER BY A, priority
The above doesn't even address the dynamic aspect. For that you would need to generate a dynamic SQL string based on the above and the dates in #TableA

Horizontal Count SQL

I apologize if this is a duplicate question but I could not find my answer.
I am trying to take data that is horizontal, and get a count of how many times a specific number appears.
Example table
+-------+-------+-------+-------+
| Empid | KPI_A | KPI_B | KPI_C |
+-------+-------+-------+-------+
| 232 | 1 | 3 | 3 |
| 112 | 2 | 3 | 2 |
| 143 | 3 | 1 | 1 |
+-------+-------+-------+-------+
I need to see the following:
+-------+--------------+--------------+--------------+
| EmpID | (1's Scored) | (2's Scored) | (3's Scored) |
+-------+--------------+--------------+--------------+
| 232 | 1 | 0 | 2 |
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
+-------+--------------+--------------+--------------+
I hope that makes sense. Any help would be appreciated.
Since you are counting data across multiple columns, it might be easier to unpivot your KPI columns first, then count the scores.
You could use either the UNPIVOT function or CROSS APPLY to convert your KPI columns into multiple rows. The syntax would be similar to:
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
See SQL Fiddle with Demo. This gets your multiple columns into multiple rows, which is then easier to work with:
| EMPID | KPI | VAL |
|-------|-----|-----|
| 232 | A | 1 |
| 232 | B | 3 |
| 232 | C | 3 |
| 112 | A | 2 |
Now you can easily count the number of 1's, 2's, and 3's that you have using an aggregate function with a CASE expression:
select EmpId,
sum(case when val = 1 then 1 else 0 end) Score_1,
sum(case when val = 2 then 1 else 0 end) Score_2,
sum(case when val = 3 then 1 else 0 end) Score_3
from
(
select EmpId, KPI, Val
from yourtable
cross apply
(
select 'A', KPI_A union all
select 'B', KPI_B union all
select 'C', KPI_C
) c (KPI, Val)
) d
group by EmpId;
See SQL Fiddle with Demo. This gives a final result of:
| EMPID | SCORE_1 | SCORE_2 | SCORE_3 |
|-------|---------|---------|---------|
| 112 | 0 | 2 | 1 |
| 143 | 2 | 0 | 1 |
| 232 | 1 | 0 | 2 |

select the most recent in all groups of with the same value in one column

The question isn't very clear, but I'll illustrate what I mean, suppose my table is like such:
item_name | date added | val1 | val2
------------------------------------
1 | date+1 | 10 | 20
1 | date | 12 | 21
2 | date+1 | 5 | 6
3 | date+3 | 3 | 1
3 | date+2 | 5 | 2
3 | date | 3 | 1
And I want to select row 1, 3, 4 as they are the most recent entries for each item
Try this:
select *
from tableX t1
where t1.date_added = (select max(t2.date_added)
from tableX t2
where t2.item_name = t1.item_name )