SQL row value as column name - sql

I'm pretty experienced in C#, but still mostly a beginner in SQL.
We have an application in C#, using an MSSQL database.
One part of our application is simply a list of pre-written queries as reports that the application shows by simply running the query and sticking the returned table into a gridView.
The user is requesting a new report that I'm not entirely sure is even possible with just SQL and is going to require manipulation of the returned data to get it to look right.
The data that the users want, in the way they want it presented would require me to be able to take this table:
Date Category Count
---------------------
date1 Cat1 x1
date1 Cat2 y1
...
date1 CatN z1
gotten from this query:
select Date, Category, COUNT(*) as 'Count'
from Table
group by Date, Category
turned into this table:
Date Cat1 Cat2 ... CatN
---------------------------
date1 x1 y1 ... z1
date2 x2 y2 ... z2
so that I can join it by date to the other half of the data they want.
Long-winded explanation, short question and followup: is this possible to do, no matter how painfully, with just SQL?
If so, how?

You need to use pivot. One issue that may give you problems is that you must know how many categories you have since a query can't ever return a dynamic number of columns (not even one that you're pivoting).
I don't have your schema, but this should be about right (may take a few touchups here and there, but it should get you started)...
select
Date, [1] as Cat1, [2] as Cat2, [3] as Cat3, ...
from
(select date, category from table) p
pivot (
count(*)
for
Category in ([1],[2],[3],...)
) pivoted
order by
pivoted.date

Related

Particular dates in one table based on dates from another table

I have two tables, one with tasks and the other with some relative data as below:
Task
Client
Date
Hummer qty
Made something a
X1
01.02.2022
Made something b
X2
05.02.2022
Made something c
X3
05.02.2022
Made something d
X2
07.02.2022
So this one represents daily activity for different clients. I need to fill "Hummer qty" column for every day entered for every Client using second table below where there is Dated history of each Client, at which dates quantity of hummers were increased by each Client.
Client
Date
Hummer qty
X1
15.03.2021
1
X1
23.05.2021
3
X2
08.02.2019
1
X2
06.02.2022
2
X2
06.03.2022
3
X3
16.03.2022
1
as a result, first table should be as below:
Task
Client
Date
Hummer qty
Made something a
X1
01.02.2022
3
Made something b
X2
05.02.2022
1
Made something c
X3
05.02.2022
0
Made something d
X2
07.02.2022
2
in this case tried to use Dlookup with conditions where daily dates >= to history dates + Clients are identical between tables. But it does not work (empty). Simple query returns only first found quantities in history and set it to all the rows of respective client.
You want the qty of History table where History Date is closest to and lower than Tasks Date. This can be done with TOP N correlated subquery.
Consider:
SELECT Tasks.*, (SELECT TOP 1 HummerQty FROM History AS H
WHERE H.Client=Tasks.Client AND H.Date<Tasks.Date
ORDER BY H.Date DESC) AS HQ
FROM Tasks;
There is no need to commit Qty data to Tasks table as it can be calculated when needed. Also, the query cannot be directly used as source for an update because it is not an editable query. If you really must update:
UPDATE Tasks
SET HummerQty=DLookup("HQ","Query1","Client='" & [Client] & "' AND [Date]=#" & [Date] & "#")
If Tasks table has a unique identifier (such as autonumber, which can be easily added), include it in Query1 and then use it in the DLookup WHERE CONDITION argument instead of the compound criteria.
Your data shows dates as international format. This can complicate using date values in comparison expressions. Review http://allenbrowne.com/ser-36.html

QUERY - GROUP BY counting different clients by date

Data sample:
https://docs.google.com/spreadsheets/d/1DDs2PvljSsY0jD0v2VmM0NsGkJmhoBPKOXvln9d1MTw/edit?usp=sharing
Above is a link to a spreadsheet where I have a sample of the data I'm working with.
I need to do a query where i can count how many different clients I attended that day. Example (INFO column not needed as a result, just a helper here for me to describe what I need):
DATE
COUNT(DIFFERENT CLIENTS)
INFO
05/01/2021
3
"Fleury Campinas", "SEDI II AME SOROCABA", "Hospital Santa Catarina"
06/01/2021
2
"Hospital e Maternidade Metropolitano Lapa", "Fleury A+ Morumbi"
Can you help me?
Given the layout of the actual data and the locale of Brazil, I added a new sheet ("Erik Help") with this formula:
=ArrayFormula({"DATE"\"UNIQUE CLIENTS"\"INFO";{QUERY(UNIQUE({'Query from Data'!B5:B\'Query from Data'!E5:E});"Select Col1, COUNT(Col2) WHERE Col1 Is Not Null GROUP BY Col1 LABEL COUNT(Col2) ''")\REGEXREPLACE(REGEXREPLACE(TRIM(FLATTEN(QUERY(QUERY({'Query from Data'!B5:B\'Query from Data'!E5:E&","}; "Select MAX(Col2) WHERE Col1 Is Not Null GROUP BY Col2 PIVOT Col1");; 9^9)));"^\S+\s*|[,\s]+$";"");",\s*";CHAR(10))}})
This is complex, and explaining it in full would take far longer than writing it. So I am offering it as-is, inviting anyone who is interested to take it apart and see what each part does alone and collectively.
The short version:
Headers are created.
Under those, there is a virtual array formed of a two-column QUERY to the left of another one-column QUERY. The first returns unique dates and counts of unique clients (two columns). The second combines each date with the unique client list for that date and then uses REGEX-type commands to get rid of the date portion and to replace comma-space with a line return CHAR(10).
Try getting the unique values in the first and third columns then grouping by date using a query:
=query(unique({A:A,C:C}),"select Col1,count(Col2) where Col1 is not null group by Col1")

How can I use a row value to dynamically select a column name in Oracle SQL 11g?

I have two tables, one with a single row for each "batch_number" and another with defect details for each batch. The first table has a "defect_of_interest" column which I would like to link to one of the columns in the second table. I am trying to write a query that would then pick the maximum value in that dynamically linked column for any "unit_number" in the "batch_number".
Here is the SQLFiddle with example data for each table: http://sqlfiddle.com/#!9/a1c27d
For example, the maximum value in the DEFECT_DETAILS.SCRATCHES column for BATCH_NUMBER = A1 is 12.
Here is my desired output:
BATCH_NUMBER DEFECT_OF_INTEREST MAXIMUM_DEFECT_COUNT
------------ ------------------ --------------------
A1 SCRATCHES 12
B3 BUMPS 4
C2 STAINS 9
I have tried using the PIVOT function, but I can't get it to work. Not sure if it works in cases like this. Any help would be much appreciated.
If the number of columns is fixed (it seems to be) you can use CASE to select the specific value according to the related table. Then aggregating is simple.
For example:
select
batch_number,
max(defect_of_interest) as defect_of_interest,
max(defect_count) as maximum_defect_count
from (
select
d.batch_number,
b.defect_of_interest,
case when b.defect_of_interest = 'SCRATCHES' then d.scratches
when b.defect_of_interest = 'BUMPS' then d.bumps
when b.defect_of_interest = 'STAINS' then d.stains
end as defect_count
from defect_details d
join batches b on b.batch_number = d.batch_number
) x
group by batch_number
order by batch_number;
See Oracle example in db<>fiddle.

How to make a progressive in SQL that increments each x records where x can potentially be different for each row?

I'm trying to write a progressive number in my SQL code that increments every x record, but this x can vary each time.
The result that I want looks like this.
N1 Var Date
1 x1 Date1
1 x2 Date1
1 x3 Date1
2 x1 Date2
2 x3 Date2
3 x2 Date3
Var is a variable that the user can decide to write or leave empty (in that case the variable value in the DB is NULL). The problem is that there is a fixed number of that values that the user can write (in this case 3), but he can decide for each row number to fill x1,x2,x3 or none. In case of None for all Variables I would like to have only one row (just like if the user writes only on one variable). Other problem in this is that the master table of my db that i'm querying has the xs as columns. DFor multiple reasons i had to write them as rows. Also using the N1 is not sufficient for me as (for other reasons) i need to write a progressive that works in that way. x is a NVARCHAR.
N1 Var1 Var2 Var3 Date
1 x1 x2 x3 Date1
2 x1 NULL x3 Date2
3 NULL x2 NULL Date3
I hope i was clear enough. Thanks in advance for your help!
I tried already using ROW_NUMBER() with different combinations of date and x, but without success.
This is an unpivot operation. However, I much, much prefer using cross apply rather than unpivot. Cross apply implements lateral joins, which are powerful type of join operation, included in the SQL standard. Unpivot is bespoke syntax that has only one use. So:
select t.n1, v.var, t.date
from t cross apply
(values (Var1), (Var2), (Var3)
) v(var)
where var is not null;
Seems you need such an UNPIVOT operation :
SELECT N1, Var, Date
FROM tab
UNPIVOT ( Var FOR lst_Var IN (var1, var2, var3) ) AS unpvt;
Demo

How to get a value from previous result row of a SELECT statement?

If we have a table called FollowUp and has rows [ ID(int) , Value(Money) ]
and we have some rows in it, for example
ID --Value
1------70
2------100
3------150
8------200
20-----250
45-----280
and we want to make one SQL Query that get each row ID,Value and the previous Row Value in which data appear as follow
ID --- Value ---Prev_Value
1 ----- 70 ---------- 0
2 ----- 100 -------- 70
3 ----- 150 -------- 100
8 ----- 200 -------- 150
20 ---- 250 -------- 200
45 ---- 280 -------- 250
i make the following query but i think it's so bad in performance in huge amount of data
SELECT FollowUp.ID, FollowUp.Value,
(
SELECT F1.Value
FROM FollowUp as F1 where
F1.ID =
(
SELECT Max(F2.ID)
FROM FollowUp as F2 where F2.ID < FollowUp.ID
)
) AS Prev_Value
FROM FollowUp
So can anyone help me to get the best solution for such a problem ?
This sql should perform better then the one you have above, although these type of queries tend to be a little performance intensive... so anything you can put in them to limit the size of the dataset you are looking at will help tremendously. For example if you are looking at a specific date range, put that in.
SELECT followup.value,
( SELECT TOP 1 f1.VALUE
FROM followup as f1
WHERE f1.id<followup.id
ORDER BY f1.id DESC
) AS Prev_Value
FROM followup
HTH
You can use the OVER statement to generate nicely increasing row numbers.
select
rownr = row_number() over (order by id)
, value
from your_table
With the numbers, you can easily look up the previous row:
with numbered_rows
as (
select
rownr = row_number() over (order by id)
, value
from your_table
)
select
cur.value
, IsNull(prev.value,0)
from numbered_rows cur
left join numbered_rows prev on cur.rownr = prev.rownr + 1
Hope this is useful.
This is not an answer to your actual question.
Instead, I feel that you are approaching the problem from a wrong direction:
In properly normalized relational databases the tuples ("rows") of each table should contain references to other db items instead of the actual values. Maintaining these relations between tuples belongs to the data insertion part of the codebase.
That is, if containing the value of a tuple with closest, smaller id number really belongs into your data model.
If the requirement to know the previous value comes from the view part of the application - that is, a single view into the data that needs to format it in certain way - you should pull the contents out, sorted by id, and handle the requirement in view specific code.
In your case, I would assume that knowing the previous tuples' value really would belong in the view code instead of the database.
EDIT: You did mention that you store them separately and just want to make a query for it. Even still, application code would probably be the more logical place to do this combining.
What about pulling the lines into your application and computing the previous value there?
Create a stored procedure and use a cursor to iterate and produce rows.
You could use the function 'LAG'.
SELECT ID,
Value,
LAG(value) OVER(ORDER BY ID) AS Prev_Value
FROM FOLLOWUP;