Dynamic PIVOT two columns prefix - tsql

Database: Microsoft SQL Server 2012
I have one table that contains the computers and the software installed.
Now, one computer can have 30+ software installed but there are probably 100+ different software on the infrastructure.
This is a representation of the table
Computer | Software
--------------------
PC123 | Office
PC123 | Firefox
PC456 | Office
PC456 | Firefox
PC456 | CAD
PC789 | Firefox
PC789 | Outlook
...
I'm looking for a result that would look like this
Computer | Software 1 | Software 2 | Software 3
------------------------------------------------
PC123 | Firefox | Office | NULL
PC456 | CAD | Firefox | Office
PC789 | Firefox | Outlook | NULL
...
I've been looking into dynamic PIVOT but I'm still new with SQL.
Thank you for the help

There are countless examples of dynamic pivots here, however, I understand sometimes we all need a little jump-start.
Example
Declare #SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(concat('Software ',Row_Number() over (Partition By Computer Order By Software))) From Yourtable Order by 1 For XML Path('')),1,1,'')
Select #SQL = '
Select [Computer],' + #SQL + '
From (
Select Computer
,Software
,Col = concat(''Software '',Row_Number() over (Partition By Computer Order By Software))
From YourTable
) A
Pivot (max([software]) For [Col] in (' + #SQL + ') ) p'
Exec(#SQL);
Returns
Computer Software 1 Software 2 Software 3
PC123 Firefox Office NULL
PC456 CAD Firefox Office
PC789 Firefox Outlook NULL
If it helps, the generated SQL looks like this:
Select [Computer],[Software 1],[Software 2],[Software 3]
From (
Select Computer
,Software
,Col = concat('Software ',Row_Number() over (Partition By Computer Order By Software))
From YourTable
) A
Pivot (max([software]) For [Col] in ([Software 1],[Software 2],[Software 3]) ) p

Another alternative is to use the stuff function and put all of the software into one line.
My code might be slightly off. I suck without being able to test.
Results should look like this:
Computer Software
PC123 Firefox, CAD, Office
PC456 Firefox
SELECT t.Computer, Software_String.Software
FROM (SELECT DISTINCT Computer FROM TABLE) t
OUTER APPLY (
SELECT REPLACE(STUFF((
SELECT ', ' + w.Start_Date, 100)
FROM Computer w
WHERE c.Computer_ID = w.Computer_ID
FOR XML PATH('')
), 1, 2, ''), ' ', ' ') [Software]
) Software_String
Or you can use a case statement instead of pivots. I find pivot programming slow and a pain to write.

Related

How to get multiple columns with a single group by in Postgres?

I have a table that looks like below
Table "public.test_systems"
Column | Type | Modifiers
-----------------------------+------------------------+-----------
rid | integer | not null
r_osname | character varying(255) |
r_health | integer |
r_patch | bigint |
r_loc | character varying(255) |
Here each row in the table depicts a system. Now if I want to find out how many systems by unique OS names, I do a query like below
select r_osname, count(*) as total_systems from test_systems group by r_osname;
So I get a result like below
r_osname | total_systems
-----------------------------------------------+--------------
Ubuntu 18.04.4 LTS | 18
Windows 10 Pro | 2
CentOS Linux | 1
Windows Server 2019 | 3
Mac OS X - High Sierra | 2
Now I want to run the same query but for multiple columns. In other words I want to get multiple columns with a single groupby. But Postgres forces me to mention the additional columns in the groupby too.
I tried distinct on in my query like below
select distinct on (r_osname) test_systems.* from test_systems order by os_name;
I got same number of rows (partial success) but can't get the count(*) as an additional column.
The final result could look something like below (on including additional columns like r_health and r_loc)
r_osname | r_health | r_loc | total_systems
-----------------------------------------------+-----------------------------------+--------------------+--------------
Ubuntu 18.04.4 LTS | 1012 | NYC | 18
Windows 10 Pro | 1121 | LON | 2
CentOS Linux | 1255 | DEL | 1
Windows Server 2019 | 1451 | HYD | 3
Mac OS X - High Sierra | 1120 | LA | 2
How do I get the expected result?
You need a window function to make this work:
SELECT DISTINCT
r_osname, r_health, r_loc,
count(*) OVER (PARTITION BY r_osname, r_health, r_loc)
FROM test_systems
Depending on which combination of values in the columns you want to include in the result you can play with the DISTINCT ON (...) clause. Without a DISTINCT clause you will get as many rows as there are in the table (26 in your example) but if you want only 1 row for each OS then you should use DISTINCT ON (r_osname). The row that will be returned depends on the ORDER BY clause - if none is given then the first row will be returned for each set of rows which have the same r_osname but there will be no way to predict which row that will be.

How to get value from a column what has maximum count in postgres?

I understand the MAX function returns a maximum value from a given column for a numeric column. I want to return the maximum count of a column. The below table has two columns maker, product.
maker product
A Printer
B Laptop
B Laptop
A Printer
A Monitor
A Printer
A Scanner
But when I run the query '''select maker, max(type) from table group by maker; ''' I am getting the result as
maker product
A Monitor
B Laptop
I want which product has a maximum count with respect to the maker like below.
maker product
A Printer
B Laptop
Note: As maker count differs, we can't set a common number in Having clause.
You can do it with RANK() window function:
select t.maker, t.product
from (
select maker, product,
rank() over (partition by maker order by count(*) desc) rn
from tablename
group by maker, product
) t
where t.rn = 1
See the demo.
Results:
| maker | product |
| ----- | ------- |
| A | Printer |
| B | Laptop |
In PostgreSQL, the simplest solution is to use DISTINCT ON:
SELECT DISTINCT ON(maker)
maker, product
FROM atable
GROUP BY maker, product
ORDER BY maker, count(*) DESC;

Add padding to IP addresses in PostgreSQL SELECT Query?

I've already got a method for excel, but I want the padding to be done via the query to reduce my effort later in the process
Excel Example
=TEXT(LEFT(A2,FIND(".",A2,1)-1),"000") & "." & TEXT(MID(A2,FIND(
".",A2,1)+1,FIND(".",A2,FIND(".",A2,1)+1)-FIND(".",A2,1)-1),"000")
& "." & TEXT(MID(A2,FIND(".",A2,FIND(".",A2,1)+1)+1,FIND(".",A2,
FIND(".",A2,FIND(".",A2,1)+1)+1)-FIND(".",A2,FIND(".",A2,1)+1)-1),
"000") & "." & TEXT(RIGHT(A2,LEN(A2)-FIND(".",A2,FIND(".",A2,FIND(
".",A2,1)+1)+1)),"000")
I tried searching the PostgreSQL documentation but nothing was obvious on converting to padded
I also investigated potentially doing a CAST as I have done for hostnames utilizing regex
Hostname CAST Example for PostgreSQL
UPPER(regexp_replace(da.host_name, '([\.][\w\.]+)', '', 'g')) AS hostname
But, I am hitting a roadblock here. Any suggestions?
I'm making the assumption that by "Padding an IP" you are wanting to lpad 0's to the front of each ip part.
Using regexp_replace you can do the following:
SELECT regexp_replace(regexp_replace('19.2.2.2', '([0-9]{1,3})', '00\1', 'g'), '(0*)([0-9]{3})', '\2', 'g');
Optionally if you are on 9.4 or newer you could get crafty with UNNEST() or REGEXP_SPLIT_TO_TABLE() and the new WITH ORDINALITY keyword to split each ip part (and the key from the table) out to its own row. Then you can lpad() with 0's and string_agg() it back together using the ordinal that was preserved in the unnest or regexp_split_to_table():
user=# SELECT * FROM test;
id | ip
----+--------------
1 | 19.16.2.2
2 | 20.321.123.1
(2 rows)
user=# SELECT id, string_agg(lpad(ip_part, 3, '0'),'.' ORDER BY rn) FROM test t, regexp_split_to_table(t.ip, '\.') WITH ORDINALITY s(ip_part, rn) GROUP BY id;
id | string_agg
----+-----------------
1 | 019.016.002.002
2 | 020.321.123.001
(2 rows)
Theoretically this would work in older versions since it seems like ordinals are preserved during unnest(), but it feels more like luck and I wouldn't productionize any code that depends on that.

Postgresql , opposite of a group by

Here's my use case:
We have a analytics-like tools which used to count the number of users per hour on our system. And now the business would like to have the number of unique users. As our amount of user is very small, we will do that using
SELECT count(*)
FROM (
SELECT DISTINCT user_id
FROM unique users
WHERE date BETWEEN x and y
) distinct_users
i.e we will store the couple user_id, date and count unique users using DISTINCT (user_id is not a foreign key, as users are not logged in, it's just a unique identifier generated by the system, some kind of uuidv4 )
this works great in term of performance for a magnitude of data.
Now the problem is to import legacy data in it
I would like to know the SQL query to transform
date | number_of_users
12:00 | 2
13:00 | 4
into
date | user_id
12:00 | 1
12:00 | 2
13:00 | 1
13:00 | 2
13:00 | 3
13:00 | 4
(as long as the "count but not unique" returns the same number as before, we're fine if the "unique users count" is a bit off)
Of course, I could do a python script, but I was wondering if there was a SQL trick to do that, using generate_series or something related
generate_series() is indeed the way to go:
with data (date, number_of_users) as (
values
('12:00',2),
('13:00',4)
)
select d.date, i.n
from data d
cross join lateral generate_series(1, d.number_of_users) i (n)
order by d.date, i.n ;

MySQL Select if field is unique or null

Sorry, I can't find an example anywhere, mainly because I can't think of any other way to explain it that doesn't include DISTINCT or UNIQUE (which I've found to be misleading terms in SQL).
I need to select unique values AND null values from one table.
FLAVOURS:
id | name | flavour
--------------------------
1 | mark | chocolate
2 | cindy | chocolate
3 | rick |
4 | dave |
5 | jenn | vanilla
6 | sammy | strawberry
7 | cindy | chocolate
8 | rick |
9 | dave |
10 | jenn | caramel
11 | sammy | strawberry
I want the kids who have a unique flavour (vanilla, caramel) and the kids who don't have any flavour.
I don't want the kids with duplicate flavours (chocolate, strawberry).
My searches for help always return an answer for how to GROUP BY, UNIQUE and DISTINCT for chocolate and strawberry. That's not what I want. I don't want any repeated terms in a field - I want everything else.
What is the proper MySQL select statement for this?
Thanks!
You can use HAVING to select just some of the groups, so to select the groups where there is only one flavor, you use:
SELECT * from my_table GROUP BY flavour HAVING COUNT(*) = 1
If you then want to select those users that have NULL entries, you use
SELECT * FROM my_table WHERE flavour IS NULL
and if you combine them, you get all entries that either have a unique flavor, or NULL.
SELECT * from my_table GROUP BY flavour HAVING COUNT(*) = 1 AND flavour IS NOT NULL
UNION
SELECT * FROM my_table WHERE flavour IS NULL
I added the "flavour IS NOT NULL" just to ensure that a flavour that is NULL is not picked if it's the single one, which would generate a duplicate.
I don't have a database to hand, but you should be able to use a query along the lines of.
SELECT name from FLAVOURS WHERE flavour IN ( SELECT flavour, count(Flavour) from FLAVOURS where count(Flavour) = 1 GROUP BY flavour) OR flavour IS NULL;
I apologise if this isn't quite right, but hopefully is a good start.
You need a self-join that looks for duplicates, and then you need to veto those duplicates by looking for cases where there was no match (that's the WHERE t2.flavor IS NULL). Then your're doing something completely different, looking for nulls in the original table, with the second line in the WHERE clause (OR t1.flavor IS NULL)
SELECT DISTINCT t1.name, t1.flavor
FROM tablename t1
LEFT JOIN tablename t2
ON t2.flavor = t1.flavor AND t2.ID <> t1.ID
WHERE t2.flavor IS NULL
OR t1.flavor IS NULL
I hope this helps.