Czy da się coś takiego zrobić w pętli w mysql

Czy da się coś takiego zrobić w pętli w mysql
T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Mam taki problem nigdy nie uzywałem pętli bezpośrednio w mysql, ale chyba teraz będa potrzebne.
Mam sobie cos takiego jest sobie funkcja w laravelu.

Kopiuj
public static function selectOldUsee(int $idProduct,string $dateEnd,int $idUsers,int $startDay,$hour) {
  return self::join("products","products.id","usees.id_products")
    ->selectRaw("sum(usees.portion) as portions")
    ->selectRaw("products.type_of_portion as type")
    ->selectRaw("
                        count(usees.portion)

                      //tutaj w tym miejscyu
                    as how")
    ->selectRaw(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) as dat "))
    ->where("usees.date","<=",$dateEnd)
    ->where("usees.id_products",$idProduct)
    ->where("usees.id_users",$idUsers)
    ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) <= '$hour[1]'")
    ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) >= '$hour[0]'")
    ->groupBy(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) )  "))
    ->orderBy("usees.date","DESC")
    ->get();
}

I teraz zaniemiem tego jest wyświetlanie liczby rekordów w danym dniu, i sie dobrze wyświetla, ale są rekordy, które miały datę przykładowo 2024.10.23 12:12:00 i 2024.10.23 12:13:00

Czyli różnica w czasie ich dodania różni się maxymalnie 3 minuty to wtedy dany rekord będzie zapisywany jako 1 wystapienie a nie dwa czyli jak będą takie rekordy w danym dniu

Kopiuj
2024.10.23 12:12:00 i 2024.10.23 12:13:00  i 2024.10.23 15:12:00 i 2024.10.23 20:12:00

To wtedy zapytanie ma zwrócić count = 3

I w pętli mysql da się to zrobić, ale nigdy wcześniej nie korzystałem z pętli w mysql

No uruchamiam w mysql adminie taka kkonstrukcję

Kopiuj
CREATE PROCEDURE testif()
BEGIN
    SET @number_title = 0;
    SET @max1 = 1;
    label1: LOOP
        IF (@number_title<@max1) THEN 
            ITERATE label1;
        END IF;
        SET @number_title := @number_title + 1;
    END LOOP;
END $$

To wyskakuje błąd

Kopiuj
Nie udało się wykonać SQL : Wykonanie SQL CREATE PROCEDURE testif() BEGIN SET @number_title = 0; SET @max1 = 1; label1: LOOP IF (@number_title<@max1) THEN ITERATE label1; END IF; SET @number_title := @number_title + 1; END LOOP; END $$ nie powiodło się : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '$$' at line 1

Natomiast juz cos takiego jest dobrze,

Kopiuj
BEGIN
# LOOP - Obiegi początek
SET @obieg=1;
obieg_loop: LOOP
SELECT 'obieg: '+@obieg;
IF @obieg=1 THEN
SET @zxc=5;
ELSEIF @obieg=2 THEN
SET @zxc=4;
ELSEIF @obieg=3 THEN
SET @zxc=7;
ELSE
SELECT 'błąd';
END IF;
SELECT @zxc;
END

Ale jak ten sam kod wkleje w zapytanie w kodzie php(laravel) to wysakuje ten sam błąd

lion137
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 5025
0

A spróbuj zmienić delimiter na $$ dla tej procedury; acha i możesz utworzyć ją raz na serwerze i potem tylko wołać w kodzie

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Ta funkcja lag by była chyba dobra tylko teraz jak próbuje uzyć w niej instrukcji WHEN albo IF to wyskakuje błąd na poziomie mysql jeszcze jutro po próbuje.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
2

Wyszedłeś z założenia,.że potrzebujesz pętli. Opis jest dla mnie niejasny, bo nie wiem co masz w tabelach.

Ale mam 99 % pewności że pętla nie jest potrzebna, ale z tego opisu to nie bardzo wiem co chcesz osiągnąć.

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Chodzi o to, żeby zliczyć ilość rekordów w zalezności od tego jaka będzie różnica między teraźniejszym, a młodszym rekordem w bazie.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

Jaka wersja mysql/Mariadb?

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

mysql Ver 15.1 Distrib 10.6.18-MariaDB,

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
2

pola czas,poprzedni,roznicaminuty nie jest potrzebny, dałem tylko do celów edukacujnych

Kopiuj
select 
	sum(czy_liczyc) 
FROM (SELECT 
        czas,
        LAG(czas) over (order by czas) poprzedni,
        TIMESTAMPDIFF(MINUTE,LAG(czas) over (order by czas),czas) roznicaminuty,
        case when coalesce(TIMESTAMPDIFF(MINUTE,LAG(czas) over (order by czas),czas),10) > 3 then 1 else 0 end czy_liczyc
	from 
       tab) r

https://sqlfiddle.com/mariadb/online-compiler?id=d85c55e4-e5e9-44ac-92d5-564af0145280

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Teraz tak to działa, ale próbuje to zapytanie wstawić w mój model, a że to jest z dwa razy from to nie wiem jakich dodatkowych funkcji uzyć bo ja to używam wmodelu próbowałem coś takiego i nie idzie

Kopiuj
return self::join("products","products.id","usees.id_products")
                ->join("substances_products","substances_products.id_products","products.id")
                ->select(DB::Raw(" select sum(FROM (SELECT 
        date,
        LAG(date) over (order by date) poprzedni,
        TIMESTAMPDIFF(MINUTE,LAG(date) over (order by date),date) roznicaminuty,
        case when coalesce(TIMESTAMPDIFF(MINUTE,LAG(date) over (order by date),date),10) > 3 then 1 else 0 end czy_liczyc
	)) 
  as how 
                         "))
                ->selectRaw(" "
                        . "( CASE "
                        . " WHEN products.type_of_portion = 2  THEN ('2' ) "
                        . " WHEN products.type_of_portion = 4  THEN ('4' ) "
                        . " WHEN products.type_of_portion = 5  THEN ('5' ) "
                        . " WHEN products.type_of_portion = 6  THEN ('6' ) "
                        . " WHEN substances_products.Mg_Ug = 2  THEN ('7' ) "
                        . "ELSE '1' "
                        . " END)"
                        . "  as type ")
                ->selectRaw(" round(sum("
                        . " CASE "
                        . " WHEN products.type_of_portion = 2  THEN ( (products.how_percent / 100) * usees.portion ) " 
                        . " WHEN substances_products.doseProduct is NULL  THEN (usees.portion ) "
                        . "ELSE (substances_products.doseProduct * usees.portion) "
                        . " END),2)"
                        . "  as portions ")
                ->selectRaw(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) as dat "))
                ->where("usees.date","<=",$dateEnd)
                ->where("substances_products.id_substances",$idSubstances)
                ->where("usees.id_users",$idUsers)
                ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) <= '$hour[1]'")
                ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) >= '$hour[0]'")
                ->groupBy(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) )  "))
                ->orderBy("usees.date","DESC")
                ->get();
PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

Nie znam sie na php, mogę się tylko dymślać jak to działa, ale ten fragment kodu:

Kopiuj
 ->select(DB::Raw(" select sum(FROM (SELECT 
        date,
        LAG(date) over (order by date) poprzedni,
        TIMESTAMPDIFF(MINUTE,LAG(date) over (order by date),date) roznicaminuty,
        case when coalesce(TIMESTAMPDIFF(MINUTE,LAG(date) over (order by date),date),10) > 3 then 1 else 0 end czy_liczyc
	)) 
  as how 
                         "))

Zawiera niepoprawny SQL, więc siłą rzeczy nie zadziała

Jak już to próbowałbym tak:

Kopiuj
select(DB::Raw("(select sum(czy_liczyc) FROM (SELECT case when coalesce(TIMESTAMPDIFF(MINUTE,LAG(czas) over (order by czas),czas),10) > 3 then 1 else 0 end czy_liczyc from  tab) r ) as how"))

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Takie pytanie czy uyzywając funkcji LAG koniecznie trzeba dwa razy uzyć select nie da się w to w jednym zapytaniu ?

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

Trzeba w podzapytanie, bo nie da się użyć window function w funkcjach agregujących, i to jest jedno zapytanie.

jurek1980
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3581
0

Spróbuj albo

Kopiuj
    ->addSelect(DB::raw('(Twoje podzapytanie z LEG) as subSeelctData'))
    ->get();

Albo

Kopiuj
->whereRaw('(select....)')

Pamiętaj o użyciu
toSql() by zdebugować zapytanie.

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Musiałem to zapytanie zkonwertować do mojego całościowego zapytanie

Teraz mam takie zapytanie.

Kopiuj
select sum(czy_liczyc) as how from (select  LAG(usees.date) over (order by usees.date) , TIMESTAMPDIFF(MINUTE,LAG(usees.date) over (order by usees.date),usees.date) , case when coalesce(TIMESTAMPDIFF(MINUTE,LAG(usees.date) over (order by usees.date),usees.date),10) > 3 then 1 else 0 end czy_liczyc , ( CASE WHEN products.type_of_portion = 2 THEN ('2' ) WHEN products.type_of_portion = 4 THEN ('4' ) WHEN products.type_of_portion = 5 THEN ('5' ) WHEN products.type_of_portion = 6 THEN ('6' ) WHEN substances_products.Mg_Ug = 2 THEN ('7' ) ELSE '1' END) as type , round(sum( CASE WHEN products.type_of_portion = 2 THEN ( (products.how_percent / 100) * usees.portion ) WHEN substances_products.doseProduct is NULL THEN (usees.portion ) ELSE (substances_products.doseProduct * usees.portion) END),2) as portions , (DATE(IF(HOUR( usees.date) >= '5', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) as dat  from `usees` inner join `products` on `products`.`id` = `usees`.`id_products` inner join `substances_products` on `substances_products`.`id_products` = `products`.`id` where `usees`.`date` <= '2023-10-10' and `substances_products`.`id_substances` = 23 and `usees`.`id_users` = 38 and (time(date_add(usees.date,INTERVAL - 5 hour))) <= '23:59:00' and (time(date_add(usees.date,INTERVAL - 5 hour))) >= '00:00:00' group by (DATE(IF(HOUR( usees.date) >= '5', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) order by `usees`.`date` desc ) as `r`

Tylko, że ono sumuje całe dni i wynik daje tylko w jednym wyniki a mi chodzi, żeby pogrupowało na dni.

PA
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 3891
0

@tomixtomi0001 ja się poddaje. SQL nie wymaga szklanej kuli, a podania danych na których się pracuje. Jak zauwazyłeś celowo podalem ci wynik na fiddle bo to umożliwa wspólna pracę nad zagadnieniem. Tobie nawet nie chce sie przygotowac reprezentacyjnego przykładu danych, co rzucasz ogólniki do rozwiązania, nie wyjasniając na jakich danych pracujesz. Już nie wspomnę że podajeśz SQL w jednej linijce, zamiast go sformatować np. tak:

Kopiuj
SELECT 
	Sum(czy_liczyc) AS how
FROM   (
                  SELECT     Lag(usees.date) over (ORDER BY usees.date) ,
                             timestampdiff(minute,lag(usees.date) over (ORDER BY usees.date),usees.date) ,
                             CASE
                                        WHEN coalesce(timestampdiff(minute,lag(usees.date) over (ORDER BY usees.date),usees.date),10) > 3 THEN 1
                                        ELSE 0
                             end czy_liczyc , (
                             CASE
                                        WHEN products.type_of_portion = 2 THEN ('2' )
                                        WHEN products.type_of_portion = 4 THEN ('4' )
                                        WHEN products.type_of_portion = 5 THEN ('5' )
                                        WHEN products.type_of_portion = 6 THEN ('6' )
                                        WHEN substances_products.mg_ug = 2 THEN ('7' )
                                        ELSE '1'
                             end) AS type ,
                             round(sum(
                             CASE
                                        WHEN products.type_of_portion = 2 THEN ( (products.how_percent / 100) * usees.portion )
                                        WHEN substances_products.doseproduct IS NULL THEN (usees.portion )
                                        ELSE (substances_products.doseproduct * usees.portion)
                             end),2)                                                                                   AS portions ,
                             (date(IF(hour( usees.date) >= '5', usees.date,date_add(usees.date, INTERVAL - 1 day) )) ) AS dat
                  FROM       
					`usees`
					INNER JOIN `products` ON `products`.`id` = `usees`.`id_products`
					INNER JOIN `substances_products` ON `substances_products`.`id_products` = `products`.`id`
                  WHERE      
					`usees`.`date` <= '2023-10-10'
					AND `substances_products`.`id_substances` = 23
					AND `usees`.`id_users` = 38
					AND time(date_add(usees.date,INTERVAL - 5 hour)) <= '23:59:00'
					AND time(date_add(usees.date,INTERVAL - 5 hour)) >= '00:00:00'
                  GROUP BY
					date(IF(hour( usees.date) >= '5', usees.date,date_add(usees.date, INTERVAL - 1 day) ))
                  ORDER BY   
				     `usees`.`date` DESC 
		) AS `r`

Ja mam w sobie sporo cierpliwości, ale nie jestem tutaj na zleceniu, więc jak chcesz pomocy to daj coś od siebie a nie tylko wymagaj

T0
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 197
0

Wprowadziłem kilka zmian i udało mi się zrobic tak jak ja chciałem

To jest kod

Kopiuj
   public static function selectOldUseeSubstances(int $idSubstances,string $dateEnd,int $idUsers,int $startDay,$hour) {
        return self::select(DB::Raw("sum(czy_liczyc) as how"))
        ->selectRaw("type")
        ->selectRaw("sum(portions) as portions")
        ->selectRaw("dat as dat")
        ->fromSub(function ($query) use ($idSubstances,$dateEnd,$idUsers,$startDay,$hour) {
            $query->from('usees')
            ->join("products","products.id","usees.id_products")
            ->join("substances_products","substances_products.id_products","products.id")

            ->select(DB::Raw("(  Lag(usees.date)  over (ORDER BY dat ) ) ,
                           (  timestampdiff(minute,lag(usees.date) over (ORDER BY dat ),usees.date) ) ,
                             ( CASE
                                        WHEN coalesce(timestampdiff(minute,lag(usees.date) over (ORDER BY usees.date ),usees.date),10) > 3 THEN 1
                                        ELSE 0
                             end ) as czy_liczyc "))
            ->selectRaw(" "
                    . "( CASE "
                    . " WHEN products.type_of_portion = 2  THEN ('2' ) "
                    . " WHEN products.type_of_portion = 4  THEN ('4' ) "
                    . " WHEN products.type_of_portion = 5  THEN ('5' ) "
                    . " WHEN products.type_of_portion = 6  THEN ('6' ) "
                    . " WHEN substances_products.Mg_Ug = 2  THEN ('7' ) "
                    . "ELSE '1' "
                    . " END)"
                    . "  as type ")
            ->selectRaw(" round(sum("
                        . " CASE "
                        . " WHEN products.type_of_portion = 2  THEN ( (products.how_percent / 100) * usees.portion ) " 
                        . " WHEN substances_products.doseProduct is NULL  THEN (usees.portion ) "
                        . "ELSE (substances_products.doseProduct * usees.portion) "
                        . " END),2)"
                        . "  as portions ")
            ->selectRaw(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) as dat "))
            ->where("usees.date","<=",$dateEnd)
            ->where("substances_products.id_substances",$idSubstances)
            ->where("usees.id_users",$idUsers)
            ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) <= '$hour[1]'")
            ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) >= '$hour[0]'")
            ->groupBy(DB::Raw(" 
				usees.date"));
            

        }, 'r')->groupBy("dat")
        ->orderBy("dat","DESC")
        ->get();
Panczo napisał(a):

@tomixtomi0001 ja się poddaje. SQL nie wymaga szklanej kuli, a podania danych na których się pracuje. Jak zauwazyłeś celowo podalem ci wynik na fiddle bo to umożliwa wspólna pracę nad zagadnieniem. Tobie nawet nie chce sie przygotowac reprezentacyjnego przykładu danych, co rzucasz ogólniki do rozwiązania, nie wyjasniając na jakich danych pracujesz. Już nie wspomnę że podajeśz SQL w jednej linijce, zamiast go sformatować np. tak:

Ja mam w sobie sporo cierpliwości, ale nie jestem tutaj na zleceniu, więc jak chcesz pomocy to daj coś od siebie a nie tylko wymagaj

I przepraszam, że to tak wyszło z mojej strony.

Riddle
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 10227
0
tomixtomi0001 napisał(a):
Kopiuj
public static function selectOldUsee(int $idProduct,string $dateEnd,int $idUsers,int $startDay,$hour) {
  return self::join("products","products.id","usees.id_products")
    ->selectRaw("sum(usees.portion) as portions")
    ->selectRaw("products.type_of_portion as type")
    ->selectRaw("
                        count(usees.portion)

                      //tutaj w tym miejscyu
                    as how")
    ->selectRaw(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) ) as dat "))
    ->where("usees.date","<=",$dateEnd)
    ->where("usees.id_products",$idProduct)
    ->where("usees.id_users",$idUsers)
    ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) <= '$hour[1]'")
    ->whereRaw("(time(date_add(usees.date,INTERVAL - $startDay hour))) >= '$hour[0]'")
    ->groupBy(DB::Raw("(DATE(IF(HOUR(    usees.date) >= '" . $startDay . "', usees.date,Date_add(usees.date, INTERVAL - 1 DAY) )) )  "))
    ->orderBy("usees.date","DESC")
    ->get();
}

Jak widzę takie "orm", to autentycznie wolę już gołe SQL.

VBService
  • Rejestracja: dni
  • Ostatnio: dni
0

CREATE PROCEDURE testif()
BEGIN
SET @number_title = 0;
SET @max1 = 1;
label1: LOOP
IF (@number_title<@max1) THEN
ITERATE label1;
END IF;
SET @number_title := @number_title + 1;
END LOOP;
END $$

IMO spróbuj może taki zapis:

Kopiuj
DELIMITER $$

CREATE PROCEDURE testif()
BEGIN
    DECLARE number_title INT DEFAULT 0;
    DECLARE max1 INT DEFAULT 1;
    
    label1: LOOP
        IF (number_title < max1) THEN
            SET number_title = number_title + 1;
            ITERATE label1;
        END IF;
        LEAVE label1;
    END LOOP;
END$$

DELIMITER ;

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.