SQL Server query to stack columns vertically for summation, with multiple variables - sql-server-2008

I have several columns that contain multiple variables and their associated values. I want to stack the Variable columns and their associated Value columns for summation.
Each variable (eg ABC) may or may not appear in each column. There are about 200 variables (and their associated values) that may appear in 10 variable/value columns. I have just shown 6 variables in 3 variable/value pairs.
Here are my columns:
Variable1 Value1 Variable2 Value2 Variable3 Value3
ABC 1 ABC 9 ABC 6
DEF 2 DEF 8 DEF 5
XYZ 3 XYZ 7 XYZ 4
KLM 4 KLM 6 KLM 3
TUV 5 TUV 2
GHI 1
Here is my desired output:
VariableAll ValueAll
ABC 16
DEF 15
XYZ 14
KLM 13
TUV 7
GHI 1
Any guidance you can provide would be greatly appreciated. Thank you for your help!

You need 3 separate selects, one for each columns pair, and union all to combine them into single result.
declare #t table(Variable1 varchar(50), Value1 int, Variable2 varchar(50), Value2 int, Variable3 varchar(50), Value3 int)
insert into #t(Variable1, Value1, Variable2, Value2, Variable3, Value3) values
('ABC', 1, 'ABC', 9, 'ABC', 6),
('DEF', 2, 'DEF', 8, 'DEF', 5),
('XYZ', 3, 'XYZ', 7, 'XYZ', 4),
('KLM', 4, 'KLM', 6, 'KLM', 3),
('TUV', 5, null, null, 'TUV', 2),
( null, null, null, null, 'GHI', 1)
;with cte (Variable, [Value]) as (
select Variable1, Value1 from #t where Variable1 is not null
union all
select Variable2, Value2 from #t where Variable2 is not null
union all
select Variable3, Value3 from #t where Variable3 is not null
)
select Variable, SUM([Value]) as Total
from cte
group by Variable
order by Total desc

Related

MySQL update with correlated query on the same table

The problem can be simplified as follows:
Original state:
id type val
---------------
1 1 300
2 1 200
3 1 100
4 2 10
5 2 20
6 2 30
Desired state - for a range of ids (here 3 to 4), update val so that it's equal to the maximum val for the given type:
id type val
---------------
1 1 300
2 1 200
3 1 300 <--
4 2 30 <--
5 2 20
6 2 30
So only rows 3 and 4 were updated, maximum val for type 1 is 300, for type 2 it's 30.
I thought it's an easy update, but I can't get it to work.
Here's the table definition and data:
create table test (id integer primary key, type integer, val integer);
insert into test (id, type, val) values (1, 1, 300);
insert into test (id, type, val) values (2, 1, 200);
insert into test (id, type, val) values (3, 1, 100);
insert into test (id, type, val) values (4, 2, 10);
insert into test (id, type, val) values (5, 2, 20);
insert into test (id, type, val) values (6, 2, 30);
And here's what I thought would work:
update test t1 set t1.val=(
select max(t2.val) from (
select * from test where type=t1.type
) t2
) where id>=3 and id<5;
However MySQL gives the error:
Unknown column 't1.type' in 'where clause'
I've found some answers to similar problems suggesting rewriting the update so that it uses JOIN, but I'm not sure how to do that - my attempt resulted in an error message about a nonexisting table. Please tell me what I'm missing.
Try this query:
UPDATE test t1 INNER JOIN
(SELECT TYPE,MAX(val) mval FROM test GROUP BY TYPE) t2
ON t1.type=t2.type
SET t1.val=t2.mval
WHERE t1.id BETWEEN 3 AND 4;
Fiddle here : https://www.db-fiddle.com/f/7Sn9aDBPze7pVvHNHFzQ1u/2
To answer your quesetion, you need to select only val from inner sub-query.
This works:
UPDATE test1 t1
SET
t1.val = (SELECT
MAX(t2.val)
FROM
(SELECT
val
FROM
test1
WHERE
type = t1.type) t2)
WHERE
id >= 3 AND id < 5;

Ignore null entries in sql

The example below builds a table that extracts the first two score values by userId and passageId. How can I select only records where each record in the new table contains at least two scores (i.e. ignore records where score2 is null)?
Example
Code:
drop table if exists simon;
drop table if exists simon2;
Create table simon (userId int, passageId int, score int);
Create table simon2 (userId int, passageId int, score1 int,score2 int);
INSERT INTO simon (userId , passageId , score )
VALUES
(10, 1, 2),
(10, 1, 3),
(10, 2, 1),
(10, 2, 1),
(10, 2, 5),
(11, 1, 1),
(11, 2, 2),
(11, 2, 3),
(11, 3, 4);
insert into simon2(userId,passageId,score1,score2)
select t.userId, t.passageId,
substring_index(t.scores,',',1) as score1,
(case when length(t.scores) > 1 then substring_index(t.scores,',',-1)
else null
end
) as score2
from
(select userId,passageId,
substring_index (group_concat(score separator ','),',',2) as scores
from simon
group by userId,passageId) t;
select *from simon2;
This is what I get now:
userId passageId score1 score2
1 10 1 2 3
2 10 2 1 1
3 11 1 1 NULL
4 11 2 2 3
5 11 3 4 NULL
This is what I want:
userId passageId score1 score2
1 10 1 2 3
2 10 2 1 1
4 11 2 2 3
Just add this around your query
Select * from ( ...... ) x where score2 is not null
You have no ordering specifying what score goes into score_1 and score_2, so I'll just use min() and max(). You can then do the logic as:
select s.userid, s.passageid,
max(score) as score_1, max(score) as score_2
from simon s
group by s.userid, s.passageid
having count(*) >= 2;
This doesn't give you exactly the same results for 10/2. However, your results are arbitrary because the group_concat() has no order by. SQL tables represent unordered sets. There is no ordering unless you specify it.
If you want an ordering, then define the table as:
Create table simon (
simonId int auto_increment primary key,
userId int,
passageId int,
score int
);
And then use the simonId column appropriately.

SQL - Return the order of products purchased by adding new column

I'm working on another SQL query.
I have the following table.
PURCHASES
ID CUST_ID PROD_CODE PURCH_DATE
1 1 'WER' 01/12/2012
2 2 'RRE' 02/10/2005
3 3 'RRY' 02/11/2011
4 3 'TTB' 15/05/2007
5 3 'GGD' 20/06/2016
6 2 'SSD' 02/10/2011
I'm trying to add another column PURCH_COUNT that would display the purchase count for the CUST_ID based on PURCH_DATE.
If this is a first purchase it would return 1, if second then 2, and so on.
So the result I'm hoping is:
ID CUST_ID PROD_CODE PURCH_DATE PURCH_COUNT
1 1 'WER' 01/12/2012 1
2 2 'RRE' 02/10/2005 1
3 3 'RRY' 02/11/2011 2
4 3 'TTB' 15/05/2007 1
5 3 'GGD' 20/06/2016 3
6 2 'SSD' 02/10/2011 2
Thanks in advance!
Sample Data
DECLARE #Table1 TABLE
(ID int, CUST_ID int, PROD_CODE varchar(7), PURCH_DATE datetime)
;
INSERT INTO #Table1
(ID, CUST_ID, PROD_CODE, PURCH_DATE)
VALUES
(1, 1, 'WER', '2012-01-12 05:30:00'),
(2, 2, 'RRE', '2005-02-10 05:30:00'),
(3, 3, 'RRY', '2011-02-11 05:30:00'),
(4, 3, 'TTB', '2008-03-05 05:30:00'),
(5, 3, 'GGD', '2017-08-06 05:30:00'),
(6, 2, 'SSD', '2011-02-10 05:30:00')
;
IN SQL :
select ID,
CUST_ID,
PROD_CODE,
PURCH_DATE,
ROW_NUMBER()OVER(PARTITION BY CUST_ID ORDER BY (SELECT NULL))RN
from #Table1
In MySql :
SELECT a.ID, a.CUST_ID,a.PROD_CODE,a.PURCH_DATE, (
SELECT count(*) from #Table1 b where a.CUST_ID >= b.CUST_ID AND a.ID = b.ID
) AS row_number FROM #Table1 a
Use a correlated sub-query to get the counts per customer.
SELECT t.*,
(SELECT 1+count(*)
FROM table1
WHERE t.cust_id = cust_id
AND t.purch_date > purch_date) as purch_cnt
FROM table1 t
ORDER BY cust_id,purch_date
SQL Fiddle
Any correlated subquery or window function can be expressed as a join, too. Sometimes a join is easier to understand, or is produced from components you can re-use, and sometimes the DBMS doesn't support the fancier feature. (AFAIK, a subquery in a SELECT clause is nonstandard.)
create table T
(ID, CUST_ID, PROD_CODE, PURCH_DATE);
INSERT INTO T
(ID, CUST_ID, PROD_CODE, PURCH_DATE)
VALUES
(1, 1, 'WER', '2012-01-12 05:30:00'),
(2, 2, 'RRE', '2005-02-10 05:30:00'),
(3, 3, 'RRY', '2011-02-11 05:30:00'),
(4, 3, 'TTB', '2008-03-05 05:30:00'),
(5, 3, 'GGD', '2017-08-06 05:30:00'),
(6, 2, 'SSD', '2011-02-10 05:30:00')
;
select PURCH_COUNT, T.*
from T join (
select count(b.ID) as PURCH_COUNT
, a.CUST_ID, a.PURCH_DATE
from T as a join T as b
on a.CUST_ID = b.CUST_ID
and b.PURCH_DATE <= a.PURCH_DATE
group by a.CUST_ID, a.PURCH_DATE
) as Q
on T.CUST_ID = Q.CUST_ID
and T.PURCH_DATE = Q.PURCH_DATE
;
Output of subquery:
PURCH_COUNT CUST_ID PURCH_DATE
----------- ---------- -------------------
1 1 2012-01-12 05:30:00
1 2 2005-02-10 05:30:00
2 2 2011-02-10 05:30:00
1 3 2008-03-05 05:30:00
2 3 2011-02-11 05:30:00
3 3 2017-08-06 05:30:00
Output of query:
PURCH_COUNT ID CUST_ID PROD_CODE PURCH_DATE
----------- ---------- ---------- ---------- -------------------
1 1 1 WER 2012-01-12 05:30:00
1 2 2 RRE 2005-02-10 05:30:00
2 3 3 RRY 2011-02-11 05:30:00
1 4 3 TTB 2008-03-05 05:30:00
3 5 3 GGD 2017-08-06 05:30:00
2 6 2 SSD 2011-02-10 05:30:00

How to compare and change values in MYSQL

My table looks like:
[Number] [Value1]
1234567 8
1234567C 7
9876543 1
9876543C 2
5555555 3
5555555C 3
I want to search the entries for same values in the first column (except the "C" in the end of the number) and set the higher value in the second column to the lower one.
There are always only two same values (one with "C") and some pairs have same values in the second column and some have different.
The result of the query should be:
Number Value1
1234567 7
1234567C 7
9876543 1
9876543C 1
5555555 3
5555555C 3
The following is not an ideal solution but should do what you want:
update yourTable
set value1 = (
select min(value1) from (
select * from yourTable
) as x
where yourTable.number = x.number + 'C');
I have tested it with this in mysql workbench:
create table yourTable(number varchar (10),value1 int);
insert into yourTable Values('1234567',8);
insert into yourTable Values('1234567C',7);
insert into yourTable Values('9876543',1);
insert into yourTable Values('9876543C',2);
insert into yourTable Values('5555555',3);
insert into yourTable Values('5555555C',3);
insert into yourTable Values('55555556',10);
insert into yourTable Values('55555556C',2);
Then select * from yourTable;will return:
1234567 8
1234567C 7
9876543 1
9876543C 2
5555555 3
5555555C 3
55555556 10
55555556C 2
After the update select * from yourTable; will return:
1234567 7
1234567C 7
9876543 1
9876543C 1
5555555 3
5555555C 3
55555556 2
55555556C 2
Hope that is what you wanted :)
Actually, you don't need any checking, since there are only 2 values (and thus the query is even simpler):
UPDATE
table
SET
Value1 =
(
SELECT
MAX(Value1)
FROM
table t
WHERE
table.Number = t.Number
OR table.Number = t.Number + 'C'
)
WHERE
RIGHT(Number, 1) != 'C'

Query for a threaded discussion

I have a single table with a self reference InReplyTo with some data like this:
PostID InReplyTo Depth
------ --------- -----
1 null 0
2 1 1
3 1 1
4 2 2
5 3 2
6 4 3
7 1 1
8 5 3
9 2 2
I want to write a query that will return this data in it's threaded form so that the post with ID=2 and all it's descendants will output before PostID=3 and so on for unlimited depth
PostID InReplyTo Depth
------ --------- -----
1 null 0
2 1 1
4 2 2
6 4 3
9 2 2
3 1 1
5 3 2
8 5 3
7 1 1
Is there a simple way to achieve this? I am able to modify the DB structure at this stage so would the new hierarchy datatype be the easiest way to go? Or perhaps a recursive CTE?
-- Test table
declare #T table (PostID int, InReplyTo int, Depth int)
insert into #T values (1, null, 0), (2, 1, 1), (3, 1, 1), (4, 2, 2),
(5, 3, 2), (6, 4, 3), (7, 1, 1), (8, 5, 3),(9, 2, 2)
-- The post to get the hierarchy from
declare #PostID int = 1
-- Recursive cte that builds a string to use in order by
;with cte as
(
select T.PostID,
T.InReplyTo,
T.Depth,
right('0000000000'+cast(T.PostID as varchar(max)), 10)+'/' as Sort
from #T as T
where T.PostID = #PostID
union all
select T.PostID,
T.InReplyTo,
T.Depth,
C.Sort+right('0000000000'+cast(T.PostID as varchar(max)), 10)+'/'
from #T as T
inner join cte as C
on T.InReplyTo = C.PostID
)
select PostID,
InReplyTo,
Depth,
Sort
from cte
order by Sort
Result:
PostID InReplyTo Depth Sort
----------- ----------- ----------- --------------------------------------------
1 NULL 0 0000000001/
2 1 1 0000000001/0000000002/
4 2 2 0000000001/0000000002/0000000004/
6 4 3 0000000001/0000000002/0000000004/0000000006/
9 2 2 0000000001/0000000002/0000000009/
3 1 1 0000000001/0000000003/
5 3 2 0000000001/0000000003/0000000005/
8 5 3 0000000001/0000000003/0000000005/0000000008/
7 1 1 0000000001/0000000007/
What you are looking for is indeed a recursive query.
A matching example to your case can be found here